[{"data":1,"prerenderedAt":698},["ShallowReactive",2],{"content-query-iWjge2MrpP":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"layout":10,"crumb":11,"term":11,"summary":12,"group":13,"order":14,"updated":15,"cta":16,"related":24,"faq":42,"body":55,"_type":691,"_id":692,"_source":693,"_file":694,"_stem":695,"_extension":696,"sitemap":697},"/glossary/webhook-fan-out","glossary",false,"","What is webhook fan-out? Send one webhook to many endpoints","Webhook fan-out delivers one incoming webhook to several destinations at once. Why providers that allow one URL need it, how to implement it, and the pitfalls to avoid.","reference","Webhook fan-out","Deliver one webhook to several destinations.","Delivery and reliability",26,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay","Fan out one webhook to up to 200 destinations","Give the provider a single Webhook Relay URL and add the destinations that need each event: public APIs, servers on localhost or a private network, Slack, Discord or email. Each destination gets its own retries, logs and optional transform.","/features/webhook-multiple-destinations/","See multiple destinations","/webhook-gateway/","Webhook forwarding",[25,27,30,33,36,39],{"title":23,"to":26},"/glossary/webhook-forwarding/",{"title":28,"to":29},"Webhook gateway","/glossary/webhook-gateway/",{"title":31,"to":32},"Webhook retries","/glossary/webhook-retries/",{"title":34,"to":35},"Webhook URL","/glossary/webhook-url/",{"title":37,"to":38},"Mailgun webhook fan-out","/blog/mailgun-webhook-fanout/",{"title":40,"to":41},"502 Bad Gateway","/http-errors/502-bad-gateway/",[43,46,49,52],{"q":44,"a":45},"What is webhook fan-out?","Webhook fan-out is delivering a single incoming webhook to several destinations. The provider sends the event once to one URL, and a fan-out layer copies it to every service that needs it, such as production, staging, an analytics pipeline and a Slack channel.",{"q":47,"a":48},"Why do I need webhook fan-out?","Many providers allow only one webhook URL per account, app or event type, or limit how many you can register. Fan-out lets several services receive the same events without changing the provider's settings, and lets you add or remove consumers without touching the provider.",{"q":50,"a":51},"How do I fan out a webhook to multiple URLs?","Point the provider at one URL you control, then forward each request to every destination. You can do this with your own endpoint that writes to a queue or pub/sub topic, with a cloud service such as SNS or Pub/Sub, or with a webhook gateway that supports multiple destinations per URL.",{"q":53,"a":54},"What happens if one fan-out destination fails?","In a well-built fan-out, nothing happens to the others. Each destination should have its own delivery, timeout and retry state, so one slow or failing endpoint cannot block or duplicate deliveries to the rest.",{"type":56,"children":57,"toc":683},"root",[58,88,98,105,166,172,266,273,477,493,499,594,600,659,677],{"type":59,"tag":60,"props":61,"children":62},"element","p",{},[63,70,72,79,81,86],{"type":59,"tag":64,"props":65,"children":66},"strong",{},[67],{"type":68,"value":69},"text","Webhook fan-out is delivering one incoming webhook to several destinations.",{"type":68,"value":71}," The provider sends each event once, to one URL, and a fan-out layer copies it to every service that needs it: production and staging, a data warehouse, a Slack channel, a teammate's laptop. Each destination receives the same ",{"type":59,"tag":73,"props":74,"children":76},"a",{"href":75},"/glossary/webhook-payload/",[77],{"type":68,"value":78},"payload",{"type":68,"value":80}," and is delivered and ",{"type":59,"tag":73,"props":82,"children":83},{"href":32},[84],{"type":68,"value":85},"retried",{"type":68,"value":87}," independently.",{"type":59,"tag":89,"props":90,"children":92},"pre",{"code":91},"                       ┌──▶ https://api.example.com/webhooks         (production)\nStripe ──▶ one URL ────┼──▶ https://staging.example.com/webhooks     (staging)\n                       ├──▶ http://localhost:3000/webhooks           (developer laptop)\n                       └──▶ Slack #payments                          (people)\n",[93],{"type":59,"tag":94,"props":95,"children":96},"code",{"__ignoreMap":7},[97],{"type":68,"value":91},{"type":59,"tag":99,"props":100,"children":102},"h2",{"id":101},"why-fan-out-is-needed",[103],{"type":68,"value":104},"Why fan-out is needed",{"type":59,"tag":106,"props":107,"children":108},"ul",{},[109,120,138,148],{"type":59,"tag":110,"props":111,"children":112},"li",{},[113,118],{"type":59,"tag":64,"props":114,"children":115},{},[116],{"type":68,"value":117},"One URL per provider.",{"type":68,"value":119}," Many providers allow a single webhook URL per account or app, or a small number. If two services need the same events, something has to copy them.",{"type":59,"tag":110,"props":121,"children":122},{},[123,128,130,136],{"type":59,"tag":64,"props":124,"children":125},{},[126],{"type":68,"value":127},"Separate concerns.",{"type":68,"value":129}," Billing, analytics and notifications can each react to ",{"type":59,"tag":94,"props":131,"children":133},{"className":132},[],[134],{"type":68,"value":135},"invoice.paid",{"type":68,"value":137}," without one service calling the others.",{"type":59,"tag":110,"props":139,"children":140},{},[141,146],{"type":59,"tag":64,"props":142,"children":143},{},[144],{"type":68,"value":145},"Safer testing.",{"type":68,"value":147}," Send real production events to staging or a local build alongside production, without changing what production receives.",{"type":59,"tag":110,"props":149,"children":150},{},[151,156,158,164],{"type":59,"tag":64,"props":152,"children":153},{},[154],{"type":68,"value":155},"Easy changes.",{"type":68,"value":157}," Add or remove a consumer without editing the provider's settings or rotating its ",{"type":59,"tag":73,"props":159,"children":161},{"href":160},"/glossary/webhook-secret/",[162],{"type":68,"value":163},"secret",{"type":68,"value":165},".",{"type":59,"tag":99,"props":167,"children":169},{"id":168},"ways-to-implement-fan-out",[170],{"type":68,"value":171},"Ways to implement fan-out",{"type":59,"tag":173,"props":174,"children":175},"table",{},[176,200],{"type":59,"tag":177,"props":178,"children":179},"thead",{},[180],{"type":59,"tag":181,"props":182,"children":183},"tr",{},[184,190,195],{"type":59,"tag":185,"props":186,"children":187},"th",{},[188],{"type":68,"value":189},"Approach",{"type":59,"tag":185,"props":191,"children":192},{},[193],{"type":68,"value":194},"How",{"type":59,"tag":185,"props":196,"children":197},{},[198],{"type":68,"value":199},"Trade-offs",{"type":59,"tag":201,"props":202,"children":203},"tbody",{},[204,231,249],{"type":59,"tag":181,"props":205,"children":206},{},[207,213,226],{"type":59,"tag":208,"props":209,"children":210},"td",{},[211],{"type":68,"value":212},"Loop in your endpoint",{"type":59,"tag":208,"props":214,"children":215},{},[216,218,224],{"type":68,"value":217},"Receive, then ",{"type":59,"tag":94,"props":219,"children":221},{"className":220},[],[222],{"type":68,"value":223},"POST",{"type":68,"value":225}," to each destination",{"type":59,"tag":208,"props":227,"children":228},{},[229],{"type":68,"value":230},"Simple, but one slow destination slows the response and a failure mid-loop is hard to retry",{"type":59,"tag":181,"props":232,"children":233},{},[234,239,244],{"type":59,"tag":208,"props":235,"children":236},{},[237],{"type":68,"value":238},"Queue or pub/sub",{"type":59,"tag":208,"props":240,"children":241},{},[242],{"type":68,"value":243},"Endpoint publishes to SNS, Pub/Sub, Kafka or EventBridge; each consumer subscribes",{"type":59,"tag":208,"props":245,"children":246},{},[247],{"type":68,"value":248},"Reliable and scalable, but you run the infrastructure and the endpoint",{"type":59,"tag":181,"props":250,"children":251},{},[252,256,261],{"type":59,"tag":208,"props":253,"children":254},{},[255],{"type":68,"value":28},{"type":59,"tag":208,"props":257,"children":258},{},[259],{"type":68,"value":260},"Provider sends to the gateway, which delivers to each configured destination",{"type":59,"tag":208,"props":262,"children":263},{},[264],{"type":68,"value":265},"No code; per-destination retries and logs built in",{"type":59,"tag":267,"props":268,"children":270},"h3",{"id":269},"the-naive-loop-and-why-it-breaks",[271],{"type":68,"value":272},"The naive loop, and why it breaks",{"type":59,"tag":89,"props":274,"children":278},{"code":275,"language":276,"meta":7,"className":277,"style":7},"app.post('/webhooks', async (req, res) => {\n  for (const url of DESTINATIONS) {\n    await fetch(url, { method: 'POST', headers: req.headers, body: req.rawBody })  // ✗\n  }\n  res.sendStatus(200)\n})\n","js","language-js shiki shiki-themes github-dark",[279],{"type":59,"tag":94,"props":280,"children":281},{"__ignoreMap":7},[282,357,396,431,440,468],{"type":59,"tag":283,"props":284,"children":287},"span",{"class":285,"line":286},"line",1,[288,294,300,305,311,316,322,327,333,337,342,347,352],{"type":59,"tag":283,"props":289,"children":291},{"style":290},"--shiki-default:#E1E4E8",[292],{"type":68,"value":293},"app.",{"type":59,"tag":283,"props":295,"children":297},{"style":296},"--shiki-default:#B392F0",[298],{"type":68,"value":299},"post",{"type":59,"tag":283,"props":301,"children":302},{"style":290},[303],{"type":68,"value":304},"(",{"type":59,"tag":283,"props":306,"children":308},{"style":307},"--shiki-default:#9ECBFF",[309],{"type":68,"value":310},"'/webhooks'",{"type":59,"tag":283,"props":312,"children":313},{"style":290},[314],{"type":68,"value":315},", ",{"type":59,"tag":283,"props":317,"children":319},{"style":318},"--shiki-default:#F97583",[320],{"type":68,"value":321},"async",{"type":59,"tag":283,"props":323,"children":324},{"style":290},[325],{"type":68,"value":326}," (",{"type":59,"tag":283,"props":328,"children":330},{"style":329},"--shiki-default:#FFAB70",[331],{"type":68,"value":332},"req",{"type":59,"tag":283,"props":334,"children":335},{"style":290},[336],{"type":68,"value":315},{"type":59,"tag":283,"props":338,"children":339},{"style":329},[340],{"type":68,"value":341},"res",{"type":59,"tag":283,"props":343,"children":344},{"style":290},[345],{"type":68,"value":346},") ",{"type":59,"tag":283,"props":348,"children":349},{"style":318},[350],{"type":68,"value":351},"=>",{"type":59,"tag":283,"props":353,"children":354},{"style":290},[355],{"type":68,"value":356}," {\n",{"type":59,"tag":283,"props":358,"children":360},{"class":285,"line":359},2,[361,366,370,375,381,386,391],{"type":59,"tag":283,"props":362,"children":363},{"style":318},[364],{"type":68,"value":365},"  for",{"type":59,"tag":283,"props":367,"children":368},{"style":290},[369],{"type":68,"value":326},{"type":59,"tag":283,"props":371,"children":372},{"style":318},[373],{"type":68,"value":374},"const",{"type":59,"tag":283,"props":376,"children":378},{"style":377},"--shiki-default:#79B8FF",[379],{"type":68,"value":380}," url",{"type":59,"tag":283,"props":382,"children":383},{"style":318},[384],{"type":68,"value":385}," of",{"type":59,"tag":283,"props":387,"children":388},{"style":377},[389],{"type":68,"value":390}," DESTINATIONS",{"type":59,"tag":283,"props":392,"children":393},{"style":290},[394],{"type":68,"value":395},") {\n",{"type":59,"tag":283,"props":397,"children":399},{"class":285,"line":398},3,[400,405,410,415,420,425],{"type":59,"tag":283,"props":401,"children":402},{"style":318},[403],{"type":68,"value":404},"    await",{"type":59,"tag":283,"props":406,"children":407},{"style":296},[408],{"type":68,"value":409}," fetch",{"type":59,"tag":283,"props":411,"children":412},{"style":290},[413],{"type":68,"value":414},"(url, { method: ",{"type":59,"tag":283,"props":416,"children":417},{"style":307},[418],{"type":68,"value":419},"'POST'",{"type":59,"tag":283,"props":421,"children":422},{"style":290},[423],{"type":68,"value":424},", headers: req.headers, body: req.rawBody })  ",{"type":59,"tag":283,"props":426,"children":428},{"style":427},"--shiki-default:#6A737D",[429],{"type":68,"value":430},"// ✗\n",{"type":59,"tag":283,"props":432,"children":434},{"class":285,"line":433},4,[435],{"type":59,"tag":283,"props":436,"children":437},{"style":290},[438],{"type":68,"value":439},"  }\n",{"type":59,"tag":283,"props":441,"children":443},{"class":285,"line":442},5,[444,449,454,458,463],{"type":59,"tag":283,"props":445,"children":446},{"style":290},[447],{"type":68,"value":448},"  res.",{"type":59,"tag":283,"props":450,"children":451},{"style":296},[452],{"type":68,"value":453},"sendStatus",{"type":59,"tag":283,"props":455,"children":456},{"style":290},[457],{"type":68,"value":304},{"type":59,"tag":283,"props":459,"children":460},{"style":377},[461],{"type":68,"value":462},"200",{"type":59,"tag":283,"props":464,"children":465},{"style":290},[466],{"type":68,"value":467},")\n",{"type":59,"tag":283,"props":469,"children":471},{"class":285,"line":470},6,[472],{"type":59,"tag":283,"props":473,"children":474},{"style":290},[475],{"type":68,"value":476},"})\n",{"type":59,"tag":60,"props":478,"children":479},{},[480,482,491],{"type":68,"value":481},"If the third destination hangs, the provider times out and retries, and destinations one and two get the event again. If the third returns ",{"type":59,"tag":73,"props":483,"children":484},{"href":41},[485],{"type":59,"tag":94,"props":486,"children":488},{"className":487},[],[489],{"type":68,"value":490},"502",{"type":68,"value":492},", there's no record of it to retry. Fan-out needs a stored copy of the event and an independent delivery per destination.",{"type":59,"tag":99,"props":494,"children":496},{"id":495},"getting-fan-out-right",[497],{"type":68,"value":498},"Getting fan-out right",{"type":59,"tag":500,"props":501,"children":502},"ol",{},[503,521,539,557,574,584],{"type":59,"tag":110,"props":504,"children":505},{},[506,511,513,519],{"type":59,"tag":64,"props":507,"children":508},{},[509],{"type":68,"value":510},"Acknowledge the provider first.",{"type":68,"value":512}," Store the event, return ",{"type":59,"tag":94,"props":514,"children":516},{"className":515},[],[517],{"type":68,"value":518},"2xx",{"type":68,"value":520},", then fan out asynchronously.",{"type":59,"tag":110,"props":522,"children":523},{},[524,529,531,537],{"type":59,"tag":64,"props":525,"children":526},{},[527],{"type":68,"value":528},"Independent delivery per destination.",{"type":68,"value":530}," Each gets its own timeout, ",{"type":59,"tag":73,"props":532,"children":534},{"href":533},"/glossary/exponential-backoff/",[535],{"type":68,"value":536},"retry schedule",{"type":68,"value":538}," and status. A failing staging server must not affect production.",{"type":59,"tag":110,"props":540,"children":541},{},[542,547,549,555],{"type":59,"tag":64,"props":543,"children":544},{},[545],{"type":68,"value":546},"Pass the body through unchanged",{"type":68,"value":548}," so each destination can still verify the provider's ",{"type":59,"tag":73,"props":550,"children":552},{"href":551},"/glossary/webhook-signature/",[553],{"type":68,"value":554},"signature",{"type":68,"value":556},". Strip or replace headers only on purpose.",{"type":59,"tag":110,"props":558,"children":559},{},[560,572],{"type":59,"tag":64,"props":561,"children":562},{},[563,565,571],{"type":68,"value":564},"Make every consumer ",{"type":59,"tag":73,"props":566,"children":568},{"href":567},"/glossary/idempotency/",[569],{"type":68,"value":570},"idempotent",{"type":68,"value":165},{"type":68,"value":573}," Each destination can see retries independently.",{"type":59,"tag":110,"props":575,"children":576},{},[577,582],{"type":59,"tag":64,"props":578,"children":579},{},[580],{"type":68,"value":581},"Filter per destination.",{"type":68,"value":583}," Not every consumer needs every event type. Forwarding rules or a transform can drop the rest.",{"type":59,"tag":110,"props":585,"children":586},{},[587,592],{"type":59,"tag":64,"props":588,"children":589},{},[590],{"type":68,"value":591},"Watch per-destination health",{"type":68,"value":593}," so a dead consumer is noticed instead of silently failing.",{"type":59,"tag":99,"props":595,"children":597},{"id":596},"fan-out-with-webhook-relay",[598],{"type":68,"value":599},"Fan-out with Webhook Relay",{"type":59,"tag":60,"props":601,"children":602},{},[603,605,610,612,618,620,626,627,633,635,641,643,649,651,657],{"type":68,"value":604},"A Webhook Relay bucket has one input URL and can deliver to up to 200 ",{"type":59,"tag":73,"props":606,"children":607},{"href":20},[608],{"type":68,"value":609},"destinations",{"type":68,"value":611},". A destination can be a public HTTPS endpoint, a server on localhost or a private network reached through the ",{"type":59,"tag":94,"props":613,"children":615},{"className":614},[],[616],{"type":68,"value":617},"relay",{"type":68,"value":619}," agent, or a notification channel such as Slack, Discord, Telegram, Microsoft Teams or email. Each output has its own ",{"type":59,"tag":73,"props":621,"children":623},{"href":622},"/features/durable-retries/",[624],{"type":68,"value":625},"durable retries",{"type":68,"value":315},{"type":59,"tag":73,"props":628,"children":630},{"href":629},"/features/forwarding-rules/",[631],{"type":68,"value":632},"forwarding rules",{"type":68,"value":634}," and optional ",{"type":59,"tag":73,"props":636,"children":638},{"href":637},"/features/transform-webhooks/",[639],{"type":68,"value":640},"transform function",{"type":68,"value":642},", and every delivery shows up in the ",{"type":59,"tag":73,"props":644,"children":646},{"href":645},"/features/webhook-logs/",[647],{"type":68,"value":648},"logs",{"type":68,"value":650},". Several developers can also run ",{"type":59,"tag":94,"props":652,"children":654},{"className":653},[],[655],{"type":68,"value":656},"relay forward",{"type":68,"value":658}," against the same bucket, and every connected agent receives every webhook.",{"type":59,"tag":60,"props":660,"children":661},{},[662,664,670,672,676],{"type":68,"value":663},"For sending your own events to many customer endpoints, the same idea in reverse, the ",{"type":59,"tag":73,"props":665,"children":667},{"href":666},"/outbound-webhooks/",[668],{"type":68,"value":669},"Outbound Gateway",{"type":68,"value":671}," fans each published message out to every endpoint subscribed to its event type, and fan-out doesn't count as extra messages. For a worked example on the receiving side, see ",{"type":59,"tag":73,"props":673,"children":674},{"href":38},[675],{"type":68,"value":37},{"type":68,"value":165},{"type":59,"tag":678,"props":679,"children":680},"style",{},[681],{"type":68,"value":682},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":398,"depth":398,"links":684},[685,686,689,690],{"id":101,"depth":359,"text":104},{"id":168,"depth":359,"text":171,"children":687},[688],{"id":269,"depth":398,"text":272},{"id":495,"depth":359,"text":498},{"id":596,"depth":359,"text":599},"markdown","content:glossary:webhook-fan-out.md","content","glossary/webhook-fan-out.md","glossary/webhook-fan-out","md",{"loc":4},1793199807279]