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}
| Zaglavlje | Značenje |
|---|---|
webhook-id | ID događaja. Isti je pri svakom ponovnom pokušaju. Koristite ga za odbacivanje duplikata. |
webhook-timestamp | Kad je ovaj pokušaj potpisan, u Unix sekundama. |
webhook-signature | Potpis u formatu Standard Webhooks. |
X-KB-Signature | Ista isporuka potpisana u ranijem formatu Surse. |
X-KB-Event | Vrsta događaja. |
X-KB-Attempt | Broj pokušaja, od 1. |
X-KB-Sequence | Mjesto 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.
| Format | Potpisani niz | Ključ | Potpis |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Dio tajne nakon whsec_, dekodiran iz Base64 | v1, + Base64 od HMAC-SHA256 |
Raniji format (X-KB-Signature) | <t>.<body> | Cijeli niz tajne, kao tekst | v1= + 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' ' -f1Testirajte 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.
| Ulaz | Vrijednost |
|---|---|
| Tajna | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp i t | 1791194400 |
| Tijelo | {"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. 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
429ili503sRetry-Afterpomič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 Goneisključ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 ujednoidu tijelu) svakog događaja koji obradite i preskočite onaj koji ste već vidjeli. - Razmišljanje u stanjima. Kod promjena istog objekta usporedite
created_atilisequences 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.
- Rotirajte tajnu i pohranite novu tajnu uz staru u svojem primatelju.
- Prihvatite isporuku ako se uspješno provjerava bilo kojom od tajni.
- 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 s2xx. - Preskačite duplikate i zastarjele događaje; kad je važno, pročitajte trenutačni objekt putem API-ja.
- Odgovorite s
429ili503uzRetry-Afterkad trebate da Sursa uspori; nikada nehotice ne odgovorite s410. - Preskačite vrste događaja i polja koja ne poznajete.
- Redovito usklađujte stanje s feedom događaja.