Requex.me LogoRequex.me

Documentation

Browse by section

Keep all guides, tool docs, automation recipes, and comparison pages in one navigable place.

Docs Home
Docs

Foundation docs for getting started fast, understanding key terms, and tracking what has changed.

Guides

Start with fundamentals, then move into provider-specific webhook testing and production hardening.

Tool Docs

These pages explain what each tool does, when to use it, and how it fits into a webhook debugging workflow.

Automation Docs

Use these setup guides when you want forwarding rules, custom responses, security checks, or multi-destination fanout.

Compare

Use these pages to compare developer workflows, pricing tradeoffs, and feature differences between webhook tools.

Webhook Testing in Node.js

Set up a webhook receiver, verify HMAC signatures, and debug payloads — complete Node.js walkthrough from first request to production-ready handler.

Editorially reviewed by the Requex team12 min readAbout the product

TL;DR: Use Express (or the built-in http module) to receive webhook POST requests. Always use raw body middleware for HMAC signature verification — never parse JSON before you verify the signature.

Minimal Webhook Receiver in Node.js

The fastest way to start receiving webhooks in Node.js is with Express. Install it with npm install express, then create a route that accepts POST requests, reads the headers, and returns 200.

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  console.log('Event type:', req.headers['x-event-type']);
  console.log('Payload:', req.body);
  res.status(200).json({ received: true });
});

app.listen(3000, () => console.log('Listening on port 3000'));

This is enough to accept any JSON webhook. The provider expects a 2xx response within its timeout window (typically 5–30 seconds). Return it immediately — do not wait for processing to complete before responding.

Using Requex During Development

Before you write a single line of handler code, point your webhook provider at a Requex.me endpoint. Requex gives you a public URL instantly — no tunneling, no ngrok, no server required. Trigger the real event in your provider (a test payment, a push event, a new order) and inspect the exact payload structure in Requex.

Once you know the exact shape of the payload and which headers carry the signature, switch the provider URL to your local server and replay the same request using curl:

curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Event-Type: payment.succeeded" \
  -d '{"id":"evt_123","amount":4900,"currency":"usd"}'

Raw Body for HMAC Signature Verification

Most webhook providers (Stripe, GitHub, Shopify) sign their payloads with an HMAC-SHA256 signature. The signature is computed over the raw request body bytes. If you call express.json() before verifying, Express has already parsed and re-serialized the body — the bytes no longer match and your signature check will always fail.

The fix is to mount express.raw() on the specific webhook route instead of the global JSON middleware:

const crypto = require('crypto');

app.post('/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['x-signature-256'];
    const secret = process.env.WEBHOOK_SECRET;
    const expected = crypto
      .createHmac('sha256', secret)
      .update(req.body)
      .digest('hex');

    if (sig !== `sha256=${expected}`) {
      return res.status(401).send('Invalid signature');
    }

    const event = JSON.parse(req.body);
    // Handle event...
    res.json({ received: true });
  }
);

With express.raw(), req.body is a Buffer containing the exact bytes sent by the provider. Always use crypto.timingSafeEqual in production to prevent timing-attack signature forgery.

Async Processing — Respond Fast

Webhook providers measure your response time strictly. If your handler writes to a database, calls a third-party API, or sends an email, those operations can easily exceed the provider's timeout. The standard pattern is to return 200 immediately, then fire the actual work after the response is sent:

app.post('/webhook', express.json(), (req, res) => {
  res.json({ received: true }); // Respond immediately
  setImmediate(() => processWebhook(req.body)); // Process async
});

async function processWebhook(payload) {
  // Safe to do slow work here — response is already sent
  await db.insert(payload);
  await notifySlack(payload);
}

For higher reliability in production, push the payload to a queue (Bull, BullMQ, SQS) instead of using setImmediate. Queues give you retries and dead-letter handling if the background job fails.

Testing Without a Running Server

The most common development workflow is:

  1. Point the provider at a Requex endpoint to capture the real payload.
  2. Copy the full request (headers + body) from the Requex inspector.
  3. Replay it against your local handler with curl as many times as needed.
  4. Iterate on your handler logic without triggering a real event each time.

This loop is significantly faster than trying to trigger real events and waiting for the provider to deliver each time. It also lets you test edge-case payloads that are hard to trigger naturally (failed payment, account suspended, etc.).

Capture Your First Node.js Webhook

Get a public endpoint in seconds. Inspect headers, body, and signatures — no server required.

Open Requex →

Related guides

Start Testing Webhooks Now

Generate your unique URL and test webhooks instantly. Free, no signup.

Open Webhook Tester →