Pāriet uz saturu
Sursa Developers

Webhook paziņojumu saņemšana

Webhook paziņojums informē jūsu sistēmu par izmaiņu Sursā, tiklīdz tā notiek: kartīte ir publicēta, pārbaudīta vai arhivēta, uz jautājumu nav atbildes. Sursa nosūta HTTPS POST ar notikumu uz jūsu reģistrētu adresi. Šajā ceļvedī tiek izveidots saņēmējs, kas darbojas pareizi arī atkārtojumu, mainītas secības un dublikātu gadījumā.

Ko katrs notikums nozīmē un satur, sk. webhook paziņojumu pārskatā un notikumu katalogā API rokasgrāmatā.

1. Reģistrējiet galapunktu

Administrators reģistrē galapunktus Sursā sadaļā Administration → Integrations → Webhooks and API (administrēšana, integrācijas, webhook paziņojumi un API) vai ar POST /v1/webhooks un atslēgu ar lomu admin un tvērumu 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"]
  }'

Atbildē parakstīšanas noslēpums ir laukā secret, un tas sākas ar whsec_. Tas tiek parādīts vienreiz. Glabājiet to kopā ar savu saņēmēju, piemēram, vides mainīgajā SURSA_WEBHOOK_SECRET.

Adresei jāizmanto https un jāatrisinās uz publiskām IP adresēm: adreses privātos, atgriezeniskās cilpas (loopback) un citos rezervētos tīklos tiek noraidītas gan reģistrējot, gan katrā piegādē. Lai izstrādātu savā datorā, padariet lokālo saņēmēju pieejamu caur tunelēšanas pakalpojumu, kas tam piešķir publisku HTTPS adresi.

Testa notikumu jebkurā laikā var nosūtīt ar POST /v1/webhooks/{webhook_id}/test. Atbilde parāda, ko atbildēja jūsu saņēmējs.

2. Kā izskatās piegāde

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}
GalveneNozīme
webhook-idNotikuma ID. Tas pats katrā atkārtojumā. Izmantojiet to dublikātu atmešanai.
webhook-timestampKad šis mēģinājums parakstīts, Unix sekundēs.
webhook-signatureParaksts Standard Webhooks formātā.
X-KB-SignatureTā pati piegāde, parakstīta agrākajā Sursas formātā.
X-KB-EventNotikuma tips.
X-KB-AttemptMēģinājuma numurs, sākot no 1.
X-KB-SequenceŠīs piegādes pozīcija galapunkta secībā.

Galveņu nosaukumos lielie un mazie burti netiek šķiroti; jūsu ietvars tos var rādīt kā X-Kb-Signature vai x-kb-signature.

Saturs ir notikums. Tā data ir atkarīgs no tipa. Galapunkts ar satura režīmu thin saņem notikumu bez data; tā vietā nolasiet objektu caur API ar savu atslēgu.

3. Pārbaudiet parakstu

Pirms uzticaties piegādei, pārbaudiet tās parakstu. Abi formāti paraksta precīzus satura baitus, tāpēc nolasiet neapstrādāto saturu (raw body) pirms jebkādas JSON parsēšanas: atkārtota JSON serializēšana maina baitus, un paraksta pārbaude neizdodas.

FormātsParakstītā virkneAtslēgaParaksts
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>Noslēpuma daļa aiz whsec_, dekodēta no base64v1, + HMAC-SHA256 base64 formā
Agrākais formāts (X-KB-Signature)<t>.<body>Visa noslēpuma virkne kā tekstsv1= + HMAC-SHA256 heksadecimālā formā

Pietiek pārbaudīt vienu no abiem parakstiem. Dodiet priekšroku galvenei Standard Webhooks: tāda pati pārbaude ir pieejama daudzās gatavās bibliotēkās. Noslēpuma rotācijas laikā katrā galvenē ir pa vienam parakstam katram derīgajam noslēpumam (webhook-signature – atdalīti ar atstarpi, X-KB-Signature – atkārtots v1=). Piegāde ir derīga, ja sakrīt jebkurš no tiem.

Noraidiet arī laika zīmogu, kas no jūsu pulksteņa atšķiras vairāk nekā par 5 minūtēm. Tas neļauj kādam vēlāk atkārtoti nosūtīt ierakstītu piegādi.

# 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

Izmēģiniet paraksta pārbaudes kodu

Pirms pieslēdzat kodu reālām piegādēm, palaidiet to ar šīm vērtībām. Ja pulkstenis iestatīts uz laika zīmogu, abām pārbaudēm jāizdodas, bet, mainot vienu satura baitu, tām jāneizdodas.

