BlogStripe Webhooks

How to Test Stripe Webhooks

Stripe uses webhooks to notify your application when events like payments, subscriptions, and disputes occur.

Looking for the broader picture? See the 7 best webhook testing tools (2026), or if you're already on Webhook.site, the 60-second migration to HookRay.

Stripe Official Webhook Docs

1. Stripe Webhook Events

Stripe can send the following webhook events to your endpoint:

checkout.session.completed
payment_intent.succeeded
payment_intent.payment_failed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
invoice.paid
invoice.payment_failed
charge.refunded
charge.dispute.created

2. Set Up a Test Endpoint with HookRay

Follow these steps to start receiving Stripe webhooks for testing:

  1. Go to HookRay and click "Start Testing — Free" to get your unique webhook URL.
  2. Copy the URL (e.g., https://h.hookray.com/abc123).
  3. In your Stripe dashboard, navigate to the webhook settings and paste the HookRay URL as your endpoint.
  4. Select the events you want to receive (see list above).
  5. Trigger a test event — HookRay will show the incoming webhook in real-time.

3. Sample Stripe Webhook Payload

Here's an example of what a Stripe webhook payload looks like:

payload.json
{
  "id": "evt_1234567890",
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_test_abc123",
      "amount_total": 4999,
      "currency": "usd",
      "customer_email": "customer@example.com",
      "payment_status": "paid"
    }
  }
}

4. How to Verify Stripe Webhook Signatures

Signing details
Algorithm
HMAC-SHA256
Header
Stripe-Signature
Encoding
hex

Stripe sends a composite header: `t={unix_timestamp},v1={signature}`. The signed payload is `{timestamp}.{raw_request_body}`.

Node.js (Express)

// Express + Stripe SDK — let the SDK do the timing-safe comparison.
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

app.post(
  '/webhooks/stripe',
  // CRITICAL: the verifier needs the RAW body, not parsed JSON.
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['stripe-signature'] as string;
    try {
      const event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        process.env.STRIPE_WEBHOOK_SECRET!,
      );
      // event.type, event.data.object — safe to act on
      res.json({ received: true });
    } catch (err) {
      res.status(400).send(`Webhook Error: ${(err as Error).message}`);
    }
  },
);

Python (FastAPI)

# FastAPI + stripe-python.
import os, stripe
from fastapi import FastAPI, Request, HTTPException

stripe.api_key = os.environ['STRIPE_SECRET_KEY']
app = FastAPI()

@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
    payload = await request.body()  # raw bytes
    sig_header = request.headers.get('stripe-signature')
    try:
        event = stripe.Webhook.construct_event(
            payload=payload,
            sig_header=sig_header,
            secret=os.environ['STRIPE_WEBHOOK_SECRET'],
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        raise HTTPException(status_code=400, detail='invalid signature')
    return {'received': True, 'type': event['type']}
Watch out: The Stripe SDK enforces a 5-minute tolerance on the timestamp by default to prevent replay attacks. If you implement signature checking manually, replicate that check.

Capture a real Stripe webhook with HookRay first, then replay the captured request against your verifier locally — that way you can iterate on the verification code without re-triggering events in Stripe. Read Stripe's official signing docs for the canonical reference, or see the cross-service signature verification guide for Ruby and timing-safe comparison patterns.

5. How Stripe Retries Failed Webhooks

Retry policy
Max attempts
17
Total window
Up to 3 days
Backoff
Exponential, starting ~immediately
Retries on
5xx, timeout, connection error
Stops on
Any 2xx response. 4xx also stops retries (treated as terminal).
Watch out: Stripe stops retrying on 4xx — including 400 — so don't return 4xx for known duplicates. Return 200 with `{duplicate: true}` instead. Stripe also auto-disables endpoints that fail 100% over a long enough window; check your dashboard if events stop arriving entirely.

If your handler is going to see the same Stripe event multiple times, you need idempotency by event ID and the right response codes — see the Webhook Retry Strategies guide for the full pattern (idempotency tables, dead-letter queues, replaying captured events for tests). Cross-reference with Stripe's official retry docs before relying on these numbers in production.

6. Frequently Asked Questions

How do I test Stripe webhooks without deploying?

Use HookRay to get an instant public webhook URL. Paste it into your Stripe dashboard's webhook configuration, trigger an event, and watch the payload arrive in real time. No code, no ngrok, no deployment required. The free tier captures 100 requests per month and works on all Stripe event types.

Why aren't my Stripe webhooks arriving?

The four most common causes: (1) the endpoint URL isn't publicly accessible — Stripe can't reach localhost; (2) the wrong events are subscribed in your Stripe dashboard; (3) signature verification is rejecting the request before your handler runs; (4) Stripe can't reach your server because of a firewall, expired SSL certificate, or wrong DNS. Use HookRay's URL to isolate which of these four is failing — if HookRay receives the webhook, the problem is in your handler. If HookRay doesn't, the problem is in Stripe configuration.

Why am I getting 400 or 500 errors from my Stripe webhook?

Stripe reports the response status your endpoint returned. HookRay accepts any payload and returns 200 OK by default, so if you see 400/500 in your Stripe dashboard while pointing at HookRay, the issue is in Stripe's configuration (wrong event, malformed signing secret, etc.). If you point at your own endpoint and get 400/500, the issue is in your handler — capture the request with HookRay, replay it locally, and debug from the captured payload.

How do I verify Stripe webhook signatures?

Stripe signs each webhook request with a shared secret. Capture the raw headers and body using HookRay, then verify the signature in your application using Stripe's SDK or a standard HMAC library. Once verification works against HookRay-captured data, you can safely deploy. Stripe's docs (linked above) cover the exact signing algorithm.

Can I replay a captured Stripe webhook?

Yes — HookRay's replay feature re-sends any captured webhook to a different endpoint with one click. This is the fastest way to fix a buggy handler: capture the payload once, fix your code, and replay until it works. No need to re-trigger the event in Stripe.

7. Next Steps

  • Use HookRay's webhook replay feature to re-send captured webhooks while building your handler
  • Enable smart parsing (Pro plan) to see Stripe-specific fields highlighted automatically
  • Check the Stripe webhook documentation for the complete event reference

Ready to test Stripe webhooks?

Get a free webhook URL in 5 seconds. No signup required.

Start Testing Stripe Webhooks — Free

Free PDF: Webhook Testing Cheat Sheet 2026

One-page reference for 50+ APIs — canonical events, signing methods, sample payloads. Print it, pin it, share it.

📄 Download the cheat sheet (PDF, 180KB)