Пријем вебхукова
Вебхук обавештава ваш систем о измени у платформи Sursa чим се она деси: картица је објављена, проверена или
архивирана, питање је остало без одговора. Sursa шаље HTTPS POST са догађајем на адресу коју региструјете. Овај
водич гради примаоца који ради исправно и када стигну поновни покушаји, измењен редослед и дупликати.
Шта сваки догађај значи и садржи, погледајте у прегледу вебхукова и у каталогу догађаја у API референци.
1. Региструјте вебхук
Администратор региструје вебхукове у платформи Sursa у Administration → Integrations → Webhooks and API
(администрација, интеграције, вебхукови и API) или помоћу POST /v1/webhooks
и кључа са улогом admin и опсегом 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"]
}'Одговор садржи тајну за потписивање у secret, која почиње са whsec_. Приказује се само једном. Сачувајте је
уз свог примаоца, на пример у променљивој окружења SURSA_WEBHOOK_SECRET.
Адреса мора да користи https и да се разрешава у јавне IP адресе: адресе у приватним, loopback и другим
резервисаним мрежама одбијају се при регистрацији и при свакој испоруци. Да бисте развијали на свом рачунару, изложите
локалног примаоца преко сервиса за тунеловање који му даје јавну HTTPS адресу.
Пробни догађај можете да пошаљете у сваком тренутку помоћу
POST /v1/webhooks/{webhook_id}/test. Одговор показује шта је ваш прималац
одговорио.
2. Како изгледа испорука
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}
| Заглавље | Значење |
|---|---|
webhook-id | ID догађаја. Исти је при сваком поновном покушају. Користите га за одбацивање дупликата. |
webhook-timestamp | Када је овај покушај потписан, у Unix секундама. |
webhook-signature | Потпис у формату Standard Webhooks. |
X-KB-Signature | Иста испорука, потписана у ранијем формату платформе Sursa. |
X-KB-Event | Тип догађаја. |
X-KB-Attempt | Број покушаја, од 1. |
X-KB-Sequence | Позиција ове испоруке у редоследу испорука вебхука. |
Имена заглавља не разликују велика и мала слова; ваш радни оквир може да их приказује као X-Kb-Signature или
x-kb-signature.
Тело је догађај. Његов data зависи од типа. Вебхук подешен на режим тела thin прима догађај без data; уместо
тога прочитајте објекат преко API интерфејса сопственим кључем.
3. Проверите потпис
Проверите потпис сваке испоруке пре него што јој поверујете. Оба формата потписују тачне бајтове тела, па прочитајте сирово тело пре било каквог рашчлањивања JSON садржаја: поновна серијализација JSON садржаја мења бајтове и квари проверу.
| Формат | Потписани стринг | Кључ | Потпис |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Део тајне после whsec_, декодиран из Base64 | v1, + Base64 вредност HMAC-SHA256 |
Ранији формат (X-KB-Signature) | <t>.<body> | Цео стринг тајне, као текст | v1= + хексадецимална вредност HMAC-SHA256 |
Довољно је да проверите било који од њих. Предност дајте заглављу Standard Webhooks: иста провера постоји у многим
готовим библиотекама. Током ротације тајне свако заглавље носи по један потпис за сваку важећу тајну (одвојене
размаком у webhook-signature, поновљено v1= у X-KB-Signature). Испорука је важећа ако се поклопи било који од
њих.
Одбаците и временску ознаку која се од вашег сата разликује за више од 5 минута. Тако нико не може касније поново да пошаље снимљену испоруку.
# 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Тестирајте проверу потписа
Пре него што свој код повежете са стварним испорукама, покрените га на овим вредностима. Када је сат подешен на временску ознаку, обе провере морају да прођу, а морају да падну када се промени један бајт тела.
| Улаз | Вредност |
|---|---|
| Тајна | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp и t | 1791194400 |
| Тело | {"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. Одговорите брзо, а посао обавите касније
Sursa чека ваш одговор до 10 секунди. Сваки статус 2xx значи да је испорука успела; све остало, укључујући
преусмеравање, истек времена или затворену везу, значи да није успела и да ће бити поновљена.
Зато прималац треба само да провери потпис, сачува догађај и одговори. Стварни посао обавите касније, из сопственог реда.
// 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 тек када је догађај безбедно сачуван. Ако прво одговорите, а затим се програм сруши, догађај је за
вас изгубљен, јер га је Sursa већ рачунала као испорученог.
5. Поновни покушаји
Неуспела испорука понавља се са све дужим паузама: после приближно 1 минута, 5 минута, 30 минута, 2 сата, 5 сати, 10 сати, 24 сата и 24 сата – девет покушаја током скоро три дана. Свака пауза варира до 20%, како поновни покушаји са многих вебхукова не би стигли у истом тренутку.
- Одговор
429или503саRetry-Afterпомера следећи покушај најмање на то време, највише 24 сата касније. Користите га када је ваш систем преоптерећен или у одржавању. - Одговор
410 Goneискључује вебхук: шаљите га само када не желите више испорука на ту адресу. - После 10 узастопних неуспелих покушаја вебхук се паузира, а администратори организације добијају обавештење.
Догађаји се и даље гомилају и, када се рад вебхука настави у администраторском интерфејсу или помоћу
POST /v1/webhooks/{webhook_id}/resume, испоручују се по редоследу. - После последњег покушаја испорука се означава као
exhausted. Можете ручно поново да је пошаљете.
Испоруке вебхука, са сваким покушајем, кодом вашег одговора и његовим трајањем, наводе
GET /v1/webhooks/{webhook_id}/deliveries и администраторски
интерфејс. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
поново шаље једну испоруку. Поновна испорука има исти webhook-id.
6. Редослед и дупликати
- Редослед. Испоруке једном вебхуку иду строго једна за другом, по редоследу
sequence. Док се једна испорука понавља, касније чекају. Различити вебхукови не чекају један на други. - Дупликати. Исти догађај може да стигне више пута, на пример када се ваш одговор изгубио на повратку. Сачувајте
webhook-id(у телу је то иid) сваког догађаја који обрадите и прескочите онај који сте већ видели. - Размишљање у стањима. За измене истог објекта упоредите
created_atилиsequenceса оним што сте сачували и занемарите догађај који је старији од стања које већ имате. Када нисте сигурни, прочитајте тренутни објекат преко API интерфејса: догађај вам каже да се нешто променило, а API вам каже какво је сада.
7. Ускладите податке са фидом догађаја
Сваки догађај се 30 дана чува и у фиду догађаја, без обзира на то да ли је неки вебхук био претплаћен на њега.
Ако ваш прималац није радио дуже него што поновни покушаји покривају или желите да проверите да ништа није
пропуштено, прочитајте фид помоћу кључа са улогом admin и опсегом 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"Догађај у фиду има исти id и облик као у испоруци, без поља испоруке sequence, payload и test. Пошто ваш
прималац прескаче догађаје које је већ видео, усклађивање можете да покрећете колико год често желите, на пример
једном на сат.
GET /v1/webhook-events/{event_id} чита један догађај по његовом
идентификатору.
8. Ротирајте тајну
POST /v1/webhooks/{webhook_id}/rotate-secret издаје нову тајну, а
претходну оставља важећом током периода преклапања – подразумевано 24 сата, највише 7 дана. Током преклапања свака
испорука се потписује обема тајнама.
- Ротирајте тајну и сачувајте нову поред старе у свом примаоцу.
- Прихватите испоруку ако се потврди било којом од тајни.
- После преклапања уклоните стару тајну.
Ако је стара тајна процурела, одмах прекините преклапање помоћу
POST /v1/webhooks/{webhook_id}/revoke-previous-secret или
извршите ротацију са "overlap_hours": 0.
Контролна листа
- Проверите један од потписа на сировом телу и проверите временску ознаку.
- Сачувајте догађај под кључем
webhook-id, па одговорите2xxу року од 10 секунди. - Прескачите дупликате и застареле догађаје; када је важно, прочитајте тренутни објекат преко API интерфејса.
- Одговорите
429или503саRetry-Afterкада желите да Sursa успори; никада случајно не одговорите са410. - Прескачите типове догађаја и поља које не познајете.
- Редовно усклађујте податке са фидом догађаја.