Pereiti prie turinio
Sursa Developers

Webhook pranešimų gavimas

Webhook pranešimas jūsų sistemai praneša apie pakeitimą Sursoje, kai tik jis įvyksta: kortelė paskelbta, patikrinta ar archyvuota, į klausimą neatsakyta. Sursa siunčia HTTPS POST su įvykiu jūsų užregistruotu adresu. Ši instrukcija parodo, kaip sukurti gavėją, kuris veikia teisingai esant pakartojimams, pakitusiai tvarkai ir dublikatams.

Ką reiškia ir ką turi kiekvienas įvykis, žr. webhook pranešimų apžvalgoje ir API žinyno įvykių kataloge.

1. Užregistruokite galinį tašką

Administratorius registruoja galinius taškus Sursoje, skiltyje Administration → Integrations → Webhooks and API (administravimas, integracijos, webhook pranešimai ir API), arba operacija POST /v1/webhooks su raktu, turinčiu vaidmenį admin ir apimtį 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"]
  }'

Atsake pasirašymo paslaptis pateikiama lauke secret ir prasideda whsec_. Ji parodoma vieną kartą. Laikykite ją kartu su savo gavėju, pavyzdžiui, aplinkos kintamajame SURSA_WEBHOOK_SECRET.

Adresas turi naudoti https ir būti nukreiptas į viešus IP adresus: privačių, grįžtamojo ryšio (loopback) ir kitų rezervuotų tinklų adresai atmetami registruojant ir per kiekvieną pristatymą. Kad galėtumėte kurti savo kompiuteryje, vietinį gavėją paskelbkite per tuneliavimo paslaugą, kuri suteikia jam viešą HTTPS adresą.

Bandomąjį įvykį bet kada išsiųskite operacija POST /v1/webhooks/{webhook_id}/test. Atsakas parodo, ką atsakė jūsų gavėjas.

2. Kaip atrodo pristatymas

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}
AntraštėReikšmė
webhook-idĮvykio ID. Tas pats per kiekvieną pakartojimą. Pagal jį atmeskite dublikatus.
webhook-timestampKada šis bandymas pasirašytas, Unix sekundėmis.
webhook-signatureParašas Standard Webhooks formatu.
X-KB-SignatureTas pats pristatymas, pasirašytas ankstesniu Sursos formatu.
X-KB-EventĮvykio tipas.
X-KB-AttemptBandymo numeris, nuo 1.
X-KB-SequenceŠio pristatymo vieta galinio taško eilėje.

Antraščių pavadinimuose didžiosios ir mažosios raidės nesiskiria; jūsų karkasas jas gali rodyti kaip X-Kb-Signature arba x-kb-signature.

Turinys yra įvykis. Jo data priklauso nuo tipo. Galinis taškas su turinio režimu thin gauna įvykį be data; vietoj to objektą nuskaitykite per API savo raktu.

3. Patikrinkite parašą

Prieš pasitikint kiekvienu pristatymu, patikrinkite jo parašą. Abu formatai pasirašo tikslius turinio baitus, todėl neapdorotą turinį nuskaitykite prieš bet kokią JSON analizę: iš naujo serializuotas JSON turi kitus baitus, ir patikra nepavyksta.

FormatasPasirašoma eilutėRaktasParašas
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>Paslapties dalis po whsec_, iškoduota iš base64v1, + HMAC-SHA256 base64 forma
Ankstesnis formatas (X-KB-Signature)<t>.<body>Visa paslapties eilutė kaip tekstasv1= + HMAC-SHA256 šešioliktainė forma

Pakanka patikrinti bet kurį iš jų. Rinkitės Standard Webhooks antraštę: ta pati patikra yra daugelyje paruoštų bibliotekų. Paslapties rotacijos metu kiekvienoje antraštėje yra po vieną parašą kiekvienai galiojančiai paslapčiai (webhook-signature – atskirti tarpais, X-KB-Signature – pasikartojantis v1=). Pristatymas galioja, jei sutampa bet kuris iš jų.

Taip pat atmeskite laiko žymę, kuri nuo jūsų laikrodžio skiriasi daugiau nei 5 minutėmis. Tai neleidžia kam nors vėliau pakartotinai išsiųsti įrašyto pristatymo.

# 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

Išbandykite parašo tikrinimą

Kol dar neprijungėte kodo prie tikrų pristatymų, paleiskite jį su šiomis reikšmėmis. Kai laikrodis nustatytas pagal laiko žymę, abi patikros turi pavykti, o pakeitus vieną turinio baitą – nepavykti.

