Skip to content

Forward webhooks to your app

.md

The most common Tapeline setup: your app runs on localhost, Tapeline sits in front of it, and a tunnel gives the pair a public URL you paste into a provider dashboard. Real sandbox events then arrive on your machine, get recorded, and land in your handler — and when the handler fails, you fix the code and replay the same event instead of triggering the provider again.

This guide uses a payment provider as the example, but the flow is identical for any service that delivers webhooks.

  • Your app runs locally and has a webhook route. The examples assume http://localhost:3000/webhooks.
  • The Tapeline CLI is installed.
  • You have any tunnel CLI available — cloudflared, ngrok, or whatever your team already uses. Tapeline itself never opens a public URL.

Run your app as usual so the webhook route is live:

Terminal — window 1
npm run dev

In a second terminal, start the listener in forward mode:

Terminal — window 2
tapeline listen --forward http://localhost:3000/webhooks
Output
✔ listening on http://127.0.0.1:8787/hooks
✔ forwarding to http://localhost:3000/webhooks
✔ recording to .tapeline/events.ndjson

From now on, every request that reaches /hooks is stored first, then re-sent to your app with the method, path, headers, and body untouched. The provider receives whatever status your app returns.

Give the listener a public URL:

Terminal — window 3
cloudflared tunnel --url http://127.0.0.1:8787

The tunnel prints a public hostname. In your provider’s dashboard, set the webhook endpoint to that hostname plus the listener path:

Provider dashboard — webhook endpoint
https://lucky-otter-demo.trycloudflare.com/hooks

Use the provider’s dashboard or test tooling to fire a sandbox event, then watch it arrive:

Terminal — window 2
tapeline tail --follow
Output
12:04:11 POST /hooks payment.settled → 200 in 41 ms stored as evt_8xk2mp

A 200 from your app means the whole chain works: provider → tunnel → Tapeline → handler.

Here is the loop Tapeline exists for. Suppose the next event breaks your handler:

Output
12:05:02 POST /hooks customer.updated → 500 in 9 ms stored as evt_9f3kq2

The event is already captured, so you do not need the provider anymore:

  1. Read the failure in your app’s logs and fix the code.

  2. Replay the exact same event:

    Terminal — window 3
    tapeline replay evt_9f3kq2
  3. Repeat until the replay reports a 200:

    Output
    ✔ replayed customer.updated → 200 in 18 ms
  • If your app is down or times out (forward.timeout_ms, default 5 seconds), the delivery is marked failed in the tail output — but the event is still stored, so you can replay it once your app is back.
  • Tapeline forwards events in the order they arrive and does not retry on its own. Retrying is an explicit tapeline replay, so you always know what hit your handler and when.
  • Signature verification keeps working on forwards and replays because the original bytes are preserved. See the FAQ note about timestamped signatures for the one caveat.