To test webhooks locally: (1) install the Stripe CLI or use ngrok to expose your local server, (2) forward events to your local endpoint, (3) use the platform's test event trigger to send a real event. For production testing, use the platform's redeliver feature in the delivery log. After testing, set up monitoring so you know immediately when production deliveries fail.
How to Test Webhooks Locally (Stripe CLI, ngrok, smee.io)
Webhooks are hard to test because they require a publicly reachable HTTPS endpoint. Your localhost:3000 isn't accessible to Stripe or Shopify. This guide covers the fastest way to get real webhook events hitting your local server for each platform, and what to do after go-live.
Why Is Testing Webhooks Difficult?
Three things make webhook testing awkward compared to testing regular code:
- Inbound connections — platforms push events to you; your localhost isn't accessible from the internet without a tunnel
- Signature verification — each platform signs its payloads; if you test with a manually crafted POST, you need to either skip signature verification or generate a valid HMAC (error-prone)
- Event variety — you want to test against real event payloads from the platform, not hand-crafted JSON that may not match the actual schema
The tools below solve all three by creating an authenticated tunnel and forwarding real platform events to your local server.
Also review the webhook best practices guide before testing — knowing what to test (signature verification, idempotency, response time) makes your tests more meaningful.
How Do You Test Stripe Webhooks Locally?
The Stripe CLI is the best tool for this. It's official, handles signature forwarding automatically, and lets you trigger specific event types on demand.
Step 1: Install the Stripe CLI
# macOS brew install stripe/stripe-cli/stripe # Linux curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list sudo apt update && sudo apt install stripe
Step 2: Log in and start forwarding
stripe login stripe listen --forward-to localhost:3000/webhook
The CLI prints a webhook signing secret (whsec_xxx) — copy this into your local .env as STRIPE_WEBHOOK_SECRET. This is different from your production webhook secret. The CLI generates a session-specific secret that matches the signatures on forwarded events.
Step 3: Trigger a test event
# In a second terminal stripe trigger payment_intent.succeeded stripe trigger invoice.payment_failed stripe trigger customer.subscription.deleted
The CLI sends a real Stripe event to your local endpoint with a valid signature. Your handler runs exactly as it would in production. See the Stripe integration guide for the full list of events worth testing.
How Do You Test Shopify Webhooks Locally?
Shopify doesn't have an official CLI equivalent for webhook forwarding. Use ngrok to expose your local server.
Step 1: Install and start ngrok
brew install ngrok ngrok http 3000
ngrok prints a public HTTPS URL like https://a1b2c3d4.ngrok.io. This URL proxies to your localhost:3000.
Step 2: Register the ngrok URL as a Shopify webhook
Go to Shopify Admin → Settings → Notifications → Webhooks. Create a new webhook pointing at https://a1b2c3d4.ngrok.io/webhook. Copy the signing secret from the same screen into your local .env.
Step 3: Send a test notification
Shopify has a "Send test notification" button next to each registered webhook. Click it — Shopify sends a real (test-mode) payload to your ngrok URL, which forwards it to your local server. The X-Shopify-Hmac-Sha256 header is populated with a valid signature.
ngrok free tier limitation: the public URL changes every time you restart ngrok. You'll need to update the Shopify webhook URL each session. ngrok paid plans give you a stable subdomain.
See the Shopify integration guide for the events most worth testing.
How Do You Test GitHub Webhooks Locally?
Use ngrok (same as Shopify) or smee.io — a free event forwarding service from GitHub's own tooling team.
Option A: smee.io (no install required)
npx smee-client --url https://smee.io/your-channel-id --target http://localhost:3000/webhook
Go to smee.io, click "Start a new channel", copy the URL. Use that URL as your GitHub webhook endpoint. Run the smee client locally — it forwards events from smee.io to your local server.
Option B: ngrok
ngrok http 3000
Paste the ngrok URL directly into GitHub repository or organization webhook settings (Settings → Webhooks → Add webhook). GitHub sends events directly to the ngrok URL, which proxies to your local server.
GitHub provides a "Recent Deliveries" panel in webhook settings — you can see each delivery attempt and use "Redeliver" to resend any event. This is useful for testing your handler against real payloads after initial setup.
See the GitHub integration guide for delivery log details.
What Should You Test in Your Webhook Handler?
Beyond "does the event arrive and get processed", test these specific scenarios:
- Bad signature — modify the payload before processing and verify your handler returns 400, not 200. If it returns 200 on a bad signature, you're not actually verifying.
- Duplicate event — send the same event twice (use the CLI's trigger or the "Redeliver" button). Verify your handler processes it exactly once (check your idempotency table or your database for duplicate rows).
- Unknown event type — send an event type your handler doesn't subscribe to. Verify your handler returns 200 (not 400). Returning 400 for unknown types causes the platform to treat unrecognized events as failures.
- Response time — if you're using the async queue pattern, verify your handler returns 200 in under 1 second. If not, measure the p95 response time against Shopify's 5-second limit.
- Missing fields — Stripe and Shopify occasionally change event schemas. Test that your handler doesn't throw on optional fields being absent.
What Does Testing Miss? Why You Still Need Production Monitoring?
Local testing validates your implementation under controlled conditions. It does not catch:
- A bad deploy — a regression that causes your handler to return 500 goes unnoticed until someone reports a broken feature or Stripe emails you after 72 hours of retries
- Database outages — if your handler depends on a database query and the database goes down, the handler fails; testing never simulates this
- Shopify subscription deletion — if your endpoint is consistently failing, Shopify silently deletes the webhook subscription after 19 failures and 48 hours; you won't know unless you're monitoring the delivery log
- GitHub silent failures — GitHub doesn't retry or notify on failure; a single bad deploy can cause you to miss CI triggers, PR events, or deployment hooks indefinitely
- Traffic spikes — your handler may work fine at 1 event/sec but time out at 100/sec under a flash sale
Webhook Guardian monitors your production delivery logs every 5 minutes via read-only OAuth and sends a Slack or email alert within 5 minutes of any failure — covering the gap between "works in testing" and "works in production".
FAQ: Webhook Testing
How do you test webhooks locally?
stripe listen --forward-to localhost:3000/webhook) for Stripe — it handles signature forwarding automatically. For Shopify or GitHub, use ngrok (ngrok http 3000) to get a public HTTPS URL, paste it into the platform's webhook settings, then use the platform's test notification button. Both approaches deliver real platform payloads with valid signatures to your local server.What is the Stripe CLI webhook command?
stripe listen --forward-to localhost:3000/webhook — this forwards all Stripe webhook events to your local endpoint. The CLI prints a signing secret (whsec_xxx) — use this as your STRIPE_WEBHOOK_SECRET env var for local testing. In a second terminal, run stripe trigger payment_intent.succeeded to fire a specific event type.Does testing replace webhook monitoring?
Testing done. Now monitor production. Start a free 14-day trial of Webhook Guardian — connect Stripe, Shopify, or GitHub via read-only OAuth and get alerted within 5 minutes of any production failure. See also: how webhook monitoring works and webhook glossary.