[{"data":1,"prerenderedAt":474},["ShallowReactive",2],{"content-query-LkReSbXV0S":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":467,"_id":468,"_source":469,"_file":470,"_stem":471,"_extension":472,"sitemap":473},"/glossary/at-least-once-delivery","glossary",false,"","What is at-least-once delivery? Webhook delivery guarantees","At-least-once delivery guarantees a message is delivered one or more times, never zero, so duplicates are possible. How it compares to at-most-once and exactly-once, and why webhooks use it.","reference","At-least-once delivery","Every message arrives, possibly more than once.","Delivery and reliability",23,"2026-10-28",{"eyebrow":17,"title":18,"text":19,"to":20,"label":21,"secondaryTo":22,"secondaryLabel":23},"Webhook Relay durable retries","Make at-least-once actually mean at least once","Provider retry windows are short. Webhook Relay persists every webhook before delivering it and retries with exponential backoff for up to 30 days, so an outage on your side doesn't turn at-least-once into never.","/features/durable-retries/","See durable retries","/docs/webhooks/durable-webhooks/","Durable webhooks docs",[25,28,31,34,37,40],{"title":26,"to":27},"Idempotency","/glossary/idempotency/",{"title":29,"to":30},"Webhook retries","/glossary/webhook-retries/",{"title":32,"to":33},"Dead-letter queue","/glossary/dead-letter-queue/",{"title":35,"to":36},"Webhook replay","/glossary/webhook-replay/",{"title":38,"to":39},"504 Gateway Timeout","/http-errors/504-gateway-timeout/",{"title":41,"to":42},"Webhook retries and idempotency","/blog/webhook-retries-and-idempotency/",[44,47,50,53],{"q":45,"a":46},"What is at-least-once delivery?","At-least-once delivery is a guarantee that every message will be delivered one or more times. The sender keeps retrying until it receives an acknowledgement, so a message is not lost, but it can arrive more than once if an acknowledgement is lost or late.",{"q":48,"a":49},"What is the difference between at-least-once and exactly-once delivery?","At-least-once can deliver duplicates. Exactly-once means each message has its effect exactly one time. Over an unreliable network, exactly-once delivery is not achievable on its own; systems get exactly-once processing by combining at-least-once delivery with an idempotent receiver that ignores duplicates.",{"q":51,"a":52},"Are webhooks at-least-once?","Most webhook providers aim for at-least-once delivery: they retry when your endpoint fails or times out, which can produce duplicates. But their retry windows are limited, so if your endpoint is down longer than the window, events can still be lost.",{"q":54,"a":55},"How do I handle at-least-once delivery?","Make the consumer idempotent. Record each message ID with a unique constraint and skip IDs you have already processed. Acknowledge only after the message is safely stored, and don't rely on messages arriving in order.",{"type":57,"children":58,"toc":459},"root",[59,73,83,90,204,232,238,273,304,310,405,411,416],{"type":60,"tag":61,"props":62,"children":63},"element","p",{},[64,71],{"type":60,"tag":65,"props":66,"children":67},"strong",{},[68],{"type":69,"value":70},"text","At-least-once delivery is a guarantee that every message is delivered one or more times, never zero times.",{"type":69,"value":72}," The sender keeps retrying until it receives an acknowledgement. Nothing is lost, but if the acknowledgement is lost or late, the receiver gets the same message again. Webhooks, most message queues (SQS, RabbitMQ, Kafka with default settings) and job systems work this way.",{"type":60,"tag":74,"props":75,"children":77},"pre",{"code":76},"sender                         receiver\n  │── event evt_123 ──────────▶│  processes it, starts responding\n  │        (timeout, no 200)   │\n  │── event evt_123 (retry) ──▶│  gets it a second time\n  │◀──────────────── 200 OK ───│\n",[78],{"type":60,"tag":79,"props":80,"children":81},"code",{"__ignoreMap":7},[82],{"type":69,"value":76},{"type":60,"tag":84,"props":85,"children":87},"h2",{"id":86},"the-three-delivery-guarantees",[88],{"type":69,"value":89},"The three delivery guarantees",{"type":60,"tag":91,"props":92,"children":93},"table",{},[94,123],{"type":60,"tag":95,"props":96,"children":97},"thead",{},[98],{"type":60,"tag":99,"props":100,"children":101},"tr",{},[102,108,113,118],{"type":60,"tag":103,"props":104,"children":105},"th",{},[106],{"type":69,"value":107},"Guarantee",{"type":60,"tag":103,"props":109,"children":110},{},[111],{"type":69,"value":112},"Lost messages?",{"type":60,"tag":103,"props":114,"children":115},{},[116],{"type":69,"value":117},"Duplicates?",{"type":60,"tag":103,"props":119,"children":120},{},[121],{"type":69,"value":122},"How it's achieved",{"type":60,"tag":124,"props":125,"children":126},"tbody",{},[127,151,183],{"type":60,"tag":99,"props":128,"children":129},{},[130,136,141,146],{"type":60,"tag":131,"props":132,"children":133},"td",{},[134],{"type":69,"value":135},"At-most-once",{"type":60,"tag":131,"props":137,"children":138},{},[139],{"type":69,"value":140},"Possible",{"type":60,"tag":131,"props":142,"children":143},{},[144],{"type":69,"value":145},"Never",{"type":60,"tag":131,"props":147,"children":148},{},[149],{"type":69,"value":150},"Send once, never retry",{"type":60,"tag":99,"props":152,"children":153},{},[154,162,171,178],{"type":60,"tag":131,"props":155,"children":156},{},[157],{"type":60,"tag":65,"props":158,"children":159},{},[160],{"type":69,"value":161},"At-least-once",{"type":60,"tag":131,"props":163,"children":164},{},[165,169],{"type":60,"tag":65,"props":166,"children":167},{},[168],{"type":69,"value":145},{"type":69,"value":170}," (within the retry window)",{"type":60,"tag":131,"props":172,"children":173},{},[174],{"type":60,"tag":65,"props":175,"children":176},{},[177],{"type":69,"value":140},{"type":60,"tag":131,"props":179,"children":180},{},[181],{"type":69,"value":182},"Retry until acknowledged",{"type":60,"tag":99,"props":184,"children":185},{},[186,191,195,199],{"type":60,"tag":131,"props":187,"children":188},{},[189],{"type":69,"value":190},"Exactly-once",{"type":60,"tag":131,"props":192,"children":193},{},[194],{"type":69,"value":145},{"type":60,"tag":131,"props":196,"children":197},{},[198],{"type":69,"value":145},{"type":60,"tag":131,"props":200,"children":201},{},[202],{"type":69,"value":203},"At-least-once delivery plus deduplication",{"type":60,"tag":61,"props":205,"children":206},{},[207,209,215,217,222,224,230],{"type":69,"value":208},"Over a network, the sender can't distinguish \"the receiver never got it\" from \"the receiver got it but the reply was lost\". It has to choose: retry and risk a duplicate (at-least-once), or give up and risk a loss (at-most-once). True exactly-once ",{"type":60,"tag":210,"props":211,"children":212},"em",{},[213],{"type":69,"value":214},"delivery",{"type":69,"value":216}," isn't possible on its own. What systems offer as \"exactly-once\" is exactly-once ",{"type":60,"tag":210,"props":218,"children":219},{},[220],{"type":69,"value":221},"processing",{"type":69,"value":223},": at-least-once delivery combined with an ",{"type":60,"tag":225,"props":226,"children":227},"a",{"href":27},[228],{"type":69,"value":229},"idempotent",{"type":69,"value":231}," receiver.",{"type":60,"tag":84,"props":233,"children":235},{"id":234},"why-webhooks-are-at-least-once",[236],{"type":69,"value":237},"Why webhooks are at-least-once",{"type":60,"tag":61,"props":239,"children":240},{},[241,243,249,251,257,259,264,266,271],{"type":69,"value":242},"A webhook provider sends an HTTP ",{"type":60,"tag":79,"props":244,"children":246},{"className":245},[],[247],{"type":69,"value":248},"POST",{"type":69,"value":250}," and waits for a ",{"type":60,"tag":79,"props":252,"children":254},{"className":253},[],[255],{"type":69,"value":256},"2xx",{"type":69,"value":258},". If it gets an error, a timeout or no connection, it ",{"type":60,"tag":225,"props":260,"children":261},{"href":30},[262],{"type":69,"value":263},"retries",{"type":69,"value":265},". Duplicates appear whenever your endpoint did the work but the provider didn't see the ",{"type":60,"tag":79,"props":267,"children":269},{"className":268},[],[270],{"type":69,"value":256},{"type":69,"value":272}," in time:",{"type":60,"tag":274,"props":275,"children":276},"ul",{},[277,289,294,299],{"type":60,"tag":278,"props":279,"children":280},"li",{},[281,283,287],{"type":69,"value":282},"The handler was slow and the provider's timeout fired first, often visible as a ",{"type":60,"tag":225,"props":284,"children":285},{"href":39},[286],{"type":69,"value":38},{"type":69,"value":288}," at your proxy.",{"type":60,"tag":278,"props":290,"children":291},{},[292],{"type":69,"value":293},"The handler threw after committing.",{"type":60,"tag":278,"props":295,"children":296},{},[297],{"type":69,"value":298},"A deploy killed the process between commit and response.",{"type":60,"tag":278,"props":300,"children":301},{},[302],{"type":69,"value":303},"The provider itself retried an internal failure.",{"type":60,"tag":84,"props":305,"children":307},{"id":306},"how-to-consume-at-least-once-messages",[308],{"type":69,"value":309},"How to consume at-least-once messages",{"type":60,"tag":311,"props":312,"children":313},"ol",{},[314,354,372,382],{"type":60,"tag":278,"props":315,"children":316},{},[317,322,324,330,332,338,339,345,347,352],{"type":60,"tag":65,"props":318,"children":319},{},[320],{"type":69,"value":321},"Deduplicate on the message ID.",{"type":69,"value":323}," Store the event ID (",{"type":60,"tag":79,"props":325,"children":327},{"className":326},[],[328],{"type":69,"value":329},"id",{"type":69,"value":331},", ",{"type":60,"tag":79,"props":333,"children":335},{"className":334},[],[336],{"type":69,"value":337},"X-GitHub-Delivery",{"type":69,"value":331},{"type":60,"tag":79,"props":340,"children":342},{"className":341},[],[343],{"type":69,"value":344},"webhook-id",{"type":69,"value":346},") under a unique constraint in the same transaction as the side effect. See ",{"type":60,"tag":225,"props":348,"children":349},{"href":27},[350],{"type":69,"value":351},"idempotency",{"type":69,"value":353},".",{"type":60,"tag":278,"props":355,"children":356},{},[357,362,364,370],{"type":60,"tag":65,"props":358,"children":359},{},[360],{"type":69,"value":361},"Acknowledge after storing, not after processing.",{"type":69,"value":363}," Write the event to a queue or table, return ",{"type":60,"tag":79,"props":365,"children":367},{"className":366},[],[368],{"type":69,"value":369},"200",{"type":69,"value":371},", and process asynchronously. A short request means fewer timeouts and fewer duplicates.",{"type":60,"tag":278,"props":373,"children":374},{},[375,380],{"type":60,"tag":65,"props":376,"children":377},{},[378],{"type":69,"value":379},"Don't assume order.",{"type":69,"value":381}," Retries reorder events. Use timestamps or version numbers, or fetch current state from the provider.",{"type":60,"tag":278,"props":383,"children":384},{},[385,390,392,397,399,404],{"type":60,"tag":65,"props":386,"children":387},{},[388],{"type":69,"value":389},"Handle the \"at least\" failing.",{"type":69,"value":391}," Senders give up eventually. Failed messages should land somewhere you can see and ",{"type":60,"tag":225,"props":393,"children":394},{"href":36},[395],{"type":69,"value":396},"replay",{"type":69,"value":398}," them, such as a ",{"type":60,"tag":225,"props":400,"children":401},{"href":33},[402],{"type":69,"value":403},"dead-letter queue",{"type":69,"value":353},{"type":60,"tag":84,"props":406,"children":408},{"id":407},"at-least-once-has-a-time-limit",[409],{"type":69,"value":410},"\"At least once\" has a time limit",{"type":60,"tag":61,"props":412,"children":413},{},[414],{"type":69,"value":415},"The guarantee only holds while the sender keeps retrying. Stripe retries for up to about three days, GitHub doesn't retry automatically, and many smaller providers try only a few times. If your endpoint is down longer than that, the guarantee quietly becomes at-most-once.",{"type":60,"tag":61,"props":417,"children":418},{},[419,421,427,429,434,436,442,444,450,452,457],{"type":69,"value":420},"A ",{"type":60,"tag":225,"props":422,"children":424},{"href":423},"/glossary/webhook-gateway/",[425],{"type":69,"value":426},"webhook gateway",{"type":69,"value":428}," that stores events before delivering them closes that gap. Webhook Relay's ",{"type":60,"tag":225,"props":430,"children":431},{"href":20},[432],{"type":69,"value":433},"durable retries",{"type":69,"value":435}," persist each webhook first, then retry with ",{"type":60,"tag":225,"props":437,"children":439},{"href":438},"/glossary/exponential-backoff/",[440],{"type":69,"value":441},"exponential backoff",{"type":69,"value":443}," for up to 30 days on the Long schedule, to public endpoints or to servers on localhost and private networks. Retried and replayed deliveries are the same request, so your existing deduplication keeps working. On the sending side, the Webhook Relay ",{"type":60,"tag":225,"props":445,"children":447},{"href":446},"/outbound-webhooks/",[448],{"type":69,"value":449},"Outbound Gateway",{"type":69,"value":451}," keeps the original ",{"type":60,"tag":79,"props":453,"children":455},{"className":454},[],[456],{"type":69,"value":344},{"type":69,"value":458}," on every retry, recovery and replay so your customers can deduplicate too.",{"title":7,"searchDepth":460,"depth":460,"links":461},3,[462,464,465,466],{"id":86,"depth":463,"text":89},2,{"id":234,"depth":463,"text":237},{"id":306,"depth":463,"text":309},{"id":407,"depth":463,"text":410},"markdown","content:glossary:at-least-once-delivery.md","content","glossary/at-least-once-delivery.md","glossary/at-least-once-delivery","md",{"loc":4},1793199806743]