Local webhooks¶
Receive webhooks on a backend running on your own computer and test your signature check. Time: 15 minutes. You
need: a webhook handler on localhost, a tunnel tool (cloudflared or ngrok), and an app in the console.
Why a tunnel¶
The platform delivers webhooks only to public https:// addresses:
- the URL is
https://, ≤ 2048 characters; - the dialled IP is checked after DNS — private networks (
10.x,192.168.x…), loopback (127.0.0.1,localhost) and link-local addresses are refused (the delivery recordsrefused: not a public address); - redirects (
3xx) are not followed — a3xxis a failed delivery.
So http://localhost:3000 cannot be the target; use a tunnel with a public https address.
Steps¶
- Run your handler locally (for example
POST http://localhost:3000/webhooks/gerege), verifying signatures over the raw body (Verifying signatures). -
Open a tunnel:
-
In the console: your app → "Webhook endpoints" → a new endpoint in the test environment: URL
https://<random>.trycloudflare.com/webhooks/gerege, and an event filter if you like. The answer shows thewhsec_…secret once — store it in your handler's environment. API:POST /api/v1/miniapp/dev/apps/{id}/webhook-endpoints{"environment": "test", "url": "…", "description": "laptop tunnel", "event_types": []}→201 {"endpoint", "webhook_secret", "notice"}(emptyevent_types= all). A test endpoint never receives live events — your live endpoints are untouched. - Send a test:
POST /api/v1/miniapp/dev/apps/{id}/webhook-endpoints/{eid}/testsends a signedwebhook.testto that endpoint while you wait and answers{event_id, endpoint_id, ok, status_code, error, duration_ms, delivery}.ok: truemeans the path real events take works (same signer, same secret). 20 a minute. - Make real events — the simulation, or create and cancel an intent with your test key.
- The console's "Deliveries" (
GET /api/v1/miniapp/dev/apps/{id}/webhook/deliveries?endpoint_id=…) — each event's status (pending,delivered,failed,skipped), attempts, last HTTP code, error, body. Fix your handler and "Send again" (POST …/webhook/deliveries/{event_id}/retry?endpoint_id=…) — the event comes again with its original body and a signature over a newt. - When you are done, delete the test endpoint or
PATCH {"status": "disabled"}— an endpoint whose tunnel address died fails for five days, is thenauto_disabled, and your organisation's admins are notified.
Things to know¶
- Rotating the secret:
POST …/webhook-endpoints/{eid}/rotate-secret— a newwhsec_…, and the old one keeps signing alongside for 24 hours (twov1per event). If the tunnel's address changes,PATCH {"url": "…"}— the secret stays. - The app's original webhook (endpoint №1,
POST …/webhook{"url"}) is one URL for both test and live. Do not point a live app's №1 at a local tunnel — add a separate endpoint for test. The oldPOST …/webhookcall still issues a new secret every time. - Endpoint №1's URL and secret cannot change while the app is in review or suspended (
409 locked_for_review). - An event that falls due while its endpoint is off (
disabled,auto_disabled) isskipped— fetch what you missed from the Events API. - Public "request bin" sites (webhook.site and the like) show every event body to a third party — never use them for live data; use your own tunnel.
- Answer 2xx within 10 seconds; queue slow work. Stopping on a debugger breakpoint times the delivery out and puts it on the retry schedule (Delivery and retries).