IevadeVērtība
Noslēpumswhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp un t1791194400
Saturs{"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. Atbildiet ātri, darbu dariet pēc tam

Sursa gaida jūsu atbildi līdz 10 sekundēm. Jebkurš 2xx statuss nozīmē, ka piegāde izdevās; jebkas cits, arī novirzīšana, noildze vai slēgts savienojums, nozīmē, ka tā neizdevās un tiks atkārtota.

Tāpēc saņēmējam jāpārbauda paraksts, jāsaglabā notikums un jāatbild – nekas vairāk. Īsto darbu dariet pēc tam no savas rindas.

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

Atbildiet ar 2xx tikai pēc tam, kad notikums ir droši saglabāts. Ja vispirms atbildēsiet un tad process avarēs, notikums jums būs zudis, jo Sursa to jau ir ieskaitījusi kā piegādātu.

5. Atkārtojumi

Neizdevusies piegāde tiek atkārtota ar pieaugošām pauzēm: pēc aptuveni 1 minūtes, 5 minūtēm, 30 minūtēm, 2 stundām, 5 stundām, 10 stundām, 24 stundām un 24 stundām – deviņi mēģinājumi gandrīz trīs dienu laikā. Katra pauze var atšķirties līdz 20 %, lai daudzu galapunktu atkārtojumi nepienāktu vienā brīdī.

  • Atbilde 429 vai 503 ar Retry-After pārceļ nākamo mēģinājumu vismaz uz šo laiku, bet ne vēlāk kā par 24 stundām. Izmantojiet to, kad jūsu sistēma ir pārslogota vai notiek apkope.
  • Atbilde 410 Gone atspējo galapunktu: sūtiet to tikai tad, ja šajā adresē vairs nevēlaties saņemt piegādes.
  • Pēc 10 neveiksmīgiem mēģinājumiem pēc kārtas galapunkts tiek apturēts, un par to tiek informēti organizācijas administratori. Notikumi turpina uzkrāties, un, kad galapunkts tiek atsākts administrēšanas saskarnē vai ar POST /v1/webhooks/{webhook_id}/resume, tie tiek piegādāti secībā.
  • Pēc pēdējā mēģinājuma piegāde tiek atzīmēta kā exhausted. To var nosūtīt vēlreiz manuāli.

Galapunkta piegādes ar katru mēģinājumu, jūsu atbildes kodu un atbildes laiku uzskaita GET /v1/webhooks/{webhook_id}/deliveries un administrēšanas saskarne. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver nosūta vienu piegādi vēlreiz. Atkārtotai piegādei ir tas pats webhook-id.

6. Secība un dublikāti

  • Secība. Piegādes vienam galapunktam notiek stingri cita pēc citas sequence secībā. Kamēr viena piegāde tiek atkārtota, vēlākās gaida. Dažādi galapunkti cits uz citu negaida.
  • Dublikāti. Viens un tas pats notikums var pienākt vairāk nekā vienu reizi, piemēram, ja jūsu atbilde pazuda atpakaļceļā. Saglabājiet katra apstrādātā notikuma webhook-id (tas ir arī id saturā) un izlaidiet notikumu, kuru jau esat redzējuši.
  • Domājiet stāvokļos. Viena objekta izmaiņām salīdziniet created_at vai sequence ar saglabāto un ignorējiet notikumu, kas ir vecāks par jau zināmo stāvokli. Ja šaubāties, nolasiet pašreizējo objektu caur API: notikums pasaka, ka kaut kas mainījās, bet API pasaka, kāds tas ir tagad.

7. Saskaņojiet ar notikumu plūsmu

Katrs notikums 30 dienas tiek glabāts arī notikumu plūsmā neatkarīgi no tā, vai kāds galapunkts to ir abonējis. Ja jūsu saņēmējs nedarbojās ilgāk, nekā aptver atkārtojumi, vai vēlaties pārliecināties, ka nekas nav palaists garām, lasiet plūsmu ar atslēgu, kurai ir loma admin un tvērums 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"

Notikumam plūsmā ir tas pats id un forma kā piegādē, tikai bez piegādes laukiem sequence, payload un test. Tā kā jūsu saņēmējs izlaiž jau redzētus notikumus, saskaņošanu varat palaist tik bieži, cik vēlaties, piemēram, reizi stundā.

GET /v1/webhook-events/{event_id} nolasa vienu notikumu pēc tā ID.

8. Nomainiet noslēpumu

POST /v1/webhooks/{webhook_id}/rotate-secret izsniedz jaunu noslēpumu un saglabā iepriekšējo derīgu pārklāšanās periodā – pēc noklusējuma 24 stundas, ne ilgāk kā 7 dienas. Pārklāšanās laikā katra piegāde tiek parakstīta ar abiem noslēpumiem.

  1. Veiciet rotāciju un saglabājiet jauno noslēpumu savā saņēmējā blakus vecajam.
  2. Pieņemiet piegādi, ja tās paraksts sakrīt ar jebkuru no noslēpumiem.
  3. Pēc pārklāšanās perioda noņemiet veco noslēpumu.

Ja vecais noslēpums ir noplūdis, izbeidziet pārklāšanos nekavējoties ar POST /v1/webhooks/{webhook_id}/revoke-previous-secret vai nomainiet noslēpumu ar "overlap_hours": 0.

Kontrolsaraksts

  • Pārbaudiet vienu no parakstiem pēc neapstrādātā satura un pārbaudiet laika zīmogu.
  • Saglabājiet notikumu ar atslēgu webhook-id, tad 10 sekunžu laikā atbildiet ar 2xx.
  • Izlaidiet dublikātus un novecojušus notikumus; ja tas ir svarīgi, nolasiet pašreizējo objektu caur API.
  • Atbildiet ar 429 vai 503 un Retry-After, ja vajag, lai Sursa samazina tempu; nekad neatbildiet ar 410 nejauši.
  • Izlaidiet notikumu tipus un laukus, kurus nepazīstat.
  • Regulāri pēc grafika saskaņojiet datus ar notikumu plūsmu.