Skip to main content
A few well-trodden patterns for testing a webhook handler before pointing it at production traffic.

Recipe 0: Send a test event from the FPT admin

The fastest end-to-end test, requiring no setup beyond an existing subscription. From the FPT admin’s webhook-endpoints page, fire a single synthetic event to your URL on demand — signed with your real signing secret, so your verification code path is exercised exactly as production traffic would exercise it.
1

Open Settings → Webhook Endpoints

Find your subscription’s row in the grid. The first action button (paper-plane icon) is Send test event.
2

Pick an event type

A dialog opens with radio buttons for each event type your subscription is subscribed to (we filter the choices so you can’t test something you wouldn’t receive in production).
3

Inspect the payload preview

Below the event selector, FPT shows the exact JSON body that will be POSTed to your endpoint — refreshed live every time you change the event type. Use the copy button to grab the payload for your own unit-test fixtures without ever firing a real send. eventId and eventTimestamp get regenerated on actual send; everything else matches.
4

Click Send

FPT builds the same payload again with a fresh eventId/eventTimestamp, signs it with your subscription’s secret, POSTs to your URL with a 10-second timeout, and records the attempt.
5

Read the result

The dialog shows the HTTP status pill, request duration, and the eventId we generated. On non-2xx, you also get the error message. Click Send again to re-fire the same event type.

How test events look on the wire

Test events are intentionally distinguishable from production traffic, so your handler can branch (or just dedupe) on them:
Test deliveries are recorded in the FPT admin’s deliveries history with attemptNumber: 0, so you can distinguish them from production traffic (which always starts at attemptNumber: 1).

Inspect and replay any delivery

From the Webhook Endpoints page, the clock-icon button on any row opens the deliveries drill-down — a paged history of every event FPT has fired to that endpoint with:
  • Health strip — last 24h count, success rate, status, last successful delivery
  • Time-range filter — last 1h / 24h / 7d / 30d / all
  • Status / event type / eventId filters — slice to just failures, or just a specific event type, or find one event by eventId prefix
  • Click-to-expand row — see the exact signed JSON body and X-FPT-Signature header that we POSTed
  • Copy as curl — one-click reproduction of the request as a curl command, with the original signature header preserved so your verifier passes on replay
This makes debugging a partner’s failed delivery a 30-second exercise instead of a 30-minute back-and-forth.

Recipe 1: webhook.site (zero-code receiver)

The fastest possible test. Use webhook.site to capture raw POSTs and inspect them without writing any code.
1

Open webhook.site

Open webhook.site — you’ll get a unique URL like https://webhook.site/8a3f-7c2e-...
2

Subscribe in FPT

Settings → Webhook Endpoints → Add Endpoint. Paste your webhook.site URL. Check the events you want — for the fastest verification, pick contact.status_changed (you can trigger it on demand by moving a test contact between lifecycle groups). Save.
3

Trigger an event

Easiest path: click the Send test event paper-plane icon on your endpoint’s row. A dialog opens with a live payload preview and fires a real signed POST to your URL. To verify with a real (non-test) event, move a test contact between lifecycle groups (Lead → Member) — within ~10 seconds, the request lands on webhook.site with full headers (including X-FPT-Signature) and body.
4

Inspect

Use webhook.site’s UI to expand headers, view the JSON body, and replay the request to a different URL if you want to forward to your local dev server.
webhook.site is great for inspection but don’t leave production subscriptions pointed at it — payloads contain customer data, and webhook.site is a public service.

Recipe 2: ngrok + your local server

For active development on your real handler, expose your localhost to the internet via ngrok:
1

Install ngrok and start a tunnel to your local server

ngrok prints a public HTTPS URL like https://abc123.ngrok-free.app.
2

Subscribe in FPT to the ngrok URL

Settings → Webhook Endpoints → URL: https://abc123.ngrok-free.app/webhooks/fpt
3

Iterate

Hit save in your code, restart your server, trigger an event in FPT. Watch the request hit your local handler in real time. Set breakpoints, log payloads, refactor.
ngrok URLs change on every restart in the free tier. Update the subscription URL in FPT each time, or get an ngrok paid plan with a reserved domain.

Recipe 3: Capture-and-replay

Once you have a few real events captured (from webhook.site or your own logs), you can replay them locally without touching FPT at all. This is the fastest dev loop for handler logic — no waiting on events to fire.
The captured signature is only valid for ~5 minutes after we sent it (our default timestamp tolerance) and for the exact body we signed. If you reformat the JSON or wait too long, signature verification will fail. For local replay testing where you want signature verification to keep working indefinitely, disable the timestamp tolerance check in your dev environment — but never in production.

Recipe 4: Generate test payloads in code

For unit tests of your handler logic, build synthetic events directly:

Common gotchas

Express, Flask, ASP.NET — most web frameworks parse JSON into an object before your handler runs. If you sign JSON.stringify(req.body) instead of the raw bytes, you’ll fail verification. See Signature verification for the right pattern.
Free-tier ngrok rotates the subdomain on every restart. Either update the FPT subscription each time, or use a paid plan with a reserved domain.
Free webhook.site sessions expire after a few days of inactivity. Your unique URL is durable but the captured request log may rotate.
If you store a captured signature in a fixture file and try to replay it weeks later, your verifier’s timestamp tolerance check will reject it. For dev/test environments, consider disabling the tolerance check or regenerating fixtures dynamically.