Skip to main content
Stripe webhooks deliver real-time notifications to your server when billing events occur — such as a successful payment, a failed charge, or a cancelled subscription. The webhook endpoint at POST /webhook receives these events, verifies their authenticity, and dispatches them to the appropriate handler.

How the endpoint works

When Stripe sends an event, the endpoint follows three steps:
  1. Reads the raw request body as text (required for signature verification).
  2. Verifies the stripe-signature header using stripe.webhooks.constructEvent. If verification fails, the request is rejected with a 400 status.
  3. Dispatches the event through a switch statement to the handler for that event type.
app/routes/webhook.ts
export const action = async ({ request }: LoaderFunctionArgs) => {
  let event;
  const body = await request.text();
  const stripeSignature = request.headers.get("stripe-signature") as string;
  const webhookSecret = "whsec_...";

  if (!stripeSignature || !webhookSecret) {
    return data({ error: "Webhook configuration error." }, { status: 400 });
  }

  try {
    event = stripe.webhooks.constructEvent(body, stripeSignature, webhookSecret);
  } catch (err) {
    return Response.json(null, { status: 400 });
  }

  const dataObject = event.data.object;

  switch (event.type) {
    case "invoice.paid": break;
    case "invoice.payment_failed": break;
    case "customer.subscription.deleted": break;
    default: break;
  }

  return Response.json({ "data": "webhook" }, { status: 200 });
};

Endpoint reference

PropertyValue
MethodPOST
Path/webhook
Required headerstripe-signature
Success response200 { "data": "webhook" }
Error response400

Signature verification

Stripe signs every webhook payload with a secret tied to your endpoint. The app verifies this signature using:
stripe.webhooks.constructEvent(body, stripeSignature, webhookSecret)
  • body — the raw request body as a string (not parsed JSON)
  • stripeSignature — the value of the stripe-signature request header
  • webhookSecret — the whsec_... secret from your Stripe Dashboard or CLI
Never parse the request body as JSON before calling constructEvent. Stripe’s signature is computed over the raw bytes, and any transformation will cause verification to fail.

Configuring the webhook

Stripe Dashboard

1

Open the Webhooks page

In your Stripe Dashboard, go to Developers → Webhooks and click Add endpoint.
2

Enter your endpoint URL

Set the endpoint URL to your public domain, for example:
https://your-app.com/webhook
3

Select events to listen for

Add the following events:
  • invoice.paid
  • invoice.payment_failed
  • customer.subscription.deleted
4

Copy the signing secret

After saving, reveal the Signing secret (whsec_...) and store it as the STRIPE_WEBHOOK_SECRET environment variable.

Stripe CLI (local development)

Use the Stripe CLI to forward events to your local server during development:
stripe listen --forward-to localhost:5173/webhook
The CLI prints a webhook signing secret at startup — use that value in your local environment instead of the Dashboard secret.
The signing secret generated by stripe listen is different from the one in the Dashboard. Use the CLI secret locally and the Dashboard secret in production.

Build docs developers (and LLMs) love