You're integrating Stripe. You need to handle payment_intent.succeeded. Before you write a single line of handler code, you need to know exactly what Stripe sends.
The classic approach:
- Deploy a temporary endpoint with logging
- Configure it in Stripe Dashboard
- Trigger a test event
- Dig through logs
- Tweak your code, repeat
That's 15 minutes of plumbing before you write any real logic. Every time you need to check a different event type, you do it again.
There's a better way.
The fast approach: use a webhook inspector
A webhook inspector gives you an instant HTTPS URL. Point your webhook provider at it, trigger an event, and see the full payload — headers, body, timing — immediately in your browser. No deploy, no logging setup, no waiting.
For this walkthrough I'll use HookTest — a free tool I built for exactly this workflow. No account required.
Step 1: Get your endpoint URL
Open HookTest and click New Endpoint. You'll get a URL like:
https://hooktest.peakline-ops.workers.dev/w/abc123xyz
That's it. Your endpoint is live.
Step 2: Add it to Stripe
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Paste your HookTest URL
- Select the events you care about — or just pick All events for exploration
- Click Add endpoint
Now trigger a test event. In Stripe, find the webhook you just created and click Send test webhook. Pick payment_intent.succeeded.
Step 3: See what Stripe actually sends
Switch back to HookTest. The request appears immediately:
POST /w/abc123xyz
stripe-signature: t=1711641600,v1=3c5d8f...
content-type: application/json
{
"id": "evt_3OqABC2eZvKYlo2C1711641600",
"object": "event",
"api_version": "2024-04-10",
"created": 1711641600,
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_3OqABC2eZvKYlo2C1711641600",
"object": "payment_intent",
"amount": 2999,
"amount_received": 2999,
"currency": "usd",
"status": "succeeded",
"customer": "cus_ABC123",
"metadata": {},
"payment_method": "pm_1OqABC2eZvKYlo2C..."
}
},
"livemode": false
}
Now you know exactly what fields exist and what types they are. Write your handler with confidence.
Step 4: Replay to your local dev server
Once your handler is written and running on localhost:3000, you don't need to re-trigger the event in Stripe. Click Replay on the captured request, enter your local URL:
http://localhost:3000/webhooks/stripe
HookTest re-sends the exact payload with all original headers — including the Stripe-Signature header. Your local handler sees a real request.
This is the part that saves the most time. Iterate on your handler by replaying the same payload over and over, without touching Stripe each time.
What about ngrok?
ngrok tunnels traffic to your local server — useful when you want Stripe to hit localhost directly. But that means your server needs to be running and accessible before you can see anything.
Webhook inspection flips this: capture first, understand the payload, then write the handler. Use both tools for different stages:
| Stage | Tool |
|---|---|
| Exploring payload structure | HookTest (inspect) |
| Iterating on your handler | HookTest (replay) |
| End-to-end integration test | ngrok / stripe listen
|
Stripe-specific tips
Verify signatures in your handler. Every Stripe webhook includes a Stripe-Signature header. Your handler should validate it:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
// fulfill order
break;
}
res.json({ received: true });
});
Note: When replaying from HookTest, the signature won't verify unless you disable signature checking in dev mode — that's fine for development. In production, always verify.
Watch for event versions. Stripe sends api_version in every event. If your Stripe account is on a different API version than what you're testing, the payload shape can differ. Check this in your Stripe Dashboard under Developers → API keys.
Other webhook providers
This exact workflow works with any webhook-based API:
| Provider | Where to configure | Test event trigger |
|---|---|---|
| GitHub | Settings → Webhooks | "Redeliver" button |
| Shopify | Partners → App → Webhooks | Test notification |
| Twilio | Console → Phone Numbers | Use ngrok + real SMS |
| Slack | App config → Event Subscriptions | Slash command or action |
| SendGrid | Settings → Mail Settings → Event Webhook | Send a test email |
Summary
The next time you need to integrate a webhook:
- Open HookTest → get an endpoint URL (5 seconds, no account)
- Paste it into your provider's webhook config
- Trigger a test event → see the full payload immediately
- Write your handler with real field names and types
- Replay from HookTest → iterate without re-triggering the source
No deploy cycle. No log parsing. No guessing.
Built HookTest because I was tired of re-deploying logging endpoints every time I integrated a new webhook provider. The free tier is genuinely useful — no account, no credit card, 100 requests before anything expires.
Top comments (0)