Ugrás a tartalomra
Sursa Developers

Webhookok fogadása

A webhook azonnal értesíti a rendszeredet a Sursában történt változásokról: egy kártyát közzétettek, ellenőriztek vagy archiváltak, egy kérdés megválaszolatlan maradt. A Sursa egy HTTPS POST kérést küld az eseménnyel az általad regisztrált címre. Ez az útmutató olyan fogadót épít, amely újrapróbálkozások, sorrendcserék és ismétlődések mellett is helyesen működik.

Hogy az egyes események mit jelentenek és mit tartalmaznak, azt A webhookok áttekintése útmutató és az API-referencia eseménykatalógusa írja le.

1. Webhook regisztrálása

A webhookokat egy adminisztrátor regisztrálja a Sursában, az Administration → Integrations → Webhooks and API (adminisztráció → integrációk → webhookok és API) alatt, vagy a POST /v1/webhooks végponttal, admin szerepkörű és webhooks:manage hatókörű kulccsal:

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"]
  }'

A válasz a secret mezőben tartalmazza az aláíró titkos kulcsot, amely whsec_ előtaggal kezdődik. Ez egyszer jelenik meg. Tárold a fogadód mellett, például a SURSA_WEBHOOK_SECRET környezeti változóban.

A címnek https sémát kell használnia, és nyilvános IP-címekre kell feloldódnia: a magán-, a visszacsatolási és más fenntartott hálózatok címeit a Sursa a regisztráláskor és minden kézbesítéskor is elutasítja. Ha a saját gépeden fejlesztesz, tedd elérhetővé a helyi fogadódat egy alagútszolgáltatáson keresztül, amely nyilvános HTTPS-címet ad neki.

Tesztesemény bármikor küldhető a POST /v1/webhooks/{webhook_id}/test végponttal. A válasz megmutatja, mit felelt a fogadód.

2. Hogyan néz ki egy kézbesítés

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}
FejlécJelentése
webhook-idAz esemény azonosítója. Minden újrapróbálkozáskor ugyanaz. Ezzel szűrd ki az ismétlődéseket.
webhook-timestampMikor írták alá ezt a kísérletet, Unix-másodpercben.
webhook-signatureAz aláírás Standard Webhooks formátumban.
X-KB-SignatureUgyanez a kézbesítés a korábbi Sursa-formátumban aláírva.
X-KB-EventAz eseménytípus.
X-KB-AttemptA kísérlet sorszáma, 1-től kezdve.
X-KB-SequenceEnnek a kézbesítésnek a helye a webhook sorrendjében.

A fejlécnevekben a kis- és nagybetű nem számít; a keretrendszered X-Kb-Signature vagy x-kb-signature alakban is mutathatja őket.

A törzs maga az esemény. A data mezője a típustól függ. A thin adattartalomra beállított webhook data nélkül kapja meg az eseményt; ilyenkor az objektumot a saját kulcsoddal olvasd ki az API-n keresztül.

3. Az aláírás ellenőrzése

Minden kézbesítés aláírását ellenőrizd, mielőtt megbíznál benne. Mindkét formátum a törzs pontos bájtjait írja alá, ezért a nyers törzset olvasd be, mielőtt bármilyen JSON-elemzést végeznél: a JSON újraszerializálása megváltoztatja a bájtokat, és az ellenőrzés meghiúsul.

FormátumAláírt karakterláncKulcsAláírás
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>A titkos kulcs whsec_ utáni része, base64-dekódolvav1, + a HMAC-SHA256 base64 alakja
Korábbi formátum (X-KB-Signature)<t>.<body>A teljes titkoskulcs-karakterlánc, szövegkéntv1= + a HMAC-SHA256 hexadecimális alakja

Elég az egyiket ellenőrizni. A Standard Webhooks fejlécet részesítsd előnyben: ugyanez az ellenőrzés számos kész könyvtárban elérhető. A titkos kulcs megújítása alatt minden fejléc érvényes titkos kulcsonként egy aláírást tartalmaz (a webhook-signature fejlécben szóközzel elválasztva, az X-KB-Signature fejlécben ismételt v1= elemekkel). A kézbesítés érvényes, ha bármelyik egyezik.

Utasítsd el azt az időbélyeget is, amely 5 percnél többel eltér a saját órádtól. Így senki nem tudja később visszajátszani egy rögzített kézbesítést.

# 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

Az aláírás-ellenőrző kód tesztelése

Mielőtt valódi kézbesítésekre kapcsolnád, futtasd le a kódodat ezekkel az értékekkel. Ha az óra az időbélyegre van állítva, mindkét ellenőrzésnek sikerülnie kell, és meg kell hiúsulnia, ha a törzs egyetlen bájtja megváltozik.

