Webhook Guide
Stripe Webhook Testing
Payments and checkout event webhooks
Quick path: CLI helper commands
Use these first to avoid setup mistakes, then follow the full provider steps below.
instatunnel webhook init --provider stripe --port 4242 --path /webhooks/stripe
instatunnel webhook verify --provider stripe --secret-env STRIPE_WEBHOOK_SECRET
instatunnel webhook test --provider stripe
If you run into provider-specific issues, use the full checklist sections below.
1. Local app setup
Create a local webhook endpoint at: /webhooks/stripe
import express from 'express'
import Stripe from 'stripe'
const app = express()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.header('stripe-signature') || ''
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
)
console.log('stripe event:', event.type)
res.status(200).send('ok')
} catch (err) {
console.error('invalid signature', err)
res.status(400).send('invalid signature')
}
})
app.listen(3000, () => console.log('listening on :3000'))2. Run InstaTunnel command
instatunnel 3000 --subdomain stripe-dev
Keep a fixed subdomain so your provider dashboard URL does not keep changing.
3. Provider config fields to paste
| Field | Value | Where/notes |
|---|---|---|
| Endpoint URL | {{WEBHOOK_URL}} | Stripe Dashboard > Developers > Webhooks |
| Events to send | checkout.session.completed, payment_intent.succeeded | Limit to only what your app handles |
| Signing secret | whsec_... | Copy from endpoint details and set STRIPE_WEBHOOK_SECRET |
| API version | Latest stable | Use one version consistently in staging and prod |
Use the helper below to generate exact values with your chosen subdomain and path.
One-Click Webhook Setup Helper
Generate copy-ready tunnel command, webhook URL, and provider config values.
Run InstaTunnel
instatunnel 3000 --subdomain stripe-devWebhook URL
https://stripe-dev.instatunnel.my/webhooks/stripe| Provider field | Value to paste | Notes |
|---|---|---|
| Endpoint URL | https://stripe-dev.instatunnel.my/webhooks/stripe | Stripe Dashboard > Developers > Webhooks |
| Events to send | checkout.session.completed, payment_intent.succeeded | Limit to only what your app handles |
| Signing secret | whsec_... | Copy from endpoint details and set STRIPE_WEBHOOK_SECRET |
| API version | Latest stable | Use one version consistently in staging and prod |
Tip: keep one stable subdomain per provider to avoid reconfiguring dashboards.
4. Send test event
- Open Stripe Dashboard > Developers > Webhooks > your endpoint.
- Use "Send test webhook" for a handled event type.
- Confirm your local logs show the event and a 2xx response.
# Optional Stripe CLI flow stripe listen --forward-to localhost:3000/webhooks/stripe stripe trigger checkout.session.completed
5. Verify signature
Verify this header on every request: stripe-signature
// Stripe verifies with raw request body + endpoint secret
const event = stripe.webhooks.constructEvent(
req.body,
req.header('stripe-signature')!,
process.env.STRIPE_WEBHOOK_SECRET!
)6. Retries and idempotency
- Store Stripe event IDs and ignore duplicates.
- Return 2xx quickly, then process asynchronously.
- If you return non-2xx, Stripe retries with backoff.
7. Common failures and quick fixes
400 invalid signature
Use raw body middleware and confirm STRIPE_WEBHOOK_SECRET value.
Timed out endpoint
Acknowledge quickly with 200, move heavy work to a queue.
No events received
Check endpoint URL and selected events in Stripe dashboard.