Test a Stripe webhook

Test a Stripe webhook, without deploying.

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.

  • Stripe-Signature
  • Content-Type

Where to paste the URL

Four steps, no server-side configuration. Replace ‹domain› and ‹your-id› with your session's values.

  1. 1

    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.

  2. 2

    In the Stripe Dashboard, open Developers → Webhooks (or Workbench → Webhooks depending on the dashboard version), then "Add endpoint".

  3. 3

    Paste the capture URL into the "Endpoint URL" field, choose the events to receive (e.g. payment_intent.succeeded), then save.

  4. 4

    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.

What the received request looks like

The headers below are the ones Stripe documents on every delivery. Tracehook shows them in the order received, with the raw body.

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).

Content-Type

application/json; the body is Stripe's Event object.

Body shape (example, fake values)
{
  "id": "evt_…",
  "object": "event",
  "api_version": "…",
  "created": 1758190000,
  "type": "payment_intent.succeeded",
  "livemode": false,
  "data": {
    "object": {
      "id": "pi_…",
      "object": "payment_intent",
      "…": "…"
    }
  }
}

Common pitfalls

What explains most of the "my webhook isn't arriving" or "invalid signature" reports.

Verify the signature on the raw body

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.

One secret per endpoint and per mode

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.

Tracehook always answers 200

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.

Timestamp tolerance

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.

Filter the events

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.

curl example

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.

With the Stripe CLI

stripe listen --forward-to https://<domain>/h/<your-id>/stripe
stripe trigger payment_intent.succeeded

stripe listen forwards events from your test account to the given URL; stripe trigger generates one. Forwarded requests also carry the Stripe-Signature header.

Limits to know

One value, everywhere.

  • 50last requests per session
  • 1 MiBper request, headers included (413 beyond that)
  • 24 hof inactivity, then everything is erased

Details on encryption and retention: Security. Routes, SSE stream and error codes: API reference. Other provider: test a GitHub webhook.

Your next Stripe webhook — you'll read it live.

No sign-up