Webhook Playground

Use this page to check how a receiver handles webhook deliveries before a real service sends one. Pick a scenario below: a delivery that fails and is retried, a receiver that is down, a duplicate, events that arrive out of order, a wrong secret, an old event, or a body changed in transit. Then change the secret, the timestamp or the failures, and see whether the receiver accepts, retries or refuses each delivery. Everything runs in your browser. To test your own app's endpoint, see the steps further down the page.

1. Choose a scenario

2. Sender and receiver

Answers: 200 accepted, 500 server error, slow processed but answered after the sender gave up, drop no answer. The last answer repeats if there are more attempts.

3. What happened

Times are simulated, so every run gives the same timeline. Replies from the receiver are shown as the sender sees them.

Receiver's log

How the signature works

The sender signs the text timestamp.body with its secret, using HMAC-SHA256, and sends the result in a header with the timestamp. The receiver makes the same signature from the body it received and its own secret, then compares them. Any change to the body, the timestamp or the secret makes the comparison fail.

Check the timestamp as well as the signature: a valid signature from a week ago should still be refused, so a copy captured earlier cannot be replayed.


  

Test your own endpoint with tinypdf-mock

This page simulates the sender. To test your app's real endpoint, run the mock on your computer. It needs Node.js 18 or newer and nothing to install.

  1. Start your receiver, for example at http://localhost:3000/webhooks, and have it check the signature with the secret you use below.
  2. Send a valid event:
    TINYPDF_WEBHOOK_SECRET=whsec_test npx tinypdf-mock webhook send http://localhost:3000/webhooks --body '{"type":"invoice.paid","id":"evt_1"}'
    The output says "delivered" when your app answers with a 2xx status.
  3. Check refusals: with a wrong secret (TINYPDF_WEBHOOK_SECRET=other) or an old event (--age 900), your app should refuse it. The mock does not retry a 401.
  4. Check retries: have your app answer 503 for a while. The mock prints each try and how long it waits before the next.

To check what your app sends, start the receiver, then point your app at it:

TINYPDF_WEBHOOK_SECRET=whsec_test npx tinypdf-mock webhook receive --port 4848

Your app should send to http://localhost:4848/hooks. Each delivery is printed as ok, mismatch (wrong secret or changed body), expired or malformed.

Get a public address for your computer. A tunnel gives your local port a public URL, so a real service such as Stripe or GitHub can reach it. Start the receiver first, then open a second terminal and run one of these:

ngrok http 4848

ngrok prints a forwarding address such as https://<name>.ngrok-free.app. Add /hooks to it and enter that as the webhook URL in the service. The free ngrok plan needs a free account, and you set the authtoken once with ngrok config add-authtoken.

Cloudflare works the same way and needs no account for a quick tunnel:

cloudflared tunnel --url http://localhost:4848

It prints an address ending in trycloudflare.com. Add /hooks to it as above.

Set TINYPDF_WEBHOOK_SECRET to the secret the service gives you, so the receiver checks its real signature. Anyone who has the tunnel address can send requests to your receiver, so stop the tunnel when you are done.

Full steps and options: the Webhooks section of the tinypdf-mock README.

Questions

Can I test a real provider like Stripe or GitHub with this page?

No. Those services send to a public address, and this page runs in your browser. It simulates the sender and tests your receiver's logic. To receive real webhooks you need a public address, for example a tunnel to your computer.

How do I test my own endpoint?

Use the tinypdf-mock command line tool. npx tinypdf-mock webhook send http://localhost:3000/webhooks sends a signed event to your endpoint, and npx tinypdf-mock webhook receive checks what your app sends. The Webhooks section of its README lists the steps.

How do I capture a real webhook to see what a service sends?

Two options. For a quick look at the raw request with no setup, webhook.site gives you a unique URL and shows everything that arrives. It is a public service, so do not send real secrets or personal data through it. To keep the data on your computer, run npx tinypdf-mock webhook receive --port 4848 and expose it with the ngrok or cloudflared tunnel above.

Why are the retries not repeated at the same speed?

The sender waits longer after each failure (back-off), so a receiver that is struggling gets room to recover. The first wait and the largest wait are set above.

Why is a 4xx answer not retried?

A 4xx answer means the request itself was refused, such as a wrong signature or an old timestamp. Sending the same request again will be refused again, so senders usually stop.

What does a receiver need to do about duplicates?

Remember the event id it has processed, and ignore a second copy. Senders retry when they do not get an answer in time, even if the receiver did process the event.

Is my secret sent anywhere?

No. Signing and checking happen in your browser, and nothing is uploaded.