Preskočiť na obsah
Sursa Developers

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čkaVýznam
webhook-idID udalosti. Rovnaké pri každom opakovaní. Podľa neho vyraďujte duplicity.
webhook-timestampKedy bol tento pokus podpísaný, v sekundách Unixu.
webhook-signaturePodpis vo formáte Standard Webhooks.
X-KB-SignatureTo isté doručenie podpísané v staršom formáte Sursy.
X-KB-EventTyp udalosti.
X-KB-AttemptČíslo pokusu, od 1.
X-KB-SequencePoradie 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átPodpisovaný reťazecKľúčPodpis
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>Časť tajného kľúča za whsec_, dekódovaná z base64v1, + base64 hodnoty HMAC-SHA256
Starší formát (X-KB-Signature)<t>.<body>Celý reťazec tajného kľúča ako textv1= + 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' ' -f1

Otestujte 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.

VstupHodnota
Tajný kľúčwhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp a t1791194400
Telo{"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}}
webhook-signaturev1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg=
X-KB-Signaturet=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ď 429 alebo 503 s Retry-After posunie ď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 Gone webhook 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 aj id) každej spracovanej udalosti a udalosť, ktorú ste už videli, preskočte.
  • Myslenie v stavoch. Pri zmenách toho istého objektu porovnajte created_at alebo sequence s 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.

  1. Vykonajte výmenu a nový tajný kľúč uložte u príjemcu vedľa starého.
  2. Prijmite doručenie, ak sa overí ktorýmkoľvek z tajných kľúčov.
  3. 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-id a potom do 10 sekúnd odpovedzte 2xx.
  • Preskakujte duplicity a zastarané udalosti; keď na tom záleží, načítajte aktuálny objekt cez API.
  • Keď potrebujete, aby Sursa spomalila, odpovedzte 429 alebo 503 s Retry-After; nikdy neodpovedzte 410 omylom.
  • Preskakujte typy udalostí a polia, ktoré nepoznáte.
  • Pravidelne zosúlaďujte údaje s kanálom udalostí.