BemenetÉrték
Titkos kulcswhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp és t1791194400
Törzs{"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. Gyors válasz, későbbi feldolgozás

A Sursa legfeljebb 10 másodpercig vár a válaszodra. Bármely 2xx állapotkód azt jelenti, hogy a kézbesítés sikerült; minden más, az átirányítást, az időtúllépést és a lezárt kapcsolatot is beleértve, azt jelenti, hogy sikertelen volt, és újrapróbálkozás következik.

Ezért a fogadó csak ellenőrizzen, rögzítse az eseményt, és válaszoljon. A tényleges munkát utána végezd el, a saját sorodból.

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

Csak azután válaszolj 2xx kóddal, hogy az eseményt biztonságosan eltároltad. Ha előbb válaszolsz, és utána leáll a folyamatod, az esemény elvész számodra, mert a Sursa már kézbesítettnek tekinti.

5. Újrapróbálkozások

A sikertelen kézbesítést a Sursa egyre hosszabb szünetekkel próbálja újra: körülbelül 1 perc, 5 perc, 30 perc, 2 óra, 5 óra, 10 óra, 24 óra és még 24 óra után, összesen kilenc kísérlettel, közel három nap alatt. Minden szünet legfeljebb 20%-kal eltérhet, hogy a sok webhooknak szóló újrapróbálkozások ne ugyanabban a pillanatban érkezzenek.

  • A Retry-After fejléces 429 vagy 503 válasz legalább ennyivel, legfeljebb 24 órával későbbre tolja a következő kísérletet. Akkor használd, ha a rendszered túlterhelt vagy karbantartás alatt áll.
  • A 410 Gone válasz letiltja a webhookot: csak akkor küldd, ha erre a címre nem kérsz több kézbesítést.
  • 10 egymást követő sikertelen kísérlet után a webhook szüneteltetve lesz, és a szervezet adminisztrátorai értesítést kapnak. Az események közben gyűlnek, és amikor a webhookot újraindítják, az adminisztrációs felületen vagy a POST /v1/webhooks/{webhook_id}/resume végponttal, sorrendben kézbesítődnek.
  • Az utolsó kísérlet után a kézbesítés exhausted jelölést kap. Kézzel újra elküldheted.

A webhook kézbesítéseit, az egyes kísérletekkel, a válaszkódoddal és az időzítéssel együtt, a GET /v1/webhooks/{webhook_id}/deliveries végpont és az adminisztrációs felület sorolja fel. A POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver újra elküld egy kézbesítést. Az újrakézbesítésnek ugyanaz a webhook-id azonosítója.

6. Sorrend és ismétlődések

  • Sorrend. Az egy webhooknak szóló kézbesítések szigorúan egymás után mennek ki, a sequence sorrendjében. Amíg egy kézbesítés újrapróbálása folyik, a későbbiek várnak. A különböző webhookok nem várnak egymásra.
  • Ismétlődések. Ugyanaz az esemény többször is megérkezhet, például ha a válaszod a visszaúton elveszett. Minden feldolgozott esemény webhook-id azonosítóját (ez a törzsben az id is) tárold el, és hagyd ki azt, amelyet már láttál.
  • Állapotokban gondolkodás. Ugyanannak az objektumnak a változásainál vesd össze a created_at vagy a sequence értékét az eltárolttal, és hagyd figyelmen kívül azt az eseményt, amely régebbi a meglévő állapotnál. Kétség esetén olvasd ki az aktuális objektumot az API-n keresztül: az esemény azt mondja meg, hogy valami változott, az API azt, hogy most milyen.

7. Egyeztetés az eseménycsatornával

Minden esemény 30 napig az eseménycsatornában is megmarad, függetlenül attól, hogy feliratkozott-e rá webhook. Ha a fogadód hosszabb ideig nem működött, mint amennyit az újrapróbálkozások lefednek, vagy ellenőrizni szeretnéd, hogy semmi nem maradt ki, olvasd be a csatornát egy admin szerepkörű és webhooks:manage hatókörű kulccsal:

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"

A csatornában lévő esemény id értéke és szerkezete ugyanaz, mint a kézbesítésben, csak a sequence, a payload és a test kézbesítési mező hiányzik belőle. Mivel a fogadód kihagyja a már látott eseményeket, az egyeztetést olyan gyakran futtathatod, ahogy szeretnéd, például óránként.

A GET /v1/webhook-events/{event_id} egy eseményt olvas be az azonosítója alapján.

8. A titkos kulcs megújítása

A POST /v1/webhooks/{webhook_id}/rotate-secret új titkos kulcsot ad ki, az előzőt pedig egy átfedési ideig érvényben hagyja: alapértelmezés szerint 24 óráig, legfeljebb 7 napig. Az átfedési idő alatt minden kézbesítést mindkét titkos kulccsal aláír.

  1. Újítsd meg a titkos kulcsot, és tárold az újat a régi mellett a fogadódban.
  2. Fogadd el a kézbesítést, ha bármelyik titkos kulccsal sikeres az ellenőrzés.
  3. Az átfedési idő után távolítsd el a régi titkos kulcsot.

Ha a régi titkos kulcs kiszivárgott, ne várj, hanem azonnal zárd le az átfedési időt a POST /v1/webhooks/{webhook_id}/revoke-previous-secret végponttal, vagy újítsd meg a titkos kulcsot "overlap_hours": 0 értékkel.

Ellenőrzőlista

  • Ellenőrizd az egyik aláírást a nyers törzsön, és ellenőrizd az időbélyeget.
  • Tárold el az eseményt a webhook-id szerint, majd 10 másodpercen belül válaszolj 2xx kóddal.
  • Hagyd ki az ismétlődéseket és az elavult eseményeket; ha számít, olvasd ki az aktuális objektumot az API-n keresztül.
  • Válaszolj Retry-After fejléces 429 vagy 503 kóddal, ha azt szeretnéd, hogy a Sursa lassítson; soha ne válaszolj véletlenül 410 kóddal.
  • Hagyd ki az ismeretlen eseménytípusokat és mezőket.
  • Ütemezetten egyeztess az eseménycsatornával.