To test webhooks locally: install the Stripe CLI and run stripe listen --forward-to localhost:3000/webhook to forward real Stripe events to your local server. For Shopify, use ngrok to expose your local port, then register the ngrok URL as a temporary webhook endpoint in your Shopify store. For GitHub, use smee.io or ngrok and update the webhook URL in repository settings.
How to Test Webhooks Locally: Stripe, Shopify, and GitHub
Webhooks require a publicly reachable URL. Your local development server doesn't have one. That gap is the core difficulty — the platform needs somewhere to send events, and localhost:3000 isn't it.
The solutions differ by platform. Stripe has a purpose-built CLI. Shopify and GitHub need a tunnel. This guide covers all three.
Why Is Testing Webhooks Locally Harder Than Testing APIs?
When you call an API, you initiate the request. You control the timing, the parameters, and you can run the call as many times as you need from any environment. Webhooks invert this: the platform initiates the request when an event occurs, and it needs your server to be publicly reachable when it does.
Three specific problems make this difficult in local development:
- No public URL. Your local server is behind NAT and not reachable from the internet. The platform has nowhere to send the event.
- Timing dependency. You have to trigger the real event in the platform, then watch what your local handler does — as opposed to calling your own API endpoint directly.
- Signature verification. Stripe, Shopify, and GitHub all sign webhook payloads with HMAC-SHA256. The signing secret for your production endpoint won't match the one used for local testing unless you configure it explicitly.
How Do You Test Stripe Webhooks Locally?
The Stripe CLI is the best tool for this. It creates a secure connection to Stripe and forwards real webhook events to your local server — including proper signatures — without any public URL.
Step 1: Install the Stripe CLI
# macOS brew install stripe/stripe-cli/stripe # Windows (Scoop) scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git scoop install stripe # Authenticate stripe login
Step 2: Start forwarding events
stripe listen --forward-to localhost:3000/webhook
This command prints a webhook signing secret that starts with whsec_. Use this secret — not your production endpoint's secret — in your local handler to verify signatures. The CLI will print every event it forwards and the response your handler returns.
Step 3: Trigger test events
In a second terminal, trigger any Stripe event type:
stripe trigger invoice.payment_failed
# Other useful triggers
stripe trigger customer.subscription.deleted
stripe trigger checkout.session.completed
stripe trigger charge.refunded The CLI sends a real Stripe event object to your local handler. Your handler processes it exactly as it would in production. The signing secret is valid, so signature verification passes.
You can also forward only specific event types: stripe listen --events invoice.payment_failed,customer.subscription.deleted --forward-to localhost:3000/webhook
How Do You Test Shopify Webhooks Locally?
Shopify doesn't have a CLI equivalent of stripe listen. The standard approach is ngrok: expose your local port with a public HTTPS URL, then register that URL as a webhook endpoint in your Shopify store.
Step 1: Start ngrok
ngrok http 3000
ngrok prints a public URL like https://a1b2c3d4.ngrok.io. This URL is publicly reachable and forwards to your local port 3000.
Step 2: Register the webhook in Shopify
Go to your Shopify admin → Settings → Notifications → Webhooks → Create webhook. Set the event (e.g. orders/create), set the URL to https://a1b2c3d4.ngrok.io/webhook, and save.
Shopify uses the X-Shopify-Hmac-SHA256 header for signature verification. Find your webhook secret in the Shopify admin webhook settings and use it in your handler.
Step 3: Trigger a test delivery
From the webhook settings page, click "Send test notification." Shopify sends a test payload to your ngrok URL, which forwards it to your local server. Watch your terminal for the incoming request.
For real event testing, trigger the actual event in your Shopify store — create a test order, process a refund, or update a product — and watch your handler respond in real time.
Remove the temporary ngrok webhook endpoint from your Shopify store when you're done. Leaving stale endpoints causes unnecessary failed delivery attempts.
How Do You Test GitHub Webhooks Locally?
For GitHub webhooks, you have two options: ngrok (same as Shopify) or smee.io, a free webhook proxy maintained by GitHub.
Option A: smee.io
Go to smee.io and click "Start a new channel." You get a URL like https://smee.io/AbCd1234. Install the smee client and start it:
npm install --global smee-client smee --url https://smee.io/AbCd1234 --target http://localhost:3000/webhook
Register https://smee.io/AbCd1234 as the webhook URL in your GitHub repo (Settings → Webhooks → Add webhook). smee.io acts as a relay — GitHub sends events to the smee URL, and the smee client forwards them to your local server.
Option B: ngrok
ngrok http 3000
Register the ngrok URL as your webhook URL in GitHub repo or organization settings. GitHub signs deliveries with the X-Hub-Signature-256 header using the secret you set when creating the webhook.
To trigger test events, push a commit, open a PR, or use the "Redeliver" button in GitHub's recent deliveries log to resend an existing event.
What Should You Test in Your Webhook Handler?
Local testing should cover:
- Signature verification. Confirm your handler rejects requests with an invalid or missing signature header before doing any processing. Test with a tampered payload to verify rejection.
- Idempotency. Send the same event twice. Confirm your handler does not create duplicate records, send duplicate emails, or process the same charge twice.
- Event type routing. If your handler receives multiple event types, test that each type routes to the correct logic and that unknown event types return 200 without crashing.
- Error handling. Simulate a database failure or downstream API error. Confirm your handler returns a non-2xx status so the platform knows to retry (for platforms that do retry).
- Fast response. Stripe requires a 2xx response within 30 seconds; Shopify within 5 seconds. Your handler should acknowledge the event and process it asynchronously if the actual work takes longer.
What Does Local Testing Miss?
Local testing with the Stripe CLI or ngrok is valuable but it cannot cover everything:
- Network conditions. Your local server responds quickly. Production endpoints hit by real traffic can time out under load, triggering retries you never saw in development.
- Deployment gaps. The most common cause of webhook failures in production is a bad deploy — a handler that worked locally fails in the new environment due to a missing environment variable, a dependency mismatch, or a configuration error.
- Platform-side delivery failures. The platform may fail to deliver even when your endpoint is healthy — DNS issues, routing problems, or platform outages. These never appear in local testing.
- Silent failures in production. After deploy, production webhooks can fail for reasons that have nothing to do with your code. Without active monitoring, you find out from customers or from checking the delivery log manually.
Local testing covers handler correctness. Production monitoring covers delivery reliability.
How Do You Test Webhooks in Staging?
Staging environments have public URLs, which eliminates the tunneling requirement. The main considerations:
- Use separate webhook endpoints for staging — don't share a production endpoint with staging traffic.
- Use separate API keys and signing secrets. Never use production secrets in staging.
- For Stripe, use test mode API keys (
sk_test_) and test mode webhook endpoints. All Stripe test events use test mode credentials. - Register your staging URL as a webhook endpoint, run through your test scenarios with real platform events, and confirm the delivery log shows successes before deploying to production.
- Set up webhook monitoring for staging too — catching failures in staging before they reach production is exactly the point of having a staging environment.
FAQ: Testing Webhooks Locally
How do I test webhooks locally without a public URL?
What is the Stripe CLI webhook command?
Can I test Shopify webhooks locally?
Local testing verifies your handler. Production monitoring catches what testing misses. Connect Webhook Guardian to get alerted within 5 minutes of any failed delivery in production — with the payload, error, and a one-click replay link. Learn how it works →
Also see: Stripe webhook monitoring · Shopify webhook monitoring · GitHub webhook monitoring · Webhook best practices · How to know when a Stripe webhook fails