[{"data":1,"prerenderedAt":682},["ShallowReactive",2],{"content-query-IsdSozY5cm":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":43,"body":56,"_type":675,"_id":676,"_source":677,"_file":678,"_stem":679,"_extension":680,"sitemap":681},"/glossary/webhook-retries","glossary",false,"","What are webhook retries? How redelivery works","Webhook retries are repeat delivery attempts a provider makes when your endpoint fails or times out. Which responses trigger retries, typical schedules, and how to handle them safely.","reference","Webhook retries","Repeat delivery attempts after a failed or timed-out webhook.","Delivery and reliability",20,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay durable retries","Give every webhook up to 30 days of retries","Webhook Relay stores each webhook before delivering it and retries failed deliveries with exponential backoff for up to 30 days, whatever the original provider's retry policy. It works for public endpoints and for servers on localhost or behind a firewall.","/features/durable-retries/","See durable retries","/docs/webhooks/durable-webhooks/","Set up durable delivery",[25,28,31,34,37,40],{"title":26,"to":27},"Exponential backoff","/glossary/exponential-backoff/",{"title":29,"to":30},"Idempotency","/glossary/idempotency/",{"title":32,"to":33},"At-least-once delivery","/glossary/at-least-once-delivery/",{"title":35,"to":36},"Dead-letter queue","/glossary/dead-letter-queue/",{"title":38,"to":39},"503 Service Unavailable","/http-errors/503-service-unavailable/",{"title":41,"to":42},"Webhook retries and idempotency","/blog/webhook-retries-and-idempotency/",[44,47,50,53],{"q":45,"a":46},"What are webhook retries?","Webhook retries are repeat attempts a provider makes to deliver the same webhook after an earlier attempt failed. A failure is usually any non-2xx response, a timeout or a connection error. The provider waits, typically longer each time, and sends the same event again until it succeeds or its retry window ends.",{"q":48,"a":49},"What triggers a webhook retry?","Most providers retry on any response that is not 2xx, on timeouts and on network errors such as DNS failures or refused connections. Some treat certain codes specially, for example honouring Retry-After on 429 and 503, or stopping and disabling the endpoint on 410 Gone.",{"q":51,"a":52},"How long do providers retry webhooks?","It varies widely. Stripe retries with exponential backoff for up to about three days in live mode. GitHub does not retry automatically, so failed deliveries must be redelivered by hand. Many smaller providers try a few times over minutes, or not at all.",{"q":54,"a":55},"How do I stop duplicate processing from retries?","Make the handler idempotent. Store each event ID with a unique constraint when you process it, and skip any event whose ID you have already seen. Also return 2xx quickly, because slow handlers that time out cause retries of events you actually processed.",{"type":57,"children":58,"toc":666},"root",[59,105,114,121,324,358,364,443,448,454,543,549,610,616],{"type":60,"tag":61,"props":62,"children":63},"element","p",{},[64,71,73,80,82,89,91,96,98,103],{"type":60,"tag":65,"props":66,"children":67},"strong",{},[68],{"type":69,"value":70},"text","Webhook retries are repeat attempts a provider makes to deliver the same webhook after an earlier attempt failed.",{"type":69,"value":72}," If your ",{"type":60,"tag":74,"props":75,"children":77},"a",{"href":76},"/glossary/webhook-endpoint/",[78],{"type":69,"value":79},"endpoint",{"type":69,"value":81}," returns a non-",{"type":60,"tag":83,"props":84,"children":86},"code",{"className":85},[],[87],{"type":69,"value":88},"2xx",{"type":69,"value":90}," status, takes too long or can't be reached, the provider waits and sends the event again, usually with growing gaps (",{"type":60,"tag":74,"props":92,"children":93},{"href":27},[94],{"type":69,"value":95},"exponential backoff",{"type":69,"value":97},"), until it gets a ",{"type":60,"tag":83,"props":99,"children":101},{"className":100},[],[102],{"type":69,"value":88},{"type":69,"value":104}," or its retry window runs out.",{"type":60,"tag":106,"props":107,"children":109},"pre",{"code":108},"attempt 1  09:00:00  → 503 Service Unavailable\nattempt 2  09:00:05  → timeout after 15s\nattempt 3  09:05:05  → 502 Bad Gateway\nattempt 4  09:35:05  → 200 OK   ✓ delivered\n",[110],{"type":60,"tag":83,"props":111,"children":112},{"__ignoreMap":7},[113],{"type":69,"value":108},{"type":60,"tag":115,"props":116,"children":118},"h2",{"id":117},"what-triggers-a-retry",[119],{"type":69,"value":120},"What triggers a retry",{"type":60,"tag":122,"props":123,"children":124},"table",{},[125,144],{"type":60,"tag":126,"props":127,"children":128},"thead",{},[129],{"type":60,"tag":130,"props":131,"children":132},"tr",{},[133,139],{"type":60,"tag":134,"props":135,"children":136},"th",{},[137],{"type":69,"value":138},"Your endpoint's response",{"type":60,"tag":134,"props":140,"children":141},{},[142],{"type":69,"value":143},"What most providers do",{"type":60,"tag":145,"props":146,"children":147},"tbody",{},[148,165,192,209,247,268,312],{"type":60,"tag":130,"props":149,"children":150},{},[151,160],{"type":60,"tag":152,"props":153,"children":154},"td",{},[155],{"type":60,"tag":83,"props":156,"children":158},{"className":157},[],[159],{"type":69,"value":88},{"type":60,"tag":152,"props":161,"children":162},{},[163],{"type":69,"value":164},"Success, no retry",{"type":60,"tag":130,"props":166,"children":167},{},[168,179],{"type":60,"tag":152,"props":169,"children":170},{},[171,177],{"type":60,"tag":83,"props":172,"children":174},{"className":173},[],[175],{"type":69,"value":176},"3xx",{"type":69,"value":178}," redirect",{"type":60,"tag":152,"props":180,"children":181},{},[182,184,190],{"type":69,"value":183},"Usually treated as a failure. Many senders don't follow redirects for ",{"type":60,"tag":83,"props":185,"children":187},{"className":186},[],[188],{"type":69,"value":189},"POST",{"type":69,"value":191},".",{"type":60,"tag":130,"props":193,"children":194},{},[195,204],{"type":60,"tag":152,"props":196,"children":197},{},[198],{"type":60,"tag":83,"props":199,"children":201},{"className":200},[],[202],{"type":69,"value":203},"4xx",{"type":60,"tag":152,"props":205,"children":206},{},[207],{"type":69,"value":208},"Usually retried, because the provider can't tell a bug from a transient problem",{"type":60,"tag":130,"props":210,"children":211},{},[212,236],{"type":60,"tag":152,"props":213,"children":214},{},[215,225,227],{"type":60,"tag":74,"props":216,"children":218},{"href":217},"/http-errors/429-too-many-requests/",[219],{"type":60,"tag":83,"props":220,"children":222},{"className":221},[],[223],{"type":69,"value":224},"429",{"type":69,"value":226}," or ",{"type":60,"tag":74,"props":228,"children":229},{"href":39},[230],{"type":60,"tag":83,"props":231,"children":233},{"className":232},[],[234],{"type":69,"value":235},"503",{"type":60,"tag":152,"props":237,"children":238},{},[239,241],{"type":69,"value":240},"Retried, often honouring ",{"type":60,"tag":83,"props":242,"children":244},{"className":243},[],[245],{"type":69,"value":246},"Retry-After",{"type":60,"tag":130,"props":248,"children":249},{},[250,263],{"type":60,"tag":152,"props":251,"children":252},{},[253],{"type":60,"tag":74,"props":254,"children":256},{"href":255},"/http-errors/410-gone/",[257],{"type":60,"tag":83,"props":258,"children":260},{"className":259},[],[261],{"type":69,"value":262},"410 Gone",{"type":60,"tag":152,"props":264,"children":265},{},[266],{"type":69,"value":267},"Some providers stop and disable the endpoint",{"type":60,"tag":130,"props":269,"children":270},{},[271,307],{"type":60,"tag":152,"props":272,"children":273},{},[274,284,286,296,297],{"type":60,"tag":74,"props":275,"children":277},{"href":276},"/http-errors/500-internal-server-error/",[278],{"type":60,"tag":83,"props":279,"children":281},{"className":280},[],[282],{"type":69,"value":283},"500",{"type":69,"value":285},", ",{"type":60,"tag":74,"props":287,"children":289},{"href":288},"/http-errors/502-bad-gateway/",[290],{"type":60,"tag":83,"props":291,"children":293},{"className":292},[],[294],{"type":69,"value":295},"502",{"type":69,"value":285},{"type":60,"tag":74,"props":298,"children":300},{"href":299},"/http-errors/504-gateway-timeout/",[301],{"type":60,"tag":83,"props":302,"children":304},{"className":303},[],[305],{"type":69,"value":306},"504",{"type":60,"tag":152,"props":308,"children":309},{},[310],{"type":69,"value":311},"Retried",{"type":60,"tag":130,"props":313,"children":314},{},[315,320],{"type":60,"tag":152,"props":316,"children":317},{},[318],{"type":69,"value":319},"Timeout or connection error",{"type":60,"tag":152,"props":321,"children":322},{},[323],{"type":69,"value":311},{"type":60,"tag":61,"props":325,"children":326},{},[327,329,341,343,349,351,356],{"type":69,"value":328},"The key point: ",{"type":60,"tag":65,"props":330,"children":331},{},[332,334,339],{"type":69,"value":333},"only ",{"type":60,"tag":83,"props":335,"children":337},{"className":336},[],[338],{"type":69,"value":88},{"type":69,"value":340}," means \"delivered\".",{"type":69,"value":342}," If you return ",{"type":60,"tag":83,"props":344,"children":346},{"className":345},[],[347],{"type":69,"value":348},"200",{"type":69,"value":350}," and then crash, the provider considers the event delivered and won't retry. If you return ",{"type":60,"tag":83,"props":352,"children":354},{"className":353},[],[355],{"type":69,"value":283},{"type":69,"value":357}," after you already processed it, you'll get it again.",{"type":60,"tag":115,"props":359,"children":361},{"id":360},"retry-policies-vary-a-lot",[362],{"type":69,"value":363},"Retry policies vary a lot",{"type":60,"tag":365,"props":366,"children":367},"ul",{},[368,379,389,433],{"type":60,"tag":369,"props":370,"children":371},"li",{},[372,377],{"type":60,"tag":65,"props":373,"children":374},{},[375],{"type":69,"value":376},"Stripe",{"type":69,"value":378}," retries with exponential backoff for up to about three days in live mode.",{"type":60,"tag":369,"props":380,"children":381},{},[382,387],{"type":60,"tag":65,"props":383,"children":384},{},[385],{"type":69,"value":386},"GitHub",{"type":69,"value":388}," doesn't retry automatically. You redeliver failed deliveries by hand from the webhook settings or the API.",{"type":60,"tag":369,"props":390,"children":391},{},[392,403,405,410,412,417,419,424,426,432],{"type":60,"tag":65,"props":393,"children":394},{},[395,397],{"type":69,"value":396},"The Webhook Relay ",{"type":60,"tag":74,"props":398,"children":400},{"href":399},"/outbound-webhooks/",[401],{"type":69,"value":402},"Outbound Gateway",{"type":69,"value":404}," retries after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours, for up to 8 attempts within 48 hours, honours ",{"type":60,"tag":83,"props":406,"children":408},{"className":407},[],[409],{"type":69,"value":246},{"type":69,"value":411}," on ",{"type":60,"tag":83,"props":413,"children":415},{"className":414},[],[416],{"type":69,"value":224},{"type":69,"value":418}," and ",{"type":60,"tag":83,"props":420,"children":422},{"className":421},[],[423],{"type":69,"value":235},{"type":69,"value":425}," (up to one hour), and stops and disables the endpoint on ",{"type":60,"tag":83,"props":427,"children":429},{"className":428},[],[430],{"type":69,"value":431},"410",{"type":69,"value":191},{"type":60,"tag":369,"props":434,"children":435},{},[436,441],{"type":60,"tag":65,"props":437,"children":438},{},[439],{"type":69,"value":440},"Many smaller providers",{"type":69,"value":442}," try a few times over a few minutes, or not at all.",{"type":60,"tag":61,"props":444,"children":445},{},[446],{"type":69,"value":447},"You can't change a provider's policy. If an outage outlasts it, the events are gone unless something in between stored them.",{"type":60,"tag":115,"props":449,"children":451},{"id":450},"how-to-handle-retries-on-the-receiving-side",[452],{"type":69,"value":453},"How to handle retries on the receiving side",{"type":60,"tag":455,"props":456,"children":457},"ol",{},[458,475,491,523,533],{"type":60,"tag":369,"props":459,"children":460},{},[461,466,468,473],{"type":60,"tag":65,"props":462,"children":463},{},[464],{"type":69,"value":465},"Acknowledge fast.",{"type":69,"value":467}," Store or queue the event and return ",{"type":60,"tag":83,"props":469,"children":471},{"className":470},[],[472],{"type":69,"value":348},{"type":69,"value":474}," within a few seconds. A handler that does all its work inline is the most common cause of retries for events that actually succeeded.",{"type":60,"tag":369,"props":476,"children":477},{},[478,483,485,490],{"type":60,"tag":65,"props":479,"children":480},{},[481],{"type":69,"value":482},"Be idempotent.",{"type":69,"value":484}," Retries mean duplicates. Record each event ID and skip ones you've seen. See ",{"type":60,"tag":74,"props":486,"children":487},{"href":30},[488],{"type":69,"value":489},"idempotency",{"type":69,"value":191},{"type":60,"tag":369,"props":492,"children":493},{},[494,499,501,506,508,514,516,521],{"type":60,"tag":65,"props":495,"children":496},{},[497],{"type":69,"value":498},"Return the right status.",{"type":69,"value":500}," Return ",{"type":60,"tag":83,"props":502,"children":504},{"className":503},[],[505],{"type":69,"value":88},{"type":69,"value":507}," only once the event is safely stored. Return ",{"type":60,"tag":83,"props":509,"children":511},{"className":510},[],[512],{"type":69,"value":513},"5xx",{"type":69,"value":515}," if you couldn't store it, so it's retried. Acknowledge event types you don't handle with ",{"type":60,"tag":83,"props":517,"children":519},{"className":518},[],[520],{"type":69,"value":348},{"type":69,"value":522}," so they aren't retried forever.",{"type":60,"tag":369,"props":524,"children":525},{},[526,531],{"type":60,"tag":65,"props":527,"children":528},{},[529],{"type":69,"value":530},"Don't depend on order.",{"type":69,"value":532}," A retried event can arrive after a newer one. Compare timestamps or fetch current state from the provider.",{"type":60,"tag":369,"props":534,"children":535},{},[536,541],{"type":60,"tag":65,"props":537,"children":538},{},[539],{"type":69,"value":540},"Watch for failures.",{"type":69,"value":542}," A burst of retries usually means a deploy, a bad release or a full disk. Alert on them.",{"type":60,"tag":115,"props":544,"children":546},{"id":545},"how-to-retry-well-when-youre-the-sender",[547],{"type":69,"value":548},"How to retry well when you're the sender",{"type":60,"tag":365,"props":550,"children":551},{},[552,563,568,573,592],{"type":60,"tag":369,"props":553,"children":554},{},[555,557,561],{"type":69,"value":556},"Use ",{"type":60,"tag":74,"props":558,"children":559},{"href":27},[560],{"type":69,"value":95},{"type":69,"value":562}," with jitter, so a recovering endpoint isn't flattened by every pending retry at once.",{"type":60,"tag":369,"props":564,"children":565},{},[566],{"type":69,"value":567},"Keep the same event ID on every attempt so receivers can deduplicate.",{"type":60,"tag":369,"props":569,"children":570},{},[571],{"type":69,"value":572},"Set a timeout per attempt (5 to 30 seconds is common).",{"type":60,"tag":369,"props":574,"children":575},{},[576,578,583,585,591],{"type":69,"value":577},"Cap the total window, then move the event to a ",{"type":60,"tag":74,"props":579,"children":580},{"href":36},[581],{"type":69,"value":582},"dead-letter queue",{"type":69,"value":584}," or failed state where it can be ",{"type":60,"tag":74,"props":586,"children":588},{"href":587},"/glossary/webhook-replay/",[589],{"type":69,"value":590},"replayed",{"type":69,"value":191},{"type":60,"tag":369,"props":593,"children":594},{},[595,597,602,604,609],{"type":69,"value":596},"Honour ",{"type":60,"tag":83,"props":598,"children":600},{"className":599},[],[601],{"type":69,"value":246},{"type":69,"value":603}," and back off on ",{"type":60,"tag":83,"props":605,"children":607},{"className":606},[],[608],{"type":69,"value":224},{"type":69,"value":191},{"type":60,"tag":115,"props":611,"children":613},{"id":612},"adding-a-retry-layer-you-control",[614],{"type":69,"value":615},"Adding a retry layer you control",{"type":60,"tag":61,"props":617,"children":618},{},[619,621,627,629,634,636,642,644,650,652,657,659,664],{"type":69,"value":620},"Putting a ",{"type":60,"tag":74,"props":622,"children":624},{"href":623},"/glossary/webhook-gateway/",[625],{"type":69,"value":626},"webhook gateway",{"type":69,"value":628}," between the provider and your endpoint means the provider's retry policy stops being the limit. Webhook Relay's ",{"type":60,"tag":74,"props":630,"children":631},{"href":20},[632],{"type":69,"value":633},"durable retries",{"type":69,"value":635}," persist each webhook before the first delivery attempt, run fast retries for a 15-minute handoff window by default, then keep retrying with exponential backoff on a schedule you choose per destination: about 25 minutes, about 16 hours, or up to 30 days. Waiting deliveries show as ",{"type":60,"tag":83,"props":637,"children":639},{"className":638},[],[640],{"type":69,"value":641},"stalled",{"type":69,"value":643}," in the ",{"type":60,"tag":74,"props":645,"children":647},{"href":646},"/features/webhook-logs/",[648],{"type":69,"value":649},"logs",{"type":69,"value":651}," with the time of the next attempt. Setup is in the ",{"type":60,"tag":74,"props":653,"children":654},{"href":22},[655],{"type":69,"value":656},"durable webhooks docs",{"type":69,"value":658},", and the ",{"type":60,"tag":74,"props":660,"children":661},{"href":42},[662],{"type":69,"value":663},"retries and idempotency guide",{"type":69,"value":665}," goes deeper on receiver design.",{"title":7,"searchDepth":667,"depth":667,"links":668},3,[669,671,672,673,674],{"id":117,"depth":670,"text":120},2,{"id":360,"depth":670,"text":363},{"id":450,"depth":670,"text":453},{"id":545,"depth":670,"text":548},{"id":612,"depth":670,"text":615},"markdown","content:glossary:webhook-retries.md","content","glossary/webhook-retries.md","glossary/webhook-retries","md",{"loc":4},1793199909424]