Webhook Fan-Out and Multicasting

When a single webhook event needs to reach more than one place, you have a fan-out problem. A Stripe payment_intent.succeeded event needs to update your order service, trigger a confirmation email, and log to your analytics pipeline. A Shopify orders/create webhook needs to hit your fulfillment system, your CRM, and your inventory tracker. The event is one. The destinations are many.

Fan-out—taking a single inbound event and distributing it to multiple consumers—is one of the most common patterns in webhook-based architectures, and one of the most error-prone to implement yourself. This guide covers what webhook fan-out is, why it matters, common approaches to implementing it, the challenges you'll hit along the way, and how a webhook gateway simplifies the problem.

What is webhook fan-out?

Fan-out is the pattern of duplicating a single inbound event and delivering copies to multiple downstream destinations, where each destination processes the event independently.

In the context of webhooks, it works like this: an external service sends a single webhook to your system. Your system needs multiple internal services to act on that event, but each service has its own endpoint, its own processing logic, and its own failure characteristics. Fan-out is the mechanism that gets one incoming HTTP request to all the places it needs to go.

The closely related term "multicasting" describes the same idea from a networking perspective—sending a single message to a group of receivers simultaneously rather than point-to-point. In webhook infrastructure, "fan-out" and "multicasting" are effectively interchangeable.

Fan-out vs. routing

Fan-out and routing are often discussed together, but they solve different problems.

Routing is about directing an event to the correct destination based on its content. A customers/create event goes to the CRM service. An orders/create event goes to the fulfillment service. One event, one destination, chosen by a rule.

Fan-out is about delivering one event to multiple destinations simultaneously. An orders/create event goes to the fulfillment service and the notification service and the analytics pipeline. One event, many destinations, all receiving it.

In practice, most systems need both. A Shopify source might emit dozens of event types. Some events need to be routed to a single service. Others need to be fanned out to several. And some need conditional fan-out—where the event goes to a specific set of destinations based on payload content. The combination of routing and fan-out is what gives you flexible, maintainable event distribution.

Why fan-out matters

Fan-out becomes important as soon as your architecture moves beyond a single monolith consuming all webhook events. Here's when you'll encounter it.

Microservices architectures

In a monolithic application, one endpoint receives the webhook, and the application's internal logic decides which module handles it. There's no fan-out problem because everything lives in the same process.

Once you decompose into microservices, each service owns a bounded context and has its own endpoint. A single webhook event may be relevant to multiple services, but the webhook provider only sends it once to one URL. Something needs to copy and distribute that event.

Multi-system integrations

Many teams need a webhook event to reach not just their own services, but third-party systems too. A Shopify order webhook might need to reach your own order service, ShipStation for fulfillment, Twilio for SMS notifications, and HubSpot for CRM updates. Without fan-out, you'd need the webhook provider to support multiple URLs per subscription (most don't) or you'd build custom forwarding logic in your receiving endpoint.

Data pipelines and auditing

Even when a single service handles the business logic, you may need copies of events flowing to analytics systems, data warehouses, or audit logs. Fan-out lets you add these secondary consumers without modifying your primary processing pipeline.

Redundancy and resilience

Some teams fan out to a backup destination—an S3 bucket or a dead-letter store—alongside their primary processing service. If the primary destination goes down, the raw event is preserved in the backup, available for replay when the service recovers.

How to implement fan-out yourself

If you're building fan-out from scratch, there are several approaches, each with trade-offs.

Approach 1: Application-level forwarding

The simplest approach: your webhook endpoint receives the event, processes it, and then forwards copies to other internal services via HTTP.

# Pseudocode: application-level fan-out
def handle_webhook(request):
    event = verify_and_parse(request)

    # Forward to each destination
    for destination in get_destinations(event):
        try:
            http_post(destination.url, event.payload)
        except Exception as e:
            log_failure(destination, event, e)

This works for small-scale setups with a handful of destinations. The problems surface quickly. Your webhook endpoint's response time is now the sum of all downstream calls. If any destination is slow, the entire chain slows down—and many webhook providers have strict timeout windows (GitHub expects a response within 10 seconds, for example). If a destination is unavailable, you need retry logic for each forwarding call. If your endpoint itself goes down mid-fan-out, some destinations received the event and some didn't, leaving your system in an inconsistent state.

Approach 2: Queue-based fan-out

A more robust approach uses a message queue or pub/sub system as the fan-out layer.

The pattern: your webhook endpoint ingests the event, publishes it to a topic or exchange, and returns a 200 immediately. Downstream consumers subscribe to the topic and process events independently.

On AWS, this is commonly implemented with SNS (the fan-out broker) delivering to multiple SQS queues (one per consumer), with Lambda functions or workers polling each queue. On GCP, the equivalent is Pub/Sub with multiple subscriptions. Self-hosted options include RabbitMQ exchanges or Kafka topics with consumer groups.

This solves the timeout problem—your webhook endpoint responds immediately after publishing. It also decouples consumers—each processes at its own pace, and a slow consumer doesn't block others.

But it introduces significant operational complexity. You're now managing a message broker, multiple queues or subscriptions, consumer workers, dead-letter queues for each consumer, and monitoring across all of them. You need to configure each queue's retry behavior, visibility timeout, and throughput limits. If you're using FIFO queues for ordering guarantees, you're constrained to 300 messages per second per queue on AWS. And the message broker itself becomes a critical dependency—if SNS drops a delivery to an SQS queue (which can happen if the queue is unavailable), the event is lost for that consumer.

You also need to build the subscription management layer: which consumers should receive which events? Updating routing rules means changing infrastructure configuration (SNS subscriptions, SQS queue policies) rather than application code.

Approach 3: Custom fan-out service

Some teams build a dedicated internal service that sits between the webhook endpoint and downstream consumers. This service receives events, applies routing and fan-out rules from a configuration store, and manages delivery to each destination with its own retry logic, dead-letter handling, and observability.

This gives you the most control, but it's effectively building your own webhook gateway. Teams that go down this path end up investing significant engineering time in infrastructure that isn't their core product—and they still face the challenges of durability, idempotency, monitoring, and scaling that purpose-built gateways have already solved.

Challenges of DIY fan-out

Regardless of which approach you choose, fan-out introduces several hard problems.

Partial delivery failures

When an event needs to reach five destinations and delivery to the third one fails, you have an inconsistent state. Three destinations processed the event. Two didn't. Your system needs to track delivery status per destination, retry the failures independently, and avoid reprocessing at the destinations that already succeeded.

This is harder than single-destination retry logic because each destination has its own failure characteristics. One might be temporarily down. Another might be rate-limiting you. A third might be returning 500 errors due to an unrelated bug. You need independent retry queues and backoff strategies per destination.

Deduplication across destinations

If the webhook provider retries a delivery (because your endpoint was slow to respond), you'll receive the same event again. Your fan-out layer now distributes this duplicate to all destinations. Each destination needs to handle deduplication) independently, or your fan-out layer needs to deduplicate before fanning out. Both approaches require tracking event IDs and maintaining state.

Ordering and consistency

If order matters—and for many webhook types it does (a payment_intent.succeeded should be processed before a charge.refunded for the same transaction)—fan-out complicates things further. Each destination's queue may process events at different rates, so a destination that falls behind might process events out of order relative to one that's caught up. Guaranteeing ordering across fanned-out destinations is architecturally expensive and rarely achievable without trade-offs on throughput.

Observability across destinations

When an event is fanned out to five destinations, you need to trace its lifecycle across all five independently. Did destination 3 receive it? When? Did it process successfully? If it failed, how many retries have been attempted? A single event becomes five delivery traces, each with its own status, timing, and error history. Building dashboards and alerting across this matrix is significantly more complex than single-destination monitoring.

Scaling the fan-out layer

If you receive 1,000 events per minute and each event fans out to 5 destinations, your fan-out layer needs to handle 5,000 delivery operations per minute—each with its own retry, timeout, and error-handling behavior. The fan-out multiplier applies to every component downstream of the branching point: queues, workers, database connections, and network bandwidth.

How Hookdeck handles fan-out

A webhook gateway like Hookdeck Event Gateway treats fan-out as a first-class infrastructure primitive rather than something you build on top of generic tools.

The connection model

In Hookdeck, fan-out is a natural consequence of the Source -> Connection -> Destination model. A Source represents a webhook provider (Stripe, Shopify, GitHub). A Destination is an endpoint that receives events. A Connection links a Source to a Destination with optional rules (filters, transformations, retry configuration).

Fan-out happens when you create multiple Connections from the same Source. When Hookdeck receives a webhook on a Source URL, it creates a copy of the event for every Connection attached to that Source and delivers each copy to its respective Destination independently.

There's nothing extra to configure. No message broker to deploy. No subscription management to build. You add a Connection, and the new destination starts receiving events.

Conditional fan-out with filters

Raw fan-out—sending every event to every destination—is rarely what you want. Most destinations only care about a subset of events from a given source. Hookdeck's filter rules let you control exactly which events reach each destination.

Each Connection can have a filter that matches on any combination of event body, headers, and query parameters. Events that don't match the filter are silently dropped for that Connection, while other Connections from the same Source remain unaffected.

For example, a Shopify Source might have three Connections:

  • Connection to the fulfillment service: filters for orders/create and orders/updated events.
  • Connection to the notification service: filters for orders/create events only.
  • Connection to the analytics pipeline: no filter—receives all events.

This gives you selective fan-out: each destination gets precisely the events it needs, configured declaratively without code changes.

Transformations per destination

Beyond filtering, each Connection can apply a transformation to the event payload before delivery. This means the same source event can be delivered to different destinations in different formats. Your analytics pipeline might need the raw payload. Your notification service might need a simplified structure with just the order ID and customer email. Your fulfillment service might need the payload enriched with warehouse-specific fields.

Transformations are applied independently per Connection, so changing one destination's format never affects another.

Independent delivery guarantees per destination

This is where Hookdeck's approach differs most from DIY fan-out. Each fanned-out event is delivered, retried, and tracked independently per destination. If delivery to the fulfillment service fails, Hookdeck retries it according to that Connection's retry rules—while the notification service and analytics pipeline have already successfully received their copies.

Each destination has its own delivery queue, its own retry backoff, and its own rate limiting. A slow or failing destination doesn't block or affect any other destination in the fan-out.

Full observability across fanned-out events

Every fanned-out copy of an event gets its own delivery trace in Hookdeck's dashboard. You can see which destinations received the event, when each delivery attempt occurred, what response each destination returned, and which deliveries are pending retry.

Full-text search lets you find an event by any field in the payload and see its delivery status across all destinations simultaneously. Structured issue tracking opens issues when deliveries fail, so your team can investigate and resolve problems per-destination rather than parsing aggregate error logs.

Alerting per destination

Each Connection can trigger alerts independently. If your fulfillment service stops responding, your team gets a Slack notification or a PagerDuty alert for that specific destination. The analytics pipeline, which is still processing normally, doesn't generate noise.

Fan-out patterns in practice

Here are common patterns that combine fan-out with filtering and routing.

Pattern 1: Event-type routing with full fan-out for shared events

Some events are relevant to a single service. Others need fan-out. You can model this with overlapping filter rules.

Source: Stripe Connections:

  • To payment service: filters for payment_intent.* events
  • To subscription service: filters for customer.subscription.* events
  • To notification service: filters for payment_intent.succeeded and customer.subscription.created (a subset that overlaps with both services above)
  • To audit log: no filter (receives everything)

The payment service and subscription service each get their domain-specific events. The notification service gets the events that require customer communication—a subset that crosses domain boundaries. The audit log gets everything. One Source, four Connections, four different filtered views of the same event stream.

Pattern 2: Primary processing with backup

Fan out every event to both your primary processing endpoint and a backup store (S3, a secondary database, or a dedicated archiving service). If your primary service has a bug that corrupts data, the raw events are preserved in the backup, available for replay and reprocessing.

Pattern 3: Gradual microservice decomposition

When extracting a service from a monolith, fan out relevant events to both the old monolithic endpoint and the new service. Run both in parallel, compare results, and cut over to the new service once you're confident. This is the strangler fig pattern applied to webhook processing—and fan-out at the gateway level makes it trivial to set up and tear down.

Conclusion

Fan-out is straightforward in concept—one event, many destinations—but the implementation details around reliability, independent delivery guarantees, observability, and failure handling add up fast when you build it yourself.

If your fan-out needs are simple (two destinations, low volume, best-effort delivery), application-level forwarding may be sufficient. But as you add destinations, need per-destination retry logic, require visibility into delivery status across consumers, or want to change routing without redeploying code, a purpose-built webhook gateway eliminates the infrastructure burden and lets you manage fan-out declaratively.

For a step-by-step guide to setting up fan-out with Hookdeck, see the fan-out guide. For filtering and routing configuration, see the event filtering and routing guide.