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í alespoň 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
a najdete v té či oné podobě stejné varování. Stripe vám radí, ať si
zaznamenáváte ID zpracovaných událostí, protože endpoint může stejnou
událost dostat více než jednou. Adyen radí, ať stejnou událost čekáte
dvakrát a duplicity ošetříte. Shopify vám poskytuje hlavičku
X-Shopify-Webhook-Id, podle které lze deduplikovat, a rovnou
uvádí, že doručení není zaručené vůbec. Všechny tři ověřeny
13. července 2026: Stripe,
Adyen,
Shopify.
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 pod tím vším musí ještě běžet rekonciliační úloha, která si stav dotahuje z API poskytovatele. Doručit událost přes nespolehlivou síť přesně jednou nelze, takže poskytovatelé volí bezpečnou možnost: 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 stejná 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čnou se objevovat nesprávné položky „vyprodáno“.
- Faktura se vystaví dvakrát a účetnictví to najde až za několik měsíců.
- Věrnostní body se připíšou dvakrát a nikdo si toho nevšimne, dokud čísla nepřestanou sedět.
Poskytovatel udělal, co má zdokumentované. Handler předpokládal záruku, kterou poskytovatel nikdy nedal.
Řešením je idempotence
Idempotentní handler vede ke stejnému výslednému stavu, ať událost zpracuje jednou, nebo pětkrát. Tato jediná vlastnost pohltí opakování, ruční replaye i celou třídu race conditions. Dosáhnout jí je hlavně otázka disciplíny:
- Dejte každé události klíč. 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 v aplikaci má okno pro race condition, constraint ne.
- 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 nepodmíněné aritmetiky.
- Počítejte i s přeházeným pořadím. Opakování chodí i mimo
pořadí:
order.updatedmůže dorazit předorder.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í pokrývají zbytek:
- 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í handlery způsobují timeouty a timeouty způsobují právě ta opakování, kterých se bojíte.
- 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ří payload neposílají vůbec: notifikace GoPay je HTTP GET s ID platby a autoritativní stav je potřeba načíst zpět z API GoPay, teprve pak podle něj jednat (GoPay, ověřeno 13. července 2026). Tak či tak nikdy nejednejte podle požadavku v podobě, v 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í. Odkládejte je tam, upozorňujte na ně a umožněte přehrání jedné události bez SSH na produkci. Když má poskytovatel několikahodinový výpadek, replay vám umožní zmeškané události dohnat, aniž byste vyhlašovali incident.
Jediný test pokryje všechno výše uvedené: 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, jsou vaše handlery opravdu idempotentní.
Podle tohoto standardu integrace stavíme. Je to většina toho, co popisuje naše stránka o integracích, 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ů jedním z prvních míst, kde hledat.