Preskoči na sadržaj
Sursa Developers

Primanje webhookova

Webhook obavještava vaš sustav o promjeni u Sursi čim se dogodi: kartica je objavljena, provjerena ili arhivirana, pitanje je ostalo bez odgovora. Sursa šalje HTTPS POST s događajem na adresu koju registrirate. Ovaj vodič gradi primatelja koji radi ispravno unatoč ponovnim pokušajima, promijenjenom redoslijedu i duplikatima.

Što svaki događaj znači i sadrži, pogledajte u pregledu webhookova i u katalogu događaja u referenci API-ja.

1. Registrirajte webhook

Administrator registrira webhookove u Sursi u Administration → Integrations → Webhooks and API (administracija, integracije, webhookovi i API) ili pomoću POST /v1/webhooks i ključa s ulogom admin i opsegom 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"]
  }'

Odgovor sadrži tajnu za potpisivanje u secret, koja počinje s whsec_. Prikazuje se jednom. Pohranite je uz svojeg primatelja, na primjer u varijabli okruženja SURSA_WEBHOOK_SECRET.

Adresa mora koristiti https i razlučivati se u javne IP adrese: adrese u privatnim, povratnim (loopback) i drugim rezerviranim mrežama odbijaju se pri registraciji i pri svakoj isporuci. Da biste razvijali na vlastitom računalu, izložite lokalnog primatelja putem usluge tuneliranja koja mu daje javnu HTTPS adresu.

Probni događaj pošaljite u bilo kojem trenutku pomoću POST /v1/webhooks/{webhook_id}/test. Odgovor pokazuje što je vaš primatelj odgovorio.

2. Kako izgleda isporuka

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}
ZaglavljeZnačenje
webhook-idID događaja. Isti je pri svakom ponovnom pokušaju. Koristite ga za odbacivanje duplikata.
webhook-timestampKad je ovaj pokušaj potpisan, u Unix sekundama.
webhook-signaturePotpis u formatu Standard Webhooks.
X-KB-SignatureIsta isporuka potpisana u ranijem formatu Surse.
X-KB-EventVrsta događaja.
X-KB-AttemptBroj pokušaja, od 1.
X-KB-SequenceMjesto ove isporuke u redoslijedu webhooka.

U imenima zaglavlja nije bitna veličina slova; vaš ih okvir može prikazivati kao X-Kb-Signature ili x-kb-signature.

Tijelo je događaj. Njegov data ovisi o vrsti. Webhook postavljen na tijelo isporuke thin prima događaj bez data; umjesto toga pročitajte objekt putem API-ja vlastitim ključem.

3. Provjerite potpis

Provjerite potpis svake isporuke prije nego što joj povjerujete. Oba formata potpisuju točne bajtove tijela, pa sirovo tijelo pročitajte prije bilo kakve raščlambe JSON-a: ponovna serijalizacija JSON-a mijenja bajtove i narušava provjeru.

FormatPotpisani nizKljučPotpis
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>Dio tajne nakon whsec_, dekodiran iz Base64v1, + Base64 od HMAC-SHA256
Raniji format (X-KB-Signature)<t>.<body>Cijeli niz tajne, kao tekstv1= + heksadekadski zapis HMAC-SHA256

Dovoljno je provjeriti bilo koji od njih. Prednost dajte zaglavlju Standard Webhooks: ista je provjera dostupna u mnogim gotovim bibliotekama. Tijekom rotacije tajne svako zaglavlje nosi po jedan potpis za svaku valjanu tajnu (odvojene razmakom u webhook-signature, ponovljeni v1= u X-KB-Signature). Isporuka je valjana ako se podudara bilo koji od njih.

Odbijte i vremensku oznaku koja od vašeg sata odstupa više od 5 minuta. Tako se sprječava da netko kasnije ponovno pošalje snimljenu isporuku.

# 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

Testirajte provjeru potpisa

Prije nego što ga povežete sa stvarnim isporukama, pokrenite svoj kod s ovim vrijednostima. Kad je sat postavljen na vremensku oznaku, obje provjere moraju proći, a moraju pasti kad se promijeni jedan bajt tijela.

