Siirry sisältöön
Sursa Developers

Webhookien vastaanottaminen

Webhook kertoo järjestelmällesi Sursassa tapahtuneesta muutoksesta heti, kun se tapahtuu: kortti julkaistaan, vahvistetaan tai arkistoidaan, kysymys jäi vaille vastausta. Sursa lähettää tapahtuman POST-pyynnöllä HTTPS:n kautta osoitteeseen, jonka rekisteröit. Tässä oppaassa rakennetaan vastaanottaja, joka toimii oikein uusintayritysten, järjestyksen muutosten ja kaksoiskappaleiden kanssa.

Mitä kukin tapahtuma tarkoittaa ja sisältää, kerrotaan webhookien yleiskatsauksessa ja API-viitteen tapahtumaluettelossa.

1. Rekisteröi webhook

Ylläpitäjä rekisteröi webhookit Sursassa kohdassa Administration → Integrations → Webhooks and API (ylläpito → integraatiot → webhookit ja API) tai POST /v1/webhooks -pyynnöllä avaimella, jolla on rooli admin ja käyttöalue 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"]
  }'

Vastauksen secret-kentässä on allekirjoitussalaisuus, jonka alussa on whsec_. Se näytetään kerran. Tallenna se vastaanottajasi käyttöön, esimerkiksi ympäristömuuttujaan SURSA_WEBHOOK_SECRET.

Osoitteen täytyy käyttää https-protokollaa ja osoittaa julkisiin IP-osoitteisiin: yksityisten, loopback- ja muiden varattujen verkkojen osoitteet hylätään sekä rekisteröinnissä että jokaisessa toimituksessa. Jos kehität omalla tietokoneellasi, julkaise paikallinen vastaanottajasi tunnelointipalvelun kautta, joka antaa sille julkisen HTTPS-osoitteen.

Lähetä testitapahtuma milloin tahansa POST /v1/webhooks/{webhook_id}/test -pyynnöllä. Vastaus näyttää, mitä vastaanottajasi vastasi.

2. Millainen toimitus on

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}
OtsakeMerkitys
webhook-idTapahtuman tunnus. Sama jokaisessa uusintayrityksessä. Käytä sitä kaksoiskappaleiden hylkäämiseen.
webhook-timestampMilloin tämä yritys allekirjoitettiin, Unix-sekunteina.
webhook-signatureAllekirjoitus Standard Webhooks -muodossa.
X-KB-SignatureSama toimitus allekirjoitettuna Sursan aiemmassa muodossa.
X-KB-EventTapahtumatyyppi.
X-KB-AttemptYrityksen numero ykkösestä alkaen.
X-KB-SequenceTämän toimituksen sijainti webhookin toimitusjärjestyksessä.

Otsakkeiden nimissä kirjainkoolla ei ole merkitystä; sovelluskehyksesi voi näyttää ne muodossa X-Kb-Signature tai x-kb-signature.

Runko on itse tapahtuma. Sen data riippuu tyypistä. Webhook, jonka hyötykuorman tilaksi on asetettu thin, saa tapahtuman ilman data-kenttää; lue objekti sen sijaan API:n kautta omalla avaimellasi.

3. Tarkista allekirjoitus

Tarkista jokaisen toimituksen allekirjoitus ennen kuin luotat siihen. Molemmat muodot allekirjoittavat rungon tarkat tavut, joten lue raakarunko ennen JSON-jäsennystä: JSON-tiedon sarjallistaminen uudelleen muuttaa tavuja ja rikkoo tarkistuksen.

MuotoAllekirjoitettu merkkijonoAvainAllekirjoitus
Standard Webhooks (webhook-signature)<webhook-id>.<webhook-timestamp>.<body>Salaisuuden whsec_-etuliitteen jälkeinen osa base64-dekoodattunav1, + HMAC-SHA256:n base64-esitys
Aiempi muoto (X-KB-Signature)<t>.<body>Koko salaisuuden merkkijono tekstinäv1= + HMAC-SHA256:n heksadesimaaliesitys

Kumman tahansa tarkistaminen riittää. Suosi Standard Webhooks -otsaketta: sama tarkistus on saatavilla monissa valmiissa kirjastoissa. Salaisuuden uusimisen aikana kussakin otsakkeessa on yksi allekirjoitus kutakin voimassa olevaa salaisuutta kohden (webhook-signature-otsakkeessa välilyönnein eroteltuina, X-KB-Signature-otsakkeessa toistuvina v1=-kohtina). Toimitus on kelvollinen, jos mikä tahansa niistä täsmää.

Hylkää myös aikaleima, joka poikkeaa kellostasi yli 5 minuuttia. Näin kukaan ei voi toistaa tallennettua toimitusta myöhemmin.

# 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

Testaa allekirjoituksen tarkistuskoodi

Aja koodisi näillä arvoilla ennen kuin kytket sen todellisiin toimituksiin. Kun kello on asetettu aikaleiman mukaiseksi, molempien tarkistusten täytyy mennä läpi, ja niiden täytyy epäonnistua, kun rungosta muuttuu yksikin tavu.

