Webhooki
Webhooki wypychają zdarzenia na wskazany URL, zamiast zmuszać Cię do pollingu API. Każde zdarzenie jest podpisane HMAC sekretem przypisanym do endpointu, więc możesz zweryfikować, że naprawdę pochodzi ze Stawki, zanim na nie zareagujesz.
Webhooki są dostępne w planach Hobby i Pro. W planie Free przycisk tworzenia jest wyłączony; istniejące endpointy z wcześniejszego płatnego planu są zachowane, ale wstrzymane — po przejściu z powrotem na plan płatny wznawiają się automatycznie.
Konfiguracja
Dział zatytułowany „Konfiguracja”- W panelu wejdź w Webhooki (w swojej organizacji).
- Utwórz webhook → wpisz URL odbiorcy i zaznacz co najmniej jedno zdarzenie.
- Skopiuj sekret podpisujący pokazany jednorazowo — Stawka nigdy go nie pokaże ponownie. Przechowuj go jak każdy inny sekret.
- Przycisk Wyślij zdarzenie testowe w panelu wystrzeliwuje syntetyczne dostarczenie, więc możesz sprawdzić swój endpoint zanim popłyną prawdziwe zdarzenia.
Zestaw zdarzeń możesz zmienić w dowolnej chwili; sekret rotujesz ze strony szczegółów endpointa. Po rotacji poprzedni sekret przestaje weryfikować podpisy natychmiast — zaktualizuj swojego odbiorcę zanim wystrzeli kolejne zdarzenie.
Lista zdarzeń
Dział zatytułowany „Lista zdarzeń”| Zdarzenie | Kiedy strzela | Co jest w data |
|---|---|---|
key.created | Nowy klucz API utworzony w panelu. | { keyId, prefix, name, createdBy } |
key.revoked | Klucz unieważniony z panelu. | { keyId, prefix, name, revokedBy } |
key.rotated | Klucz zrotowany (stary unieważniony, nowy utworzony jako para). | { oldKeyId, newKeyId, prefix } |
quota.threshold | Pierwszy raz w danym miesiącu organizacja przekracza 80% miesięcznej kwoty zapytań. Strzela raz na miesiąc per organizacja. | { planId, limit, used, percent } |
quota.exceeded | Zapytanie zostało odrzucone, bo organizacja przekroczyła miesięczną kwotę. Strzela przy każdym odrzuconym zapytaniu — traktuj jako sygnał „przestań walić”, nie jako jednorazowy alert. | { planId, limit, used, retryAfterSeconds } |
subscription.upgraded | Plan zmieniony na wyższy (Free → Hobby/Pro, Hobby → Pro). Free → płatny emituje upgraded. | { fromPlan, toPlan } |
subscription.downgraded | Plan zmieniony na niższy lub anulowany (cokolwiek → Free). | { fromPlan, toPlan } |
rates.changed | Codzienny cron TEDB wykrył co najmniej jeden ruch stawki VAT względem wczorajszego snapshotu. | { effectiveDate, changes[] } — ten sam kształt co endpoint /changes. |
Bądź wstrzemięźliwy w subskrypcjach. quota.exceeded dla popularnego
klucza może wystrzelić tysiące razy na minutę pod atakiem. Subskrybuj
tylko te zdarzenia, które naprawdę potrzebuje Twój odbiornik.
Koperta payloadu
Dział zatytułowany „Koperta payloadu”Każde dostarczenie — niezależnie od zdarzenia — ma ten sam zewnętrzny kształt:
{ "id": "whd_2bX5tQ9...", // id dostarczenia; takie samo przy retry "event": "rates.changed", // jedna z nazw zdarzeń powyżej "orgId": "org_abc123", // id Twojej organizacji "createdAt": "2026-05-30T07:00:23.000Z", "livemode": true, // zawsze true na produkcji "data": { /* zawartość zależna od zdarzenia */ }}id to id wiersza dostarczenia, stabilny przy retry tego samego
dostarczenia. Udany odbiornik powinien deduplikować po tym polu —
jeśli już przetworzyłeś whd_2bX5tQ9... i przyszło retry, potwierdź
ACK-iem bez ponownej akcji.
Weryfikacja podpisu
Dział zatytułowany „Weryfikacja podpisu”Stawka wysyła nagłówek Stawka-Signature przy każdym zapytaniu:
Stawka-Signature: t=1748583623,v1=8b2c91a7e9d4f5c1...t— UNIX-sekundy z momentu podpisania.v1— hex SHA-256 HMAC z${t}.${rawBody}(lower case), z kluczem-sekretem Twojego endpointa. Prefiks${t}.to obrona przed replayem: atakujący, który przechwyci jeden podpisany body, nie może go wysyłać w nieskończoność.
Format wzorowany na webhookach Stripe’a — odbiorcy znający ten
wzorzec mają o jedną nową rzecz mniej do nauki. v1 to znacznik
wersji; gdybyśmy musieli zmienić algorytm, przez okno wycofania
wysyłaliśmy oba: v1 i v2.
Kroki weryfikacji
Dział zatytułowany „Kroki weryfikacji”- Odczytaj surowy body PRZED parsowaniem JSON. Białe znaki, kolejność kluczy i formatowanie liczb wpływają na HMAC. Nie serializuj ponownie z obiektu — podpisuj dokładnie te bajty, które otrzymałeś.
- Odrzuć wszystko z różnicą ponad ±5 minut. Porównaj
tz zegarem serwera; poza tym oknem zwróć 400. - HMAC-SHA256 z
${t}.${rawBody}Twoim sekretem endpointa. Porównaj w stałym czasie z polemv1(hex).
Przykład w Node.js
Dział zatytułowany „Przykład w Node.js”import crypto from "node:crypto";
function verifyStawkaSignature(rawBody, header, secret) { const parts = Object.fromEntries( header.split(",").map((kv) => kv.split("=")), ); const t = Number.parseInt(parts.t, 10); if (!Number.isFinite(t)) return false; if (Math.abs(Date.now() / 1000 - t) > 5 * 60) return false;
const expected = crypto .createHmac("sha256", secret) .update(`${t}.${rawBody}`) .digest("hex");
return crypto.timingSafeEqual( Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"), );}Wywołanie crypto.timingSafeEqual ma znaczenie — === na stringach
hex wycieka informacje o czasie, co pozwala atakującemu odzyskać
sekret bajt po bajcie. Ta sama lekcja w każdym języku: porównuj w
stałym czasie.
Polityka retry
Dział zatytułowany „Polityka retry”Wysyłamy POST i oczekujemy 2xx jako ACK. Każda inna
odpowiedź — albo jej brak w ciągu 10 sekund — liczy się jako
porażka i ponownie wstawiamy do kolejki.
Harmonogram, indeksowany numerem nieudanej próby:
| Próba | Odstęp przed kolejną |
|---|---|
| 1 | 30 s |
| 2 | 1 min |
| 3 | 2 min |
| 4 | 4 min |
| 5 | 8 min |
| 6 | 16 min |
| 7 | 32 min |
| 8 | 1 h |
| 9 | 2 h |
| 10 | 4 h |
| 11 | — (oznaczone jako dead, brak dalszych retry) |
Każde opóźnienie ma jitter ±10%, żeby rozłożyć retry między odbiorców, którzy psują się jednocześnie (np. gdy popularny host webhooków ma awarię). Łączny budżet to 11 prób.
Automatyczne wyłączanie
Dział zatytułowany „Automatyczne wyłączanie”Po 5 kolejnych nieudanych próbach endpoint jest automatycznie wstrzymany. Już wstawione dostarczenia kończą swój budżet retry; nowe zdarzenia nie wchodzą do kolejki, dopóki nie naprawisz odbiornika i nie włączysz endpointa ponownie z panelu. Pojedyncze udane dostarczenie zeruje licznik kolejnych porażek.
Semantyka dostarczania
Dział zatytułowany „Semantyka dostarczania”- At-least-once. Wstrzymanie sieci w połowie retry może
spowodować duplikat dostarczenia tego samego
id. Zawsze deduplikuj. - Kolejność best-effort. W obrębie jednego endpointa
dostarczenia z reguły przychodzą w kolejności emisji, ale retry
mogą to przetasować. Nie zakładaj, że
key.createdprzyjdzie przedkey.revokeddla tego samego klucza — jeśli kolejność istotna, odczytaj stan z API. - Porażki są obserwowalne. Każde dostarczenie jest logowane ze statusem, kodem HTTP i ewentualnym błędem sieciowym. Strona szczegółów webhooka pokazuje ostatnie dostarczenia; psujące się wiersze są widoczne inline, więc debugujesz bez grepowania logów.
Co bramkuje dostarczenie
Dział zatytułowany „Co bramkuje dostarczenie”Żeby zdarzenie rates.changed dotarło do Twojego odbiornika,
wszystkie trzy warunki muszą być spełnione:
- Twoja organizacja jest w planie Hobby lub Pro (sprawdzane ponownie w chwili strzału, więc downgrade na sekundę przed wysyłką zatrzymuje dostarczenie bez ruszania endpointa).
- Endpoint jest włączony w panelu.
rates.changedjest w zestawie subskrybowanych zdarzeń endpointa.
Ta sama bramka działa dla każdego zdarzenia. Ponowny sprawdzian planu to kontrakt „retain but pause” — Twoje endpointy przeżywają downgrade i wznawiają się w chwili powrotu do planu płatnego; nie trzeba ich konfigurować od nowa.
Wskazówki
Dział zatytułowany „Wskazówki”- Referencja payloadu zdarzeń — kształt
dataper zdarzenie jest udokumentowany w endpointach, gdzie dane zdarzenia są najbardziej istotne. Dlarates.changedzobacz/changes— ta sama tablicachanges. - Konfiguracja krok po kroku i edycja — w panelu pod Webhooki.
- Rotacja sekretu — strona szczegółów webhooka w panelu.