DEV Community

Peakline Studio
Peakline Studio

Posted on

How to Test Stripe Webhooks Without Deploying Code

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:

  1. Deploy a temporary endpoint with logging
  2. Configure it in Stripe Dashboard
  3. Trigger a test event
  4. Dig through logs
  5. 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
Enter fullscreen mode Exit fullscreen mode

That's it. Your endpoint is live.


Step 2: Add it to Stripe

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click Add endpoint
  3. Paste your HookTest URL
  4. Select the events you care about — or just pick All events for exploration
  5. 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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. Open HookTest → get an endpoint URL (5 seconds, no account)
  2. Paste it into your provider's webhook config
  3. Trigger a test event → see the full payload immediately
  4. Write your handler with real field names and types
  5. Replay from HookTest → iterate without re-triggering the source

No deploy cycle. No log parsing. No guessing.

Try HookTest free →


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)