Webhook testing, explained
A webhook is an HTTP request that one service sends to another when something happens: GitHub when code is pushed, Stripe when a payment succeeds, Shopify when an order is created. To build the receiving side you need to see exactly what arrives. A webhook tester gives you a URL that captures those requests so you can inspect them before (or instead of) writing your handler.
Why your own Worker instead of a shared service?
Services like webhook.site and RequestBin receive your requests on their shared servers, which means customer data, tokens and payment events pass through a third party. Here the endpoint runs as a Cloudflare Worker in your own account and stores requests in your own KV namespace. This page talks only to your Worker, from your browser. The Worker code is short enough to read before you deploy it.
Verifying webhook signatures
Anyone who knows your webhook URL can send requests to it, so providers sign each request with a secret only you and they know. Your server must recompute the signature over the raw body and compare it in constant time:
| Provider | Header | Signed content | Format |
| GitHub | X-Hub-Signature-256 | body | sha256= + hex HMAC-SHA256 |
| Stripe | Stripe-Signature | timestamp + "." + body | t=…,v1= hex HMAC-SHA256 |
| Slack | X-Slack-Signature | "v0:" + timestamp + ":" + body | v0= + hex HMAC-SHA256 |
| Shopify | X-Shopify-Hmac-Sha256 | body | base64 HMAC-SHA256 |
| Standard Webhooks / Svix | webhook-signature | id + "." + timestamp + "." + body | v1, + base64, secret is base64 (whsec_) |
The most common bug is verifying a re-serialised body: frameworks that parse JSON first and stringify it again change spaces and key order, so the signature never matches. Read the raw bytes. Also reject old timestamps, so a captured request can't be replayed later.
Testing retries and failures
Providers treat anything other than a 2xx response, or no answer within their timeout (ten seconds for GitHub), as a failed delivery. Stripe then retries for up to three days; GitHub marks the delivery as failed and lets you redeliver it from the webhook settings. Set a custom response of 500 or a delay to see what the provider does, then switch back to 200. Webhooks can also arrive more than once or out of order, so handlers should be idempotent: store the event or delivery ID and ignore duplicates.
Replay and local development
Once you have captured a real request, you can send it again to your development server instead of triggering a new event each time. Replay from the page works when your server allows cross-origin requests; otherwise copy the request as cURL and run it in a terminal. To receive webhooks on your own laptop directly, use a tunnel such as Cloudflare Tunnel or ngrok, or the provider's own CLI (the Stripe CLI can forward events to localhost).
Privacy and limits
Captured requests live in your KV namespace: the latest 100 per endpoint, deleted automatically seven days after the last request. Endpoint IDs are random and unguessable, but anyone with the URL can send to it and read it through your Worker, so treat the URL as a secret and create a new endpoint when you are done. Signing secrets you enter for verification stay in this browser's local storage and are never sent anywhere. Cloudflare's free plan allows 100,000 Worker requests and 1,000 KV writes per day.