Stripe-Signature
Signature of the event: timestamp t= and one or more v1= signatures (HMAC-SHA256 of the raw body with the endpoint's signing secret).
Paste a Tracehook capture URL as a Stripe webhook endpoint and read the event exactly as Stripe sends it: Stripe-Signature header, raw JSON body, live. No account, encrypted, erased after 24 h.
Four steps, no server-side configuration. Replace ‹domain› and ‹your-id› with your session's values.
Create a capture URL (button below). You get an address of the form https://<domain>/h/<your-id>. You can add a sub-path, e.g. /stripe.
In the Stripe Dashboard, open Developers → Webhooks (or Workbench → Webhooks depending on the dashboard version), then "Add endpoint".
Paste the capture URL into the "Endpoint URL" field, choose the events to receive (e.g. payment_intent.succeeded), then save.
Trigger an event in test mode (a test payment, or "Send test webhook" from the endpoint page). The request appears in your Tracehook session the instant Stripe sends it.
The headers below are the ones Stripe documents on every delivery. Tracehook shows them in the order received, with the raw body.
Signature of the event: timestamp t= and one or more v1= signatures (HMAC-SHA256 of the raw body with the endpoint's signing secret).
application/json; the body is Stripe's Event object.
{
"id": "evt_…",
"object": "event",
"api_version": "…",
"created": 1758190000,
"type": "payment_intent.succeeded",
"livemode": false,
"data": {
"object": {
"id": "pi_…",
"object": "payment_intent",
"…": "…"
}
}
}What explains most of the "my webhook isn't arriving" or "invalid signature" reports.
Stripe signs the exact bytes of the body. If your framework parses then re-serializes the JSON before verification, the signature check fails. Tracehook keeps the raw body (Raw tab): copy it as-is to reproduce the calculation.
The signing secret (whsec_…) is specific to each endpoint, and test and live modes have distinct endpoints. A 400 "invalid signature" most often comes from a secret belonging to another endpoint.
The capture URL acknowledges every event. Stripe won't retry it, which is convenient for observing, but don't rely on redelivery attempts to test your failure handling.
Stripe's recommended verification rejects events whose t= is too old. If you replay from Tracehook (copy as cURL) after a long delay, disable that tolerance in your test.
An endpoint subscribed to "all events" receives a lot of traffic; only the last 50 requests are kept per session. Select only the types you care about.
To check that your capture URL does receive a request of this shape, without waiting for the provider.
curl -X POST https://<domain>/h/<your-id>/stripe \
-H 'Content-Type: application/json' \
-H 'Stripe-Signature: t=1758190000,v1=<fake-signature>' \
-d '{"id":"evt_test","object":"event","type":"payment_intent.succeeded","data":{"object":{"id":"pi_test","object":"payment_intent"}}}'This command mimics the shape of a Stripe event (fake values, invalid signature). For a real signed event, go through the Dashboard or the CLI.
stripe listen --forward-to https://<domain>/h/<your-id>/stripe
stripe trigger payment_intent.succeededstripe listen forwards events from your test account to the given URL; stripe trigger generates one. Forwarded requests also carry the Stripe-Signature header.
One value, everywhere.
Details on encryption and retention: Security. Routes, SSE stream and error codes: API reference. Other provider: test a GitHub webhook.