ĮvestisReikšmė
Paslaptiswhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp ir t1791194400
Turinys{"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. Atsakykite greitai, darbą atlikite vėliau

Sursa jūsų atsako laukia iki 10 sekundžių. Bet kokia 2xx būsena reiškia, kad pristatymas pavyko; bet kas kita, įskaitant peradresavimą, baigusį laiko limitą ar nutrauktą ryšį, reiškia, kad jis nepavyko ir bus kartojamas.

Todėl gavėjas turėtų tik patikrinti parašą, įrašyti įvykį ir atsakyti. Tikrąjį darbą atlikite vėliau, iš savo eilės.

// 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 atsakykite tik tada, kai įvykis saugiai išsaugotas. Jei pirmiau atsakysite, o paskui jūsų procesas nulūš, įvykis jums bus prarastas, nes Sursa jį jau laiko pristatytu.

5. Pakartojimai

Nepavykęs pristatymas kartojamas vis ilgesnėmis pauzėmis: maždaug po 1 minutės, 5 minučių, 30 minučių, 2 valandų, 5 valandų, 10 valandų, 24 valandų ir dar 24 valandų – devyni bandymai per beveik tris dienas. Kiekviena pauzė gali skirtis iki 20 %, kad daugelio galinių taškų pakartojimai neatkeliautų tuo pačiu metu.

  • Atsakas 429 arba 503 su Retry-After nukelia kitą bandymą bent iki to laiko, daugiausia 24 valandoms. Naudokite jį, kai jūsų sistema perkrauta arba vyksta techninė priežiūra.
  • Atsakas 410 Gone išjungia galinį tašką: siųskite jį tik tada, kai tuo adresu daugiau pristatymų nebenorite.
  • Po 10 nepavykusių bandymų iš eilės galinis taškas pristabdomas, o organizacijos administratoriams apie tai pranešama. Įvykiai toliau kaupiami, o kai galinis taškas vėl įjungiamas administravimo sąsajoje arba operacija POST /v1/webhooks/{webhook_id}/resume, jie pristatomi iš eilės.
  • Po paskutinio bandymo pristatymas pažymimas exhausted. Jį galite išsiųsti dar kartą rankiniu būdu.

Galinio taško pristatymai su kiekvienu bandymu, jūsų atsako kodu ir jo trukme pateikiami operacijoje GET /v1/webhooks/{webhook_id}/deliveries ir administravimo sąsajoje. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver išsiunčia pristatymą dar kartą. Pakartotinis pristatymas turi tą patį webhook-id.

6. Tvarka ir dublikatai

  • Tvarka. Pristatymai vienam galiniam taškui siunčiami griežtai vienas po kito pagal sequence tvarką. Kol vienas pristatymas kartojamas, vėlesni laukia. Skirtingi galiniai taškai vienas kito nelaukia.
  • Dublikatai. Tas pats įvykis gali ateiti daugiau nei kartą, pavyzdžiui, kai jūsų atsakas dingo pakeliui atgal. Išsaugokite kiekvieno apdoroto įvykio webhook-id (turinyje tai taip pat id) ir praleiskite jau matytą.
  • Mąstykite būsenomis. Kai pakeitimai susiję su tuo pačiu objektu, palyginkite created_at arba sequence su tuo, ką išsaugojote, ir ignoruokite įvykį, senesnį už jau turimą būseną. Jei abejojate, nuskaitykite dabartinį objektą per API: įvykis praneša, kad kas nors pasikeitė, o API parodo, koks jis dabar.

7. Suderinkite su įvykių juosta

Kiekvienas įvykis 30 dienų saugomas ir įvykių juostoje, nepriklausomai nuo to, ar jį prenumeravo koks nors galinis taškas. Jei jūsų gavėjas neveikė ilgiau, nei apima pakartojimai, arba norite patikrinti, ar niekas nepraleista, nuskaitykite juostą su raktu, turinčiu vaidmenį admin ir apimtį 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"

Įvykis juostoje turi tą patį id ir formą kaip pristatyme, tik be pristatymo laukų sequence, payload ir test. Kadangi jūsų gavėjas praleidžia jau matytus įvykius, suderinimą galite vykdyti kaip dažnai norite, pavyzdžiui, kartą per valandą.

GET /v1/webhook-events/{event_id} nuskaito vieną įvykį pagal jo ID.

8. Pakeiskite paslaptį (rotacija)

POST /v1/webhooks/{webhook_id}/rotate-secret išduoda naują paslaptį, o ankstesnė galioja persidengimo laikotarpiu – pagal numatytuosius nustatymus 24 valandas, daugiausia 7 dienas. Persidengimo metu kiekvienas pristatymas pasirašomas abiem paslaptimis.

  1. Atlikite rotaciją ir savo gavėjuje išsaugokite naują paslaptį šalia senosios.
  2. Priimkite pristatymą, jei jo parašas sutampa su bet kuria iš paslapčių.
  3. Pasibaigus persidengimui, pašalinkite senąją paslaptį.

Jei senoji paslaptis nutekėjo, persidengimą nutraukite iš karto operacija POST /v1/webhooks/{webhook_id}/revoke-previous-secret arba paslaptį pakeiskite su "overlap_hours": 0.

Kontrolinis sąrašas

  • Patikrinkite vieną iš parašų pagal neapdorotą turinį ir patikrinkite laiko žymę.
  • Išsaugokite įvykį pagal webhook-id, tada per 10 sekundžių atsakykite 2xx.
  • Praleiskite dublikatus ir pasenusius įvykius; kai svarbu, nuskaitykite dabartinį objektą per API.
  • Kai reikia, kad Sursa sulėtintų siuntimą, atsakykite 429 arba 503 su Retry-After; niekada neatsakykite 410 netyčia.
  • Praleiskite nežinomus įvykių tipus ir laukus.
  • Pagal tvarkaraštį suderinkite su įvykių juosta.