Webhook Fan-Out: How to Send One Webhook to Multiple Endpoints
Fan-out — delivering one inbound webhook event to multiple destinations — is one of the most common webhook infrastructure patterns. Here's how it works and how to do it reliably.
Jason Warner
March 18, 2026
Webhook Fan-Out: How to Send One Webhook to Multiple Endpoints
Fan-out is the pattern where a single inbound event triggers delivery to multiple destinations. A Stripe payment_intent.succeeded event needs to reach your billing service, your CRM, your analytics pipeline, and a Slack notification channel. One event in, four deliveries out.
This is one of the most common webhook infrastructure requirements. Here's how to implement it correctly — and what goes wrong when you don't.
Why Fan-Out Is Harder Than It Looks
The naive implementation puts fan-out in your application code:
app.post('/webhook', async (req, res) => {
res.sendStatus(200); // respond quickly
await Promise.all([
fetch('https://billing.internal/events', { method: 'POST', body: req.body }),
fetch('https://crm.internal/events', { method: 'POST', body: req.body }),
fetch('https://analytics.internal/events', { method: 'POST', body: req.body }),
sendSlackNotification(req.body),
]);
});
This works until one of those destinations is slow or down. Then your webhook handler blocks (or worse, returns 200 and silently drops events if the downstream fails). You have no visibility into which delivery succeeded and which failed. There's no retry logic. If your server goes down during the Promise.all, all four deliveries are lost.
The Right Architecture
Fan-out done correctly has these properties:
1. Respond to the source immediately. Your webhook receiver should return 200 as fast as possible — ideally before doing any delivery. Queue the event and process asynchronously.
2. Independent delivery per destination. The success or failure of delivery to destination A should have no effect on delivery to destination B. Each destination has its own delivery state, retry queue, and log.
3. Per-destination retry. If your CRM is down, retry delivery to it independently. Don't block delivery to your analytics pipeline waiting for the CRM to come back up.
4. Per-destination transformation. Each destination may expect a different payload shape. Your analytics platform wants a flat event format. Your CRM wants a nested contact object. Each destination gets the right shape.
5. Observability per delivery attempt. You need to know, for each destination, what was sent, what was received, how many retries were attempted, and the final outcome.
Implementing Fan-Out with Bluejay Relay
Bluejay Relay's multi-destination model is designed exactly for this pattern. Each Integration has one inbound URL and can have multiple Destinations. Each Destination is independently configured.
Step 1: Create an Integration
Navigate to Integrations > New Integration, give it a name, and copy the capture URL. This is the URL you'll give to Stripe (or whatever service is sending events).
Step 2: Add Destinations
Click Add Destination for each endpoint you want to fan out to. For each destination:
- URL: The endpoint URL
- Method: POST, PUT, PATCH, etc.
- Transformation: Visual field mapper for payload shaping
- Headers: Static or template-based headers
- Retry Policy: None, Linear, or Exponential backoff with configurable retry count
Each destination is completely independent. Delivery to destination 2 happens regardless of whether destination 1 succeeded.
Step 3: Monitor Deliveries
Every inbound webhook and every delivery attempt is logged. Navigate to Logs to see:
- The raw inbound payload
- For each destination: the transformed payload sent, the response code, response body, and retry history
If a delivery fails and exhausts retries, it moves to the Dead Letter Queue where you can reprocess it manually.
Payload Transformation Per Destination
Each destination can have its own transformation. For example, with a Stripe payment_intent.succeeded event:
Billing service — forward the full payload as-is:
{{ payload | json }}
CRM — extract customer fields:
{
"contact_id": "{{ payload.data.object.metadata.customer_id }}",
"event": "payment_succeeded",
"amount": {{ payload.data.object.amount_received }}
}
Slack — a human-readable message:
{
"text": "Payment received from {{ payload.data.object.metadata.customer_email }}"
}
One inbound event, three different payload shapes, each delivered independently with its own retry logic.
Common Fan-Out Patterns
Event mirroring across environments: Route production webhooks to staging as well as production for testing. Add a staging destination, toggle it off when not needed.
Analytics + operations split: Forward raw events to your analytics pipeline and a processed version to your operational systems. Different formats, independent delivery.
Gradual migration: During a service migration, send events to both old and new services simultaneously. Once the new service is verified, remove the old destination.
Notification channels: Deliver to your application plus a Slack channel plus a monitoring webhook. If the Slack notification fails, it doesn't affect your application delivery.
Bluejay Relay's multi-destination fan-out is available on all tiers (the free tier supports up to 2 destinations per integration; paid tiers allow more). Start free — no credit card required.
Build more reliable webhook workflows.
Capture, transform, and retry webhooks with full observability. Free to start, no credit card.