SyöteArvo
Salaisuuswhsec_test_secret_0123456789abcdef
webhook-idevt_0123456789abcdef0123456789abcdef
webhook-timestamp ja t1791194400
Runko{"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. Vastaa nopeasti, tee työ myöhemmin

Sursa odottaa vastaustasi enintään 10 sekuntia. Mikä tahansa 2xx-tila tarkoittaa, että toimitus onnistui; mikä tahansa muu, myös uudelleenohjaus, aikakatkaisu tai suljettu yhteys, tarkoittaa, että toimitus epäonnistui ja sitä yritetään uudelleen.

Siksi vastaanottajan kannattaa vain tarkistaa allekirjoitus, tallentaa tapahtuma ja vastata. Tee varsinainen työ jälkeenpäin omasta jonostasi.

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

Vastaa 2xx vasta, kun tapahtuma on tallennettu turvallisesti. Jos vastaat ensin ja ohjelmasi kaatuu sen jälkeen, tapahtuma katoaa sinulta, koska Sursa on jo laskenut sen toimitetuksi.

5. Uusintayritykset

Epäonnistunutta toimitusta yritetään uudelleen yhä pidemmin tauoin: noin 1 minuutin, 5 minuutin, 30 minuutin, 2 tunnin, 5 tunnin, 10 tunnin, 24 tunnin ja 24 tunnin jälkeen, eli yhdeksän yritystä lähes kolmen päivän aikana. Kukin tauko vaihtelee enintään 20 %, jotta monien webhookien uusintayritykset eivät saavu samalla hetkellä.

  • 429- tai 503-vastaus, jossa on Retry-After, siirtää seuraavan yrityksen vähintään tämän ajan päähän, kuitenkin enintään 24 tuntia myöhemmäksi. Käytä sitä, kun järjestelmäsi on ylikuormittunut tai huoltotilassa.
  • 410 Gone -vastaus poistaa webhookin käytöstä: lähetä se vain silloin, kun et halua tähän osoitteeseen enää toimituksia.
  • 10 peräkkäisen epäonnistuneen yrityksen jälkeen webhook keskeytetään, ja organisaation ylläpitäjille ilmoitetaan siitä. Tapahtumia kertyy edelleen, ja kun webhookin toimintaa jatketaan ylläpitonäkymässä tai POST /v1/webhooks/{webhook_id}/resume -pyynnöllä, ne toimitetaan järjestyksessä.
  • Viimeisen yrityksen jälkeen toimitus merkitään tilaan exhausted. Voit lähettää sen uudelleen käsin.

Webhookin toimitukset jokaisine yrityksineen, vastauskoodeineen ja ajoituksineen näkyvät GET /v1/webhooks/{webhook_id}/deliveries -listassa ja ylläpitonäkymässä. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver lähettää toimituksen uudelleen. Uudelleentoimituksella on sama webhook-id.

6. Järjestys ja kaksoiskappaleet

  • Järjestys. Saman webhookin toimitukset lähetetään tiukasti peräkkäin sequence-järjestyksessä. Kun yhtä toimitusta yritetään uudelleen, myöhemmät odottavat. Eri webhookit eivät odota toisiaan.
  • Kaksoiskappaleet. Sama tapahtuma voi saapua useammin kuin kerran, esimerkiksi kun vastauksesi katosi matkalla takaisin. Tallenna jokaisen käsittelemäsi tapahtuman webhook-id (se on myös rungon id) ja ohita tapahtuma, jonka olet jo nähnyt.
  • Ajattele tiloina. Kun saman objektin muutoksia tulee useita, vertaa created_at- tai sequence-arvoa tallentamaasi ja ohita tapahtuma, joka on vanhempi kuin jo tallentamasi tila. Jos olet epävarma, lue objektin nykyinen tila API:n kautta: tapahtuma kertoo, että jokin muuttui, API kertoo, millainen se on nyt.

7. Täsmäytä tapahtumasyötteen avulla

Jokainen tapahtuma säilyy myös 30 päivää tapahtumasyötteessä riippumatta siitä, onko mikään webhook tilannut sen. Jos vastaanottajasi oli poissa käytöstä pidempään kuin uusintayritykset kattavat tai haluat tarkistaa, ettei mitään jäänyt väliin, lue syöte avaimella, jolla on rooli admin ja käyttöalue 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"

Syötteen tapahtumalla on sama id ja rakenne kuin toimituksessa, mutta siitä puuttuvat toimituskentät sequence, payload ja test. Koska vastaanottajasi ohittaa jo näkemänsä tapahtumat, voit ajaa täsmäytyksen niin usein kuin haluat, esimerkiksi kerran tunnissa.

GET /v1/webhook-events/{event_id} lukee yhden tapahtuman sen tunnuksella.

8. Uusi salaisuus

POST /v1/webhooks/{webhook_id}/rotate-secret myöntää uuden salaisuuden ja pitää edellisen voimassa päällekkäisyysjakson ajan: oletuksena 24 tuntia ja enintään 7 päivää. Päällekkäisyysjakson aikana jokainen toimitus allekirjoitetaan molemmilla salaisuuksilla.

  1. Uusi salaisuus ja tallenna uusi arvo vastaanottajaasi vanhan rinnalle.
  2. Hyväksy toimitus, jos se läpäisee tarkistuksen jommallakummalla salaisuudella.
  3. Poista vanha salaisuus päällekkäisyysjakson jälkeen.

Jos vanha salaisuus on vuotanut, lopeta päällekkäisyysjakso heti pyynnöllä POST /v1/webhooks/{webhook_id}/revoke-previous-secret tai uusi salaisuus asetuksella "overlap_hours": 0.

Tarkistuslista

  • Tarkista toinen allekirjoituksista raakarungosta ja tarkista aikaleima.
  • Tallenna tapahtuma webhook-id-avaimella ja vastaa sitten 2xx 10 sekunnin kuluessa.
  • Ohita kaksoiskappaleet ja vanhat tapahtumat; lue nykyinen objekti API:n kautta, kun sillä on merkitystä.
  • Vastaa 429 tai 503 ja Retry-After, kun haluat Sursan hidastavan toimituksia; älä koskaan vastaa 410 vahingossa.
  • Ohita tapahtumatyypit ja kentät, joita et tunne.
  • Täsmäytä tiedot tapahtumasyötteen avulla säännöllisesti.