Guide
Fan Out Webhooks
Fan-out routing lets one provider webhook feed several downstream consumers without asking the provider to send the same event to several URLs.
In FastHook this is not a separate fan-out object. It is the normal Connections model: one Source receives the inbound request, then every dispatchable Connection from that source owns its own branch to its own Destination.
The important part is isolation. Every branch has its own connection_id, destination_id, rules, paused_at or disabled_at state, event rows, delivery attempts, metrics, and recovery controls.
When To Use Fan-Out
Use fan-out when several services need the same provider event stream, but you want to keep each downstream consumer as its own observable branch.
- Send Stripe billing events to billing, analytics, and a fraud review service.
- Send GitHub events to CI, audit storage, and a Slack notification worker.
- Deliver the same provider request to a production API and a CLI destination for local debugging.
- Keep each consumer independent instead of hiding several services behind one endpoint.
- Retry or investigate one failed branch without replaying every downstream consumer.
Dashboard Shape
Open Connections in the dashboard and use Structured mode. The graph shows sources on the left, destinations on the right, and a separate connection edge for every fan-out branch.
This is the fastest way to check whether fan-out is explicit. If three services should receive the same provider stream, you should see three connection branches from the same source.
Backend Model
The backend model is deliberately small. Sources own ingress, destinations own outbound delivery settings, and connections own routing between the two. Fan-out is created by adding more connections from the same source.
- Source is the ingress endpoint. Providers send to one source URL.
- Destination is one downstream receiver: HTTP, CLI, or mock/test destination.
- Connection is one route from source_id to destination_id.
- One source can have many connections.
- Each connection owns rules, paused_at, disabled_at, and branch identity.
- One inbound request can create one event per dispatchable connection.
- Each event has its own status, delivery attempts, response body, and timing.
- Filtered or deduplicated branches are visible through ignored_events rather than hidden inside the request.
Provider
-> Source src_stripe
-> Request req_01...
-> Connection web_billing -> Event evt_billing -> Destination des_billing
-> Connection web_analytics -> Event evt_analytics -> Destination des_analytics
-> Connection web_cli -> Event evt_cli -> Destination des_cli1. Create One Source
Start with a single source for the provider or producer. The provider should send only to this source URL. Source auth, allowed methods, custom source response, and request verification are applied before fan-out.
- Create a source such as stripe-production, github-org, or billing-provider.
- Configure source auth when the provider supports it.
- Keep allowed_http_methods narrow, usually POST for provider webhooks.
- Copy the source URL into the provider dashboard.
- Use Requests to confirm FastHook accepts the inbound request before debugging downstream fan-out.
2. Create Destinations
Create one destination per downstream consumer. Separate destinations keep endpoint URLs and ownership clear.
- Use HTTP destinations for deployed services.
- Use CLI destinations for local development or controlled debugging.
- Name destinations by owner or service, not by provider event type.
- Avoid routing several unrelated services through one shared destination URL.
Source: stripe-production
Destinations: billing-api
analytics-ingest
fraud-review
local-cli-debug3. Create One Connection Per Branch
Fan-out happens through connections. Create a separate connection from the same source to each destination that should receive the stream.
The public API is available at https://api.fasthook.io. Include your API key and team id, then create one connection per downstream branch.
- Connection names should describe the branch, such as source-to-destination.
- Each connection represents one fan-out branch.
- Adding a connection adds a new branch for future accepted requests.
- Deleting a connection removes that branch from future fan-out.
curl -X POST "https://api.fasthook.io/v1/connections" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx" \
-H "content-type: application/json" \
-d '{
"name": "stripe-to-billing",
"source_id": "src_stripe",
"destination_id": "des_billing",
"paused_at": null,
"disabled_at": null,
"rules": []
}'
curl -X POST "https://api.fasthook.io/v1/connections" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx" \
-H "content-type: application/json" \
-d '{
"name": "stripe-to-analytics",
"source_id": "src_stripe",
"destination_id": "des_analytics",
"rules": []
}'4. Understand Dispatch Semantics
FastHook receives the provider request once and stores a request record. It then evaluates the source's connections and creates branch work for every connection that should participate.
The result is that one accepted request can be followed by several events, held events, and ignored branch records. Those records are the evidence you use during operations.
- Active connection: creates a branch event and delivery attempt work.
- Paused connection: keeps the branch represented but holds delivery until unpause.
- Disabled connection: skipped for future fan-out and does not build backlog.
- Filter or deduplicate rule: can produce ignored_events for that request branch.
- Source rejection: stops before fan-out because the provider request was not accepted.
5. Keep Branch Rules Branch-Specific
Fan-out itself is the source-to-many-connections shape. Optional connection rules are useful because they stay attached to one branch.
Use the normalized connection rule pipeline for branch behavior: deduplicate, transform, filter, delay, and retry. The rule belongs to the connection, not to the source.
- Add filters when a destination should receive only part of the stream.
- Add a transformation when one consumer needs a different payload shape.
- Add retry or delay settings when one consumer has different delivery behavior.
- Add deduplication when one consumer needs duplicate suppression.
- Avoid global workarounds that make every branch inherit one consumer's needs.
curl -X PATCH "https://api.fasthook.io/v1/connections/web_billing" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx" \
-H "content-type: application/json" \
-d '{
"rules": [
{
"type": "deduplicate",
"window": 60000,
"include_fields": ["body.id"]
},
{
"type": "filter",
"body": { "type": "invoice.paid" }
},
{
"type": "retry",
"strategy": "exponential",
"interval": 1000,
"count": 5,
"response_status_codes": ["429", "500-599", "!501"]
}
]
}'Pause, Disable, And Unpause
Pause and disable are intentionally different. Pausing a connection keeps the branch in fan-out but holds delivery. Disabling a connection removes the branch from dispatch for future requests.
- Pause when a receiver is temporarily unavailable and you want to drain held events later.
- Unpause triggers FastHook to drain paused connection events.
- Disable when a branch should stop receiving future events without building backlog.
- A source with no active or paused connections is a no-op after request acceptance.
- Paused and active connections are considered dispatchable; disabled connections are skipped.
curl -X PUT "https://api.fasthook.io/v1/connections/web_cli/pause" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"
curl -X PUT "https://api.fasthook.io/v1/connections/web_cli/unpause" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"
curl -X PUT "https://api.fasthook.io/v1/connections/web_legacy/disable" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"Inspecting Fan-Out
Debug fan-out from the top down: request first, then branch events and ignored events, then attempts. One accepted request can have many event rows because every connection owns its own event.
For noisy systems, use connection_id, destination_id, status, source_id, and time-window filters. The same filter shape works for Events and metrics drill-down.
- Requests shows the inbound request, source, verified flag, and events_count.
- GET /v1/requests/:id/events shows events created from the request.
- GET /v1/requests/:id/ignored_events explains filtered or deduplicated branches.
- Events shows one row per connection branch, with source, connection, destination, status, and event id.
- Attempts show the actual destination response, retry trigger, timing, and failure code.
- Use connection and destination filters to narrow a noisy fan-out stream.
curl "https://api.fasthook.io/v1/requests/req_xxx/events" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"
curl "https://api.fasthook.io/v1/requests/req_xxx/ignored_events" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"
curl "https://api.fasthook.io/v1/events?from=now-24h&to=now&connection_id=web_billing&status=FAILED" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"
curl "https://api.fasthook.io/v1/attempts?event_id=evt_xxx&order_by=created_at&dir=desc" \
-H "Authorization: Bearer fh_api_xxx" \
-H "x-team-id: tm_xxx"Failure Isolation And Recovery
FastHook fans out by connection and then sends each branch independently. A destination outage or delivery failure on one branch is recorded on that branch's event and attempts. Sibling branches can still succeed.
- Billing can succeed while analytics fails.
- A CLI debugging branch can be offline without blocking production HTTP delivery.
- A skipped branch does not hide events from other branches.
- A failed branch can be retried without replaying every destination.
- Use Events filtered by connection or destination to isolate the failing branch.
- Use request-level recovery only when the inbound request should be re-evaluated across branches.
Design Patterns
- One source per provider account or environment keeps routing clear.
- One destination per service owner keeps ownership explicit.
- One connection per downstream consumer keeps fan-out visible.
- Use naming like provider-to-consumer, for example stripe-to-billing.
- Use CLI destinations as temporary fan-out branches for local debugging.
- Keep production branches enabled and debugging branches paused or disabled when not in use.
Operational Checklist
- Source accepts and verifies the provider request.
- Every consumer has its own destination.
- Every destination that should receive the stream has a connection.
- Every connection name clearly identifies the branch.
- Every connection rule has been reviewed as branch-local behavior.
- Paused branches are monitored so held events do not surprise operators.
- Disabled branches are intentional and not mistaken for temporary pauses.
- Receivers are idempotent even when FastHook retries.
- Dashboards and API filters are ready for request, event, connection, destination, and attempt inspection.
Common Mistakes
- Creating several provider webhooks instead of one FastHook source and several connections.
- Putting all consumers behind one destination URL and losing per-consumer observability.
- Hiding fan-out inside your own gateway instead of modeling branches as FastHook connections.
- Disabling a connection when you meant to pause and drain it later.
- Assuming one request equals one event; in fan-out, one request can produce many connection-specific events.
- Retrying the whole provider request when only one destination branch failed.