Prijímajte webhooky
Webhook informuje váš systém o zmene v Surse hneď, ako nastane: karta sa zverejní, overí alebo archivuje, otázka
zostala bez odpovede. Sursa pošle POST cez HTTPS s udalosťou na adresu, ktorú zaregistrujete. Tento návod vytvorí
príjemcu, ktorý funguje správne aj pri opakovaniach, zmene poradia a duplicitách.
Čo jednotlivé udalosti znamenajú a obsahujú, nájdete v prehľade webhookov a v katalógu udalostí v referencii API.
1. Zaregistrujte webhook
Administrátor registruje webhooky v Surse v časti Administration → Integrations → Webhooks and API
(administrácia → integrácie → webhooky) alebo cez POST /v1/webhooks s kľúčom
s rolou admin a rozsahom webhooks:manage:
curl -X POST https://api.sursa.com/v1/webhooks \
-H "Authorization: Bearer $SURSA_ADMIN_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/sursa",
"description": "Sync verified cards to the help center",
"events": ["card.published", "card.updated", "card.verified", "card.archived"]
}'Odpoveď obsahuje tajný kľúč podpisu v secret, ktorý začína na whsec_. Zobrazí sa raz. Uložte ho k svojmu
príjemcovi, napríklad do premennej prostredia SURSA_WEBHOOK_SECRET.
Adresa musí používať https a prekladať sa na verejné IP adresy: adresy v súkromných, loopback a ďalších
vyhradených sieťach sa odmietnu pri registrácii aj pri každom doručení. Ak vyvíjate na vlastnom počítači,
sprístupnite lokálneho príjemcu cez tunelovaciu službu, ktorá mu pridelí verejnú adresu HTTPS.
Testovaciu udalosť môžete kedykoľvek odoslať cez POST /v1/webhooks/{webhook_id}/test.
Odpoveď ukáže, čo odpovedal váš príjemca.
2. Ako vyzerá doručenie
POST /sursa HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
webhook-id: evt_0123456789abcdef0123456789abcdef
webhook-timestamp: 1791194400
webhook-signature: v1,l1LLl/aEl4dQq7Geb383KbyNeZgs7VZpiw6iuoJgunI=
X-KB-Signature: t=1791194400,v1=82aa6b27378d89d7d880d17c3fe96268598443ae0e64d059c0c41e94e5d3ffbe
X-KB-Event-Id: evt_0123456789abcdef0123456789abcdef
X-KB-Event: card.verified
X-KB-Delivery: 99812
X-KB-Attempt: 1
X-KB-Sequence: 1042
{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"api_version":"1.60.0","sequence":1042,"object":{"type":"card","id":"c1"},"payload":"full","data":{"card":{"id":"c1"}},"test":false}
| Hlavička | Význam |
|---|---|
webhook-id | ID udalosti. Rovnaké pri každom opakovaní. Podľa neho vyraďujte duplicity. |
webhook-timestamp | Kedy bol tento pokus podpísaný, v sekundách Unixu. |
webhook-signature | Podpis vo formáte Standard Webhooks. |
X-KB-Signature | To isté doručenie podpísané v staršom formáte Sursy. |
X-KB-Event | Typ udalosti. |
X-KB-Attempt | Číslo pokusu, od 1. |
X-KB-Sequence | Poradie tohto doručenia v rámci webhooku. |
Názvy hlavičiek nerozlišujú veľké a malé písmená; váš framework ich môže zobrazovať ako X-Kb-Signature alebo
x-kb-signature.
Telo tvorí udalosť. Jej data závisia od typu. Webhook nastavený na obsah thin dostane udalosť bez data; objekt
si namiesto toho načítajte cez API vlastným kľúčom.
3. Overte podpis
Podpis každého doručenia overte skôr, než mu uveríte. Oba formáty podpisujú presné bajty tela, preto čítajte surové telo ešte pred akýmkoľvek spracovaním JSON: opätovná serializácia JSON zmení bajty a overenie zlyhá.
| Formát | Podpisovaný reťazec | Kľúč | Podpis |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Časť tajného kľúča za whsec_, dekódovaná z base64 | v1, + base64 hodnoty HMAC-SHA256 |
Starší formát (X-KB-Signature) | <t>.<body> | Celý reťazec tajného kľúča ako text | v1= + hex hodnoty HMAC-SHA256 |
Stačí overiť jeden z nich. Uprednostnite hlavičku Standard Webhooks: rovnaké overenie ponúka mnoho hotových
knižníc. Počas výmeny tajného kľúča nesie každá hlavička jeden podpis na každý platný tajný kľúč (oddelené medzerou
vo webhook-signature, opakované v1= v X-KB-Signature). Doručenie je platné, ak sa zhoduje ktorýkoľvek z nich.
Odmietnite aj časovú značku, ktorá sa od vašich hodín líši o viac ako 5 minút. Zabráni to tomu, aby niekto zaznamenané doručenie neskôr prehral znova.
# Check a saved delivery by hand: body.json holds the raw body, the values come from its headers.
SECRET="whsec_…"; WEBHOOK_ID="evt_…"; TIMESTAMP="1791194400"
# Standard Webhooks: compare with the value after "v1," in webhook-signature
KEY=$(printf '%s' "${SECRET#whsec_}" | tr '_-' '/+')
while [ $(( ${#KEY} % 4 )) -ne 0 ]; do KEY="$KEY="; done
KEY_HEX=$(printf '%s' "$KEY" | openssl base64 -d -A | xxd -p | tr -d '\n')
{ printf '%s.%s.' "$WEBHOOK_ID" "$TIMESTAMP"; cat body.json; } \
| openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX" -binary | openssl base64 -A
# Earlier format: compare with the value after "v1=" in X-KB-Signature
{ printf '%s.' "$TIMESTAMP"; cat body.json; } | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1Otestujte overenie podpisu
Skôr než kód pripojíte na skutočné doručenia, spustite ho s týmito hodnotami. Keď sú hodiny nastavené na časovú značku, obe overenia musia prejsť a musia zlyhať, keď sa zmení jeden bajt tela.
| Vstup | Hodnota |
|---|---|
| Tajný kľúč | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp a t | 1791194400 |
| Telo | {"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}} |
webhook-signature | v1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg= |
X-KB-Signature | t=1791194400,v1=ce26a19cb63cd3ae620217bf7688e1df1044fa7d59d9ae148d215f0292b768dc |
4. Odpovedzte rýchlo, pracujte neskôr
Sursa čaká na vašu odpoveď najviac 10 sekúnd. Každý stav 2xx znamená, že doručenie uspelo; čokoľvek iné
vrátane presmerovania, vypršania časového limitu alebo zatvoreného spojenia znamená, že zlyhalo a bude sa opakovať.
Príjemca by preto mal len overiť podpis, zaznamenať udalosť a odpovedať. Skutočnú prácu vykonajte až potom, z vlastného frontu.
// Node.js 20+, no framework. verifyStandard and verifyLegacy come from step 3.
import http from "node:http";
const SECRET = process.env.SURSA_WEBHOOK_SECRET;
http
.createServer(async (req, res) => {
const parts = [];
for await (const part of req) parts.push(part);
const body = Buffer.concat(parts); // the raw bytes, before any JSON parsing
const valid = req.headers["webhook-signature"]
? verifyStandard(SECRET, req.headers, body)
: verifyLegacy(SECRET, req.headers["x-kb-signature"], body);
if (!valid) {
res.writeHead(400).end();
return;
}
const event = JSON.parse(body);
await saveEventOnce(event); // insert into your table or queue, keyed by event.id; ignore a duplicate
res.writeHead(204).end();
})
.listen(3000);2xx odpovedzte až vtedy, keď je udalosť bezpečne uložená. Ak odpoviete skôr a potom proces spadne, udalosť je
pre vás stratená, pretože Sursa ju už považuje za doručenú.
5. Opakovania
Neúspešné doručenie sa opakuje s rastúcimi prestávkami: približne po 1 minúte, 5 minútach, 30 minútach, 2 hodinách, 5 hodinách, 10 hodinách, 24 hodinách a 24 hodinách, deväť pokusov počas takmer troch dní. Každá prestávka sa líši až o 20 %, aby opakovania z mnohých webhookov neprichádzali v tom istom okamihu.
- Odpoveď
429alebo503sRetry-Afterposunie ďalší pokus najskôr na tento čas, najviac o 24 hodín neskôr. Použite ju, keď je váš systém preťažený alebo v údržbe. - Odpoveď
410 Gonewebhook vypne: posielajte ju len vtedy, keď už na túto adresu nechcete žiadne doručenia. - Po 10 neúspešných pokusoch za sebou sa webhook pozastaví a administrátori organizácie dostanú upozornenie.
Udalosti sa ďalej hromadia a po obnovení webhooku, v administrátorskom rozhraní alebo cez
POST /v1/webhooks/{webhook_id}/resume, sa doručia v poradí. - Po poslednom pokuse sa doručenie označí ako
exhausted. Môžete ho odoslať znova ručne.
Doručenia webhooku s každým pokusom, kódom vašej odpovede a jej časovaním vypisuje
GET /v1/webhooks/{webhook_id}/deliveries aj administrátorské rozhranie.
POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver odošle
jedno z nich znova. Opätovné doručenie má rovnaké webhook-id.
6. Poradie a duplicity
- Poradie. Doručenia na jeden webhook idú prísne jedno po druhom v poradí podľa
sequence. Kým sa jedno doručenie opakuje, neskoršie čakajú. Rôzne webhooky na seba nečakajú. - Duplicity. Tá istá udalosť môže prísť viac ako raz, napríklad keď sa vaša odpoveď stratila na ceste späť.
Ukladajte
webhook-id(v tele je to ajid) každej spracovanej udalosti a udalosť, ktorú ste už videli, preskočte. - Myslenie v stavoch. Pri zmenách toho istého objektu porovnajte
created_atalebosequences tým, čo máte uložené, a udalosť staršiu než stav, ktorý už máte, ignorujte. V prípade pochybností si aktuálny objekt načítajte cez API: udalosť vám povie, že sa niečo zmenilo, API vám povie, aké je to teraz.
7. Zosúlaďte údaje s kanálom udalostí
Každá udalosť sa 30 dní uchováva aj v kanáli udalostí bez ohľadu na to, či ju odoberal nejaký webhook. Ak bol
váš príjemca mimo prevádzky dlhšie, než pokrývajú opakovania, alebo chcete overiť, že vám nič neuniklo, prečítajte
kanál kľúčom s rolou admin a rozsahom webhooks:manage:
curl -G https://api.sursa.com/v1/webhook-events \
-H "Authorization: Bearer $SURSA_API_KEY" \
--data-urlencode "type=card.*" \
--data-urlencode "created_since=2026-10-20T00:00:00Z" \
--data-urlencode "limit=200"Udalosť v kanáli má rovnaké id a tvar ako v doručení, len bez polí doručenia sequence, payload a test.
Keďže váš príjemca preskakuje udalosti, ktoré už videl, zosúladenie môžete spúšťať tak často, ako chcete,
napríklad raz za hodinu.
GET /v1/webhook-events/{event_id} načíta jednu udalosť podľa jej ID.
8. Vymeňte tajný kľúč
POST /v1/webhooks/{webhook_id}/rotate-secret vydá nový tajný kľúč
a predchádzajúci ponechá v platnosti počas obdobia prekryvu, predvolene 24 hodín a najviac 7 dní. Počas prekryvu sa
každé doručenie podpisuje oboma tajnými kľúčmi.
- Vykonajte výmenu a nový tajný kľúč uložte u príjemcu vedľa starého.
- Prijmite doručenie, ak sa overí ktorýmkoľvek z tajných kľúčov.
- Po skončení prekryvu starý tajný kľúč odstráňte.
Ak starý tajný kľúč unikol, prekryv ukončite okamžite cez
POST /v1/webhooks/{webhook_id}/revoke-previous-secret alebo
vymeňte tajný kľúč s "overlap_hours": 0.
Kontrolný zoznam
- Overte jeden z podpisov na surovom tele a skontrolujte časovú značku.
- Uložte udalosť s kľúčom
webhook-ida potom do 10 sekúnd odpovedzte2xx. - Preskakujte duplicity a zastarané udalosti; keď na tom záleží, načítajte aktuálny objekt cez API.
- Keď potrebujete, aby Sursa spomalila, odpovedzte
429alebo503sRetry-After; nikdy neodpovedzte410omylom. - Preskakujte typy udalostí a polia, ktoré nepoznáte.
- Pravidelne zosúlaďujte údaje s kanálom udalostí.