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}
| Otsake | Merkitys |
|---|---|
webhook-id | Tapahtuman tunnus. Sama jokaisessa uusintayrityksessä. Käytä sitä kaksoiskappaleiden hylkäämiseen. |
webhook-timestamp | Milloin tämä yritys allekirjoitettiin, Unix-sekunteina. |
webhook-signature | Allekirjoitus Standard Webhooks -muodossa. |
X-KB-Signature | Sama toimitus allekirjoitettuna Sursan aiemmassa muodossa. |
X-KB-Event | Tapahtumatyyppi. |
X-KB-Attempt | Yrityksen numero ykkösestä alkaen. |
X-KB-Sequence | Tä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.
| Muoto | Allekirjoitettu merkkijono | Avain | Allekirjoitus |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Salaisuuden whsec_-etuliitteen jälkeinen osa base64-dekoodattuna | v1, + 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' ' -f1Testaa 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öte | Arvo |
|---|---|
| Salaisuus | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp ja t | 1791194400 |
| Runko | {"id":"evt_0123456789abcdef0123456789abcdef","type":"card.verified","created_at":"2026-10-05T10:00:00Z","org_id":1,"data":{"card":{"id":"c1"}}} |
webhook-signature | v1,+xxWSXH6weQJ/MVg4+N78a1Dj4y/AQ4p94+PRNzpECg= |
X-KB-Signature | t=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- tai503-vastaus, jossa onRetry-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 rungonid) ja ohita tapahtuma, jonka olet jo nähnyt. - Ajattele tiloina. Kun saman objektin muutoksia tulee useita, vertaa
created_at- taisequence-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.
- Uusi salaisuus ja tallenna uusi arvo vastaanottajaasi vanhan rinnalle.
- Hyväksy toimitus, jos se läpäisee tarkistuksen jommallakummalla salaisuudella.
- 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 sitten2xx10 sekunnin kuluessa. - Ohita kaksoiskappaleet ja vanhat tapahtumat; lue nykyinen objekti API:n kautta, kun sillä on merkitystä.
- Vastaa
429tai503jaRetry-After, kun haluat Sursan hidastavan toimituksia; älä koskaan vastaa410vahingossa. - Ohita tapahtumatyypit ja kentät, joita et tunne.
- Täsmäytä tiedot tapahtumasyötteen avulla säännöllisesti.