UlazVrijednost
Tajnawhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp i t1791194400
Tijelo{"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. Odgovorite brzo, radite kasnije

Sursa na vaš odgovor čeka do 10 sekundi. Svaki status 2xx znači da je isporuka uspjela; sve ostalo, uključujući preusmjeravanje, istek vremena ili zatvorenu vezu, znači da nije uspjela i da će se ponoviti.

Zato primatelj treba samo provjeriti potpis, zabilježiti događaj i odgovoriti. Stvarni posao obavite nakon toga, iz vlastitog reda čekanja.

// 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);

S 2xx odgovorite tek kad je događaj sigurno pohranjen. Ako najprije odgovorite, a zatim se proces sruši, događaj je za vas izgubljen jer ga je Sursa već ubrojila u isporučene.

5. Ponovni pokušaji

Neuspjela isporuka ponavlja se sa sve duljim stankama: nakon otprilike 1 minute, 5 minuta, 30 minuta, 2 sata, 5 sati, 10 sati, 24 sata i 24 sata, ukupno devet pokušaja tijekom gotovo tri dana. Svaka stanka varira do 20 % kako ponovni pokušaji prema mnogim webhookovima ne bi stigli u istom trenutku.

  • Odgovor 429 ili 503 s Retry-After pomiče sljedeći pokušaj najmanje na to vrijeme, a najviše 24 sata kasnije. Koristite ga kad je vaš sustav preopterećen ili na održavanju.
  • Odgovor 410 Gone isključuje webhook: šaljite ga samo kad na tu adresu više ne želite isporuke.
  • Nakon 10 neuspjelih pokušaja zaredom webhook se pauzira, a administratori organizacije dobivaju obavijest. Događaji se i dalje nakupljaju, a kad se rad webhooka nastavi, u administratorskom sučelju ili pomoću POST /v1/webhooks/{webhook_id}/resume, isporučuju se redom.
  • Nakon posljednjeg pokušaja isporuka se označava kao exhausted. Možete je ponovno poslati ručno.

Isporuke webhooka, sa svakim pokušajem, kodom vašeg odgovora i njegovim trajanjem, navode GET /v1/webhooks/{webhook_id}/deliveries i administratorsko sučelje. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver ponovno šalje jednu isporuku. Ponovna isporuka ima isti webhook-id.

6. Redoslijed i duplikati

  • Redoslijed. Isporuke jednom webhooku idu strogo jedna za drugom, redoslijedom sequence. Dok se jedna isporuka ponavlja, kasnije čekaju. Različiti webhookovi ne čekaju jedan drugoga.
  • Duplikati. Isti događaj može stići više puta, na primjer kad se vaš odgovor izgubio na povratku. Pohranite webhook-id (to je ujedno id u tijelu) svakog događaja koji obradite i preskočite onaj koji ste već vidjeli.
  • Razmišljanje u stanjima. Kod promjena istog objekta usporedite created_at ili sequence s onim što ste pohranili i zanemarite događaj koji je stariji od stanja koje već imate. U slučaju sumnje pročitajte trenutačni objekt putem API-ja: događaj vam kaže da se nešto promijenilo, a API vam kaže kakvo je sada.

7. Uskladite stanje s feedom događaja

Svaki se događaj čuva i 30 dana u feedu događaja, bez obzira na to je li neki webhook bio pretplaćen na njega. Ako vaš primatelj nije radio dulje nego što pokrivaju ponovni pokušaji ili želite provjeriti da ništa nije propušteno, pročitajte feed ključem koji ima ulogu admin i opseg 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"

Događaj u feedu ima isti id i oblik kao u isporuci, bez polja isporuke sequence, payload i test. Budući da vaš primatelj preskače događaje koje je već vidio, usklađivanje možete pokretati koliko god često želite, na primjer jednom na sat.

GET /v1/webhook-events/{event_id} čita jedan događaj prema njegovu ID-u.

8. Rotirajte tajnu

POST /v1/webhooks/{webhook_id}/rotate-secret izdaje novu tajnu, a prethodnu ostavlja valjanom tijekom razdoblja preklapanja, zadano 24 sata, a najviše 7 dana. Tijekom preklapanja svaka se isporuka potpisuje objema tajnama.

  1. Rotirajte tajnu i pohranite novu tajnu uz staru u svojem primatelju.
  2. Prihvatite isporuku ako se uspješno provjerava bilo kojom od tajni.
  3. Nakon preklapanja uklonite staru tajnu.

Ako je stara tajna procurila, odmah završite preklapanje pomoću POST /v1/webhooks/{webhook_id}/revoke-previous-secret ili rotirajte tajnu s "overlap_hours": 0.

Kontrolni popis

  • Provjerite jedan od potpisa na sirovom tijelu i provjerite vremensku oznaku.
  • Pohranite događaj pod ključem webhook-id, a zatim unutar 10 sekundi odgovorite s 2xx.
  • Preskačite duplikate i zastarjele događaje; kad je važno, pročitajte trenutačni objekt putem API-ja.
  • Odgovorite s 429 ili 503 uz Retry-After kad trebate da Sursa uspori; nikada nehotice ne odgovorite s 410.
  • Preskačite vrste događaja i polja koja ne poznajete.
  • Redovito usklađujte stanje s feedom događaja.