Tips > Building Workflows

Build a Webhook Relay for Multi-Destination Routing

A webhook relay receives an incoming webhook, transforms the payload, and fans it out to multiple destinations.

A webhook relay is a workflow that receives one incoming webhook, transforms the payload, and fans it out to multiple destinations. It decouples the source system from its downstream consumers: the source sends a single webhook, and the relay handles distribution, per-destination transformation, and formatting so each system receives exactly the data shape it needs.

Real-world example: Stripe sends payment events to a single webhook endpoint. Your relay routes the event to your application backend, your analytics pipeline, and your accounting system, each receiving the data in its own format.

How does a webhook relay route events?

Webhook Trigger (/webhook/stripe-relay)
  |
  Code Node: Validate Stripe Signature
  |
  Switch Node: Route by Event Type
  |
  +-> "charge.succeeded":
  |     +-> HTTP Request: App Backend (full payload)
  |     +-> HTTP Request: Analytics (minimal payload)
  |     +-> HTTP Request: Accounting System (financial fields only)
  |
  +-> "customer.subscription.updated":
  |     +-> HTTP Request: App Backend
  |     +-> HTTP Request: CRM Update
  |
  +-> Default:
        +-> Log to Google Sheets (unhandled event types)

How do you verify the incoming webhook signature?

Code Node: Validate Stripe Signature

const crypto = require('crypto');

// Use rawBody to get the exact bytes sent by Stripe.
// Never use JSON.stringify() -- it may reorder keys or alter whitespace.
const payload = $json.rawBody;
const signature = $json.headers['stripe-signature'];
const webhookSecret = $env.STRIPE_WEBHOOK_SECRET;

// Parse the Stripe signature header
const elements = signature.split(',');
const timestamp = elements
  .find(e => e.startsWith('t='))?.split('=')[1];
const expectedSig = elements
  .find(e => e.startsWith('v1='))?.split('=')[1];

// Compute the expected signature
const signedPayload = `${timestamp}.${payload}`;
const computedSig = crypto
  .createHmac('sha256', webhookSecret)
  .update(signedPayload)
  .digest('hex');

// Use constant-time comparison to prevent timing attacks
const computedBuffer = Buffer.from(computedSig, 'utf8');
const expectedBuffer = Buffer.from(expectedSig || '', 'utf8');

if (computedBuffer.length !== expectedBuffer.length ||
    !crypto.timingSafeEqual(computedBuffer, expectedBuffer)) {
  throw new Error('Invalid Stripe signature');
}

// Signature valid -- pass through the event
return [{ json: $json.body }];

Warning: Enable Raw Body on the Webhook node

This code requires the Webhook node's Raw Body option to be enabled. Without it, rawBody will be undefined and signature verification will fail silently.

How do you format the payload for each destination?

HTTP Request: Analytics (minimal payload)

{
  "method": "POST",
  "url": "https://analytics.example.com/api/events",
  "sendBody": true,
  "bodyContentType": "json",
  "jsonBody": {
    "event": "={{ $json.type }}",
    "amount": "={{ $json.data.object.amount }}",
    "currency": "={{ $json.data.object.currency }}",
    "timestamp": "={{ $json.created }}"
  }
}

Tip: Respond Immediately

Configure the Webhook node with Response Mode: Immediately. Stripe (and most webhook senders) require a 200 response within a few seconds. Your relay should acknowledge receipt immediately, then process asynchronously. If per-destination delivery fails, that is handled by retry logic on each downstream branch, not by making Stripe wait.

This pattern centralizes webhook management, eliminates duplicate webhook configurations in the source system, and lets each destination receive exactly the data format it needs.

Related: Use Path Parameters in Webhook URLs for Dynamic Routing · Use Edit Fields in "Map Each" Mode for Simple Renames

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

190 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book an introductory call