Hookbase CLI v2.4 — Scaffold, Filter, and Sign in One Release
The new hookbase init command scaffolds a working webhook handler in any of five frameworks. hookbase listen filters tunnel traffic before it reaches localhost. And hookbase trigger now signs payloads using the live provider catalog. Three additions, one upgrade.
The CLI is the most direct surface developers touch when working with Hookbase. v2.4 ships three additions that compound: scaffold a handler, run a filtered tunnel against it, and trigger signed test events into it. Same workflow, three less papercuts.
npm install -g @hookbase/[email protected]
1. hookbase init — scaffold a working webhook handler
Starting a webhook receiver from scratch involves a handful of mistakes everyone makes once: parsing the body before verifying the signature, comparing signatures with === instead of timing-safe equality, mixing up the timestamp tolerance for Stripe vs Slack. We just generate the right code for you.
hookbase init --framework express --provider stripe --dir ./my-handler
Five frameworks supported:
- Express — Node, raw-body parser via
express.raw - Fastify — Node, custom string content-type parser to preserve raw bytes
- Hono — Node/Bun/edge runtimes
- Next.js — App Router route handler at
app/webhook/route.ts - Cloudflare Worker — uses Web Crypto so no Node modules
Five providers supported (signature verifier specific to each):
- Stripe —
t=<ts>.v1=<sig>with 5-minute timestamp tolerance - GitHub —
sha256=<hex> - Shopify — base64-encoded HMAC-SHA256
- Slack —
v0=<hex>overv0:timestamp:body - Custom — generic HMAC-SHA256
The generated handler reads the raw body (which signature verification requires), runs the provider-correct verifier inline, parses JSON only after verification passes, and returns 401 on bad signatures. Zero runtime dependencies beyond the framework itself.
my-handler/
├── index.js # Working handler with Stripe verifier inline
├── package.json
├── .env.example
└── README.md
cd my-handler && npm install && npm run dev
That's a runnable receiver. Point a Hookbase tunnel at it and you're handling real Stripe events.
2. hookbase listen — filter at the tunnel boundary
hookbase listen <port> is a top-level alias for tunnels start. The reason it exists isn't the rename — it's the new filter flags.
A common dev setup: one tunnel, multiple sources, lots of noise. Maybe you're debugging a Stripe charge.succeeded flow but a chatty internal source keeps blasting unrelated events into your handler logs. Until now, you had to filter inside your handler.
# Only forward charge.* events
hookbase listen 3000 --filter-event 'charge.*'
# Multiple filters AND together
hookbase listen 3000 --filter-source stripe-prod --filter-event 'charge.succeeded'
# Arbitrary JSONata over the parsed body
hookbase listen 3000 --filter-expr 'amount > 1000'
Three filter dimensions, all repeatable and stackable:
--filter-source <slug>— matchx-hookbase-sourceheader or path prefix--filter-event <pattern>— glob (*and?) against detected event type. Detection sniffsx-github-event,x-shopify-topic, the Stripe body's.type, and generic body fields likeeventandevent_type.--filter-expr <jsonata>— JSONata expression evaluated against the parsed body. Truthy = pass.
Filtered requests come back from the tunnel with status 204 (configurable via --filter-skip-status) and an x-hookbase-skip-reason header explaining why. Your localhost never sees them. The CLI logs each skip:
POST /webhook → skipped (event "customer.subscription.created" did not match [charge.*])
JSONata is lazy-loaded — only when --filter-expr is used — so it doesn't bloat startup for the 90% of users who just want event-type filtering.
3. hookbase trigger — catalog-driven and signed by default
trigger already existed but shipped only five hardcoded sample payloads, sent unsigned. The "stripe-signature" header it sent was the literal string t=1234567890,v1=test. Any source with strict signature verification rejected it. We've reworked it from the ground up.
hookbase trigger --provider stripe --event charge.succeeded
ℹ Triggering webhook to tf-demo-stripe...
✓ Webhook delivered (200)
Signed with stripe-signature header
ℹ Event ID: 66db31af-38a9-49c7-9281-e577e67f993b
What changed:
- Catalog-driven payloads — pulls from the same provider catalog the dashboard uses. Ten-plus providers, dozens of event types per provider, all with realistic sample bodies. No more "github_push", "stripe_payment", "shopify_order" generic placeholders — pick the actual event type.
- Signed by default — the API computes a real HMAC signature using the source's actual
signing_secretand the provider's correct algorithm. Your verification path runs end-to-end. Opt out with--no-signif you specifically want to test rejection. --print— output the would-be payload without sending. Useful for piping into other tools or just inspecting what a Stripecharge.succeededlooks like.- Better errors — typo an event type and you get the list of valid ones for that provider.
The signing logic on the server reuses the same computeSignature() utility that the live ingest path uses to verify signatures. They cannot drift — guarded by 63 round-trip unit tests across every signed-provider catalog entry.
How They Compose
These three commands are designed to chain. A common workflow:
# 1. Scaffold a Stripe handler
hookbase init --framework express --provider stripe --dir ./demo
cd demo && npm install && npm run dev &
# 2. Tunnel to it, but only forward charge events
hookbase listen 3000 --filter-event 'charge.*' &
# 3. Send a real signed Stripe event
hookbase trigger --provider stripe --event charge.succeeded
Three commands, three terminals — and you've exercised the full path: signed payload → ingest signature verification → tunnel filter → localhost handler signature verification → 200 OK. If anything is broken anywhere in the chain, you find out in seconds.
Backward Compatibility
Nothing breaks. tunnel, tunnels start, the legacy --event <template> shorthand on trigger, and every existing flag still work. The new flags are additive.
Get the Update
npm install -g @hookbase/[email protected]
hookbase --version
Or if you're using the GitHub Action: HookbaseApp/setup-tunnel@v1 already pins to the latest CLI by default.