Přeskočit na obsah
Zapolu

17. června 2026, Luboš Zápotočný

Webhooky se opakují. Počítejte s tím.

Platební i platformní webhooky chodí v okně pro opakování aspoň jednou, ne přesně jednou. Co dvojí doručení rozbije a jak ho idempotentní handlery pohltí.

Přečtěte si dokumentaci webhooků kteréhokoli seriózního poskytovatele (Stripe, Shopify, Adyen, GoPay) a najdete v té či oné podobě totéž varování: doručení může nastat víc než jednou. V rámci okna pro opakování zní kontrakt at-least-once, ne exactly-once; když váš endpoint zůstane celé okno nedostupný, událost nedorazí ani jednou, a právě proto je pod vším ostatním záchytnou pojistkou rekonciliační úloha, která si stav dotahuje z API poskytovatele. Doručení právě jednou přes nespolehlivou síť je proslule nemožné, takže poskytovatelé dělají to rozumné: když si nejsou jistí, že jste událost dostali, pošlou ji znovu.

Většina integrací je napsaná, jako by ta klauzule neexistovala. Fungují v demu, fungují první měsíc. A pak se někde mezi poskytovatelem a odpovědí 200 stane timeout a tatáž událost order.paid dorazí dvakrát.

Co dvojí doručení skutečně rozbije

  • Zákazník dostane dvě potvrzení objednávky.
  • Sklad se odečte dvakrát a začne fantomové „vyprodáno“.
  • Faktura se vystaví dvakrát a přijde se na to až při účetní uzávěrce.
  • Věrnostní body se připíšou dvakrát a nikdo si toho nevšimne, dokud čísla nepřestanou sedět.

Nic z toho není chyba poskytovatele. Poskytovatel udělal, co má zdokumentované. Handler předpokládal slib, který nikdo nedal.

Celá oprava se jmenuje idempotence

Idempotentní handler vede ke stejnému výslednému stavu, ať událost zpracuje jednou, nebo pětkrát. Tahle jediná vlastnost pohltí opakování, ruční replaye i celou třídu race conditions, a nestojí skoro nic. Dosáhnout jí je hlavně otázka disciplíny:

  1. Klíčujte každou událost. Poskytovatelé posílají ID události; pokud ten váš ne, odvoďte ho ze stabilních polí. Zpracovaná ID si ukládejte a duplicity přeskakujte. Vynucujte to unikátním constraintem v databázi, ne aplikačním kódem: kontrola „už jsem to viděl?“ v aplikaci má okno pro race condition; constraint ne.
  2. Dávejte přednost absolutnímu stavu před relativními změnami. „Nastav stav na zaplaceno“ je idempotentní ze své podstaty. „Odečti dva kusy ze skladu“ ne. Když poskytovatel posílá delty, překládejte je do upsertů nad vlastním záznamem události, ne do slepé aritmetiky.
  3. Počítejte i s přeházeným pořadím. Opakování chodí i mimo pořadí: order.updated může dorazit před order.created. Čísla verzí nebo časová razítka na cílovém záznamu („aplikuj, jen pokud je novější“) zvládnou to, co předpoklady o pořadí ne.

Zbytek checklistu

Idempotence je jádro; tři návyky kolem ní oddělují integrace, které přežijí produkci, od těch, které generují tickety:

  • Potvrzujte rychle, zpracovávejte asynchronně. Ověřte podpis, uložte událost, vraťte 200 a skutečnou práci dělejte z fronty. Pomalý synchronní handler vyrobí timeout, timeout vyrobí další opakování a problém se sám zesiluje.
  • Ověřujte každý webhook. Webhook endpoint je neautentizovaná URL na veřejném internetu, která mění stav vašeho byznysu. Většina poskytovatelů podepisuje payload pomocí HMAC, který přepočítáte a porovnáte; někteří (GoPay) vám dají jen ID a čekají, že si autoritativní stav sami znovu stáhnete z jejich API. Tak jako tak nikdy nejednejte podle syrového požadavku tak, jak dorazil.
  • Mějte dead-letter queue s nástrojem na replay. Některé události selžou z důvodů, které opakování nespraví: smazané SKU, díra v mapování. Odstavte je, alertujte na ně a mějte na přehrání jedné události nástroj, ať se kvůli tomu nikdo nemusí hlásit na server. Když má API třetí strany výpadek, replay vám umožní dohnat zameškané události bez vyhlašování incidentu.

Otestovat se to dá jednoduše: vezměte včerejší události ze stagingu a doručte každou z nich dvakrát, v zamíchaném pořadí. Pokud výsledný stav odpovídá produkci, integrace opakovaná doručení opravdu zvládá.

Podle tohohle standardu integrace stavíme. Je to většina toho, co na naší stránce služeb znamená „odolnost vůči výpadkům API třetích stran“, a stejné uvažování se objevuje v našem vývoji na míru. A pokud se vám skladová čísla už rozjíždějí, je vrstva webhooků jednou z častých příčin.