← Back to Blog
Guide webhooks testing development debugging tools

How to Test Webhooks: Tools, Techniques, and Best Practices

Testing webhooks is annoying because you can't just call a function — you need a running server with a public URL and a service willing to send you events. Here's every approach worth knowing.

JW

Jason Warner

March 22, 2026

How to Test Webhooks: Tools, Techniques, and Best Practices

Webhooks are uniquely annoying to test. Unlike a REST endpoint you can call with curl, a webhook handler needs to receive an inbound HTTP request — which means you need a running server, a public URL, and a service that will actually send events to it.

Here's a complete guide to every testing technique worth knowing.

Layer 1: Unit Testing Your Handler Logic

Extract your processing logic into a pure function and test it directly:

// handler.js — pure function, no HTTP
async function processStripePayment(event) {
  if (event.type !== 'payment_intent.succeeded') return { handled: false };
  const intent = event.data.object;
  await activateSubscription(intent.metadata.userId, intent.amount_received);
  return { handled: true, userId: intent.metadata.userId };
}

// handler.test.js
test('activates subscription on payment_intent.succeeded', async () => {
  const mockEvent = {
    type: 'payment_intent.succeeded',
    data: { object: { metadata: { userId: 'u_123' }, amount_received: 4900 } }
  };
  const result = await processStripePayment(mockEvent);
  expect(result.handled).toBe(true);
  expect(activateSubscription).toHaveBeenCalledWith('u_123', 4900);
});

Cover edge cases here: unknown event types, missing fields, idempotency logic.

Layer 2: Inspecting Real Payloads

Webhook.site — Instant URL, no account needed. Good for a quick look at payload shape.

Bluejay Relay Capture URL — Persistent, logged captures. More useful for ongoing development since history persists and you can replay captured events later.

curl — If you know the payload format, construct it manually:

curl -X POST http://localhost:3000/webhook \
  -H 'Content-Type: application/json' \
  -d '{ "type": "payment.succeeded", "data": { "amount": 4900 } }'

Layer 3: Testing with a Local Tunnel

ngrok: ngrok http 3000 — most common, dashboard lets you replay recent requests.

Stripe CLI: stripe listen --forward-to localhost:3000/webhook — Stripe-specific, handles signatures automatically.

Bluejay Relay + ngrok: Point the source at your Bluejay Relay integration URL. Run ngrok (ngrok http 3000) and add your ngrok public URL as a destination in Bluejay Relay. Bluejay Relay can't reach localhost directly — ngrok bridges that gap. Events are logged in Bluejay Relay even when your local server is down; replay them when you're back up.

Layer 4: Integration Tests

test('POST /webhook processes payment.succeeded', async () => {
  const payload = JSON.stringify({
    type: 'payment_intent.succeeded',
    data: { object: { id: 'pi_test', amount_received: 4900 } }
  });

  const sig = generateSignature(payload, process.env.WEBHOOK_SECRET);

  const res = await request(app)
    .post('/webhook')
    .set('Content-Type', 'application/json')
    .set('X-Stripe-Signature', sig)
    .send(payload);

  expect(res.status).toBe(200);
  await new Promise(r => setTimeout(r, 100));
  const sub = await db.subscriptions.findOne({ stripeId: 'pi_test' });
  expect(sub.status).toBe('active');
});

Key cases to cover: valid signature → 200, invalid signature → 401, unknown event type → 200 (don't error), duplicate event ID → idempotent.

Layer 5: Replay Testing

With Bluejay Relay, make a code change then replay historical events through it:

  1. Navigate to your Integration's logs
  2. Select events to replay
  3. Click Bulk Replay (or Time-Travel Replay to modify payload, or Dry Run to preview)

Common Pitfalls

  • Forgetting to test signature verification with an invalid signature
  • Testing with toy payloads instead of real captured ones
  • Not testing idempotency (send same event twice, verify result is the same)
  • Not testing the async processing failure path

Checklist

  • Handler logic unit tested with real payload shapes
  • Valid and invalid signature cases covered
  • Idempotency test for duplicate event IDs
  • Unknown event type handled gracefully (200, no error)
  • Local tunnel set up for live testing
  • Integration test covers full request → response → side effect chain
  • Replay workflow available for regression testing

Bluejay Relay makes webhook testing easier: capture real payloads, inspect every delivery end-to-end, and replay after code changes on the paid plans. Free to start.

#webhooks #testing #development #debugging #tools

Build more reliable webhook workflows.

Capture, transform, and retry webhooks with full observability. Free to start, no credit card.