Пређите на садржај
Sursa Developers

Пријем вебхукова

Вебхук обавештава ваш систем о измени у платформи 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-idID догађаја. Исти је при сваком поновном покушају. Користите га за одбацивање дупликата.
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_, декодиран из Base64v1, + 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-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp и t1791194400
Тело{"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. Одговорите брзо, а посао обавите касније

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 дана. Током преклапања свака испорука се потписује обема тајнама.

  1. Ротирајте тајну и сачувајте нову поред старе у свом примаоцу.
  2. Прихватите испоруку ако се потврди било којом од тајни.
  3. После преклапања уклоните стару тајну.

Ако је стара тајна процурела, одмах прекините преклапање помоћу POST /v1/webhooks/{webhook_id}/revoke-previous-secret или извршите ротацију са "overlap_hours": 0.

Контролна листа

  • Проверите један од потписа на сировом телу и проверите временску ознаку.
  • Сачувајте догађај под кључем webhook-id, па одговорите 2xx у року од 10 секунди.
  • Прескачите дупликате и застареле догађаје; када је важно, прочитајте тренутни објекат преко API интерфејса.
  • Одговорите 429 или 503 са Retry-After када желите да Sursa успори; никада случајно не одговорите са 410.
  • Прескачите типове догађаја и поља које не познајете.
  • Редовно усклађујте податке са фидом догађаја.