---
title: "Webhooki się powtarzają. Załóż to w integracji."
description: "Webhooki płatności i platform przychodzą w oknie ponowień co najmniej raz, a nie dokładnie raz. Co powoduje podwójne dostarczenie i jak neutralizują je idempotentne handlery."
author: "Luboš Zápotočný"
published: "2026-06-17"
language: "pl"
canonical: "https://zapolu.com/pl/blog/webhooki-sie-powtarzaja/"
---

# Webhooki się powtarzają. Załóż to w integracji.

Przeczytaj dokumentację webhooków dowolnego poważnego dostawcy
(Stripe, Shopify, Adyen, GoPay), a znajdziesz w tej czy innej
postaci to samo ostrzeżenie: dostarczenia mogą nastąpić więcej niż
raz. W oknie ponowień kontrakt brzmi *at-least-once*, nie
*exactly-once*; jeśli Twój endpoint pozostanie niedostępny przez
całe okno, zdarzenie nie dotrze ani razu, dlatego pod wszystkim
innym zabezpiecza je zadanie uzgadniające, które dociąga stan z API
dostawcy. Dostarczanie dokładnie raz przez zawodną sieć jest
niemożliwe, więc dostawcy stosują jedyne rozsądne rozwiązanie: kiedy
nie mają pewności, że zdarzenie do Ciebie dotarło, wysyłają je
jeszcze raz.

Większość integracji jest napisana tak, jakby tej klauzuli nie było.
Działają na demo, działają w pierwszym miesiącu, a potem gdzieś
między dostawcą a odpowiedzią 200 zdarza się timeout i to samo
zdarzenie `order.paid` przychodzi dwa razy.

## Jakie problemy powoduje podwójne dostarczenie

- Klient dostaje dwa potwierdzenia zamówienia.
- Stan magazynowy zostaje zdjęty dwa razy; tak zaczynają się
  widmowe „niedostępne”.
- Faktura zostaje wystawiona dwukrotnie, co prędzej czy później
  wykryje księgowość.
- Saldo lojalnościowe zostaje naliczone dwa razy.

Dostawca zrobił to, co udokumentował; handler założył obietnicę,
której nikt nie złożył.

## Idempotencja to całe rozwiązanie

Idempotentny handler daje ten sam stan końcowy niezależnie od tego,
czy zdarzenie przetworzy raz, czy pięć razy. Ta jedna właściwość
neutralizuje ponowienia, ręczne replaye i całą klasę wyścigów.
Osiągnięcie jej to głównie dyscyplina:

1. **Nadaj każdemu zdarzeniu klucz.** Dostawcy wysyłają ID zdarzenia;
   jeśli Twój nie wysyła, wyprowadź je ze stabilnych pól. Zapisuj
   przetworzone ID i pomijaj duplikaty. Wymuszaj to unikalnym
   constraintem w bazie, a nie w kodzie aplikacji: sprawdzanie „czy
   już to widziałem?” w kodzie aplikacji zostawia okno na wyścig,
   constraint nie zostawia.
2. **Wybieraj stan absolutny zamiast zmian względnych.** „Ustaw
   status na opłacone” jest idempotentne z natury. „Zdejmij 2 sztuki
   ze stanu” nie jest. Kiedy dostawca wysyła delty, tłumacz je na
   upserty względem własnego zapisu zdarzenia zamiast bezwarunkowej
   arytmetyki.
3. **Zakładaj też brak kolejności.** Ponowienia przychodzą również
   nie po kolei. `order.updated` potrafi wylądować przed
   `order.created`. Numery wersji albo znaczniki czasu na rekordzie
   docelowym („zastosuj tylko, jeśli nowsze”) rozwiązują to, czego
   założenia o kolejności nie zapewnią.

## Reszta checklisty

Idempotencja to rdzeń; trzy nawyki wokół niej oddzielają integracje,
które przeżywają produkcję, od tych, które produkują tickety:

- **Potwierdzaj szybko, przetwarzaj asynchronicznie.** Zweryfikuj
  podpis, zapisz zdarzenie, zwróć 200, a właściwą pracę wykonuj
  z kolejki. Wolne synchroniczne handlery powodują timeouty,
  a timeouty kolejne ponowienia, których się obawiasz; jedno napędza
  drugie.
- **Uwierzytelniaj każdy webhook.** Endpoint webhooka to
  nieuwierzytelniony URL w publicznym internecie, który zmienia stan
  Twojego biznesu. Większość dostawców podpisuje payload HMAC-iem,
  który przeliczasz i porównujesz; niektórzy (GoPay) dają tylko ID
  i oczekują, że sam pobierzesz miarodajny stan z ich API. Tak czy
  inaczej, nigdy nie działaj na surowym żądaniu w postaci, w jakiej
  przyszło.
- **Utrzymuj dead-letter queue z narzędziem do replayu.** Część
  zdarzeń będzie zawodzić z powodów, których retry nie naprawi:
  usunięte SKU, luka w mapowaniu. Odstawiaj je na bok, alarmuj
  o nich i spraw, żeby ponowne odtworzenie jednego zdarzenia było
  przyciskiem, a nie sesją SSH. Kiedy zewnętrzne API jest
  niedostępne, replay pozwala nadrobić zaległości bez ogłaszania
  incydentu.

Test na wszystko powyższe jest zaskakująco prosty: weź wczorajsze
zdarzenia ze stagingu i dostarcz każde z nich dwa razy,
w przetasowanej kolejności. Jeśli stan końcowy zgadza się
z produkcją, Twoja integracja rzeczywiście jest odporna na duplikaty.

To standard, według którego budujemy integracje. W dużej mierze
właśnie to znaczy „[zbudowane tak, żeby działać dalej, gdy API po
drugiej stronie ma gorszy dzień](/pl/services/integrations/)” na
naszej stronie usług, i to samo myślenie widać w naszym
[backendzie na zamówienie](/pl/services/custom-development/).
A jeśli Twoje stany magazynowe już dryfują, warstwa webhooków to
jedno z
[pierwszych miejsc, które warto sprawdzić](/pl/blog/dlaczego-erp-i-sklep-roznia-sie-stanami/).