Motta webhooks
En webhook forteller systemet ditt om en endring i Sursa idet den skjer: Et kort publiseres, verifiseres eller
arkiveres, et spørsmål ble stående ubesvart. Sursa sender en HTTPS-POST med hendelsen til en adresse du registrerer.
Denne veiledningen bygger en mottaker som fungerer riktig ved nye forsøk, endret rekkefølge og duplikater.
Hva hver hendelse betyr og inneholder, finner du i oversikten over webhooks og i hendelseskatalogen i API-referansen.
1. Registrer et endepunkt
En administrator registrerer endepunkter i Sursa under Administration → Integrations → Webhooks and API
(administrasjon, integrasjoner, webhooks og API) eller med POST /v1/webhooks
og en nøkkel med rollen admin og scopet 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"]
}'Svaret inneholder signeringshemmeligheten i secret, som begynner med whsec_. Den vises én gang. Lagre den
sammen med mottakeren din, for eksempel i miljøvariabelen SURSA_WEBHOOK_SECRET.
Adressen må bruke https og peke til offentlige IP-adresser: Adresser i private nettverk, loopback-nettverk og andre
reserverte nettverk avvises ved registreringen og ved hver leveranse. For å utvikle på din egen datamaskin gjør du den
lokale mottakeren tilgjengelig gjennom en tunneltjeneste som gir den en offentlig HTTPS-adresse.
Send en testhendelse når som helst med POST /v1/webhooks/{webhook_id}/test.
Svaret viser hva mottakeren din svarte.
2. Slik ser en leveranse ut
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}
| Header | Betydning |
|---|---|
webhook-id | ID-en til hendelsen. Den samme ved hvert nytt forsøk. Bruk den til å forkaste duplikater. |
webhook-timestamp | Når dette forsøket ble signert, i Unix-sekunder. |
webhook-signature | Signaturen i formatet Standard Webhooks. |
X-KB-Signature | Den samme leveransen signert i Sursas tidligere format. |
X-KB-Event | Hendelsestypen. |
X-KB-Attempt | Nummeret på forsøket, fra 1. |
X-KB-Sequence | Plasseringen til denne leveransen i endepunktets rekkefølge. |
Navn på headere skiller ikke mellom store og små bokstaver; rammeverket ditt kan vise dem som X-Kb-Signature eller
x-kb-signature.
Brødteksten er hendelsen. Innholdet i data avhenger av typen. Et endepunkt som er satt til nyttelasten thin,
mottar hendelsen uten data; les i stedet objektet gjennom API-et med din egen nøkkel.
3. Valider signaturen
Kontroller signaturen på hver leveranse før du stoler på den. Begge formatene signerer de nøyaktige bytene i brødteksten, så les den rå brødteksten før noen JSON-tolking: Hvis du serialiserer JSON-en på nytt, endres bytene, og kontrollen feiler.
| Format | Signert streng | Nøkkel | Signatur |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Delen av hemmeligheten etter whsec_, base64-dekodet | v1, + base64 av HMAC-SHA256 |
Tidligere format (X-KB-Signature) | <t>.<body> | Hele hemmeligheten som tekst | v1= + hex av HMAC-SHA256 |
Det er nok å validere én av dem. Foretrekk headeren for Standard Webhooks: Den samme kontrollen finnes i mange
ferdige biblioteker. Under en rotasjon av hemmeligheten har hver header én signatur per gyldig hemmelighet (atskilt
med mellomrom i webhook-signature, gjentatt v1= i X-KB-Signature). En leveranse er gyldig hvis én av dem
stemmer.
Avvis også et tidsstempel som avviker mer enn 5 minutter fra klokken din. Det hindrer at noen spiller av en registrert leveranse på nytt senere.
# 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' ' -f1Test signaturvalideringen din
Kjør koden din mot disse verdiene før du kobler den til ekte leveranser. Med klokken satt til tidsstempelet må begge kontrollene lykkes, og de må feile når én byte i brødteksten endres.
| Inndata | Verdi |
|---|---|
| Hemmelighet | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp og t | 1791194400 |
| Brødtekst | {"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. Svar raskt, jobb etterpå
Sursa venter opptil 10 sekunder på svaret ditt. Enhver 2xx-status betyr at leveransen lyktes; alt annet, også en
omdirigering, et tidsavbrudd eller en lukket tilkobling, betyr at den mislyktes og vil bli prøvd på nytt.
Mottakeren skal derfor bare validere, registrere hendelsen og svare. Gjør det egentlige arbeidet etterpå, fra din egen kø.
// 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);Svar 2xx først når hendelsen er trygt lagret. Hvis du svarer først og så krasjer, er hendelsen tapt for deg, fordi
Sursa allerede har regnet den som levert.
5. Nye forsøk
En mislykket leveranse prøves på nytt med stadig lengre pauser: etter omtrent 1 minutt, 5 minutter, 30 minutter, 2 timer, 5 timer, 10 timer, 24 timer og 24 timer, ni forsøk over nesten tre døgn. Hver pause varierer med opptil 20 %, slik at nye forsøk fra mange endepunkter ikke kommer i samme øyeblikk.
- Et
429- eller503-svar medRetry-Afterflytter neste forsøk til minst det tidspunktet, høyst 24 timer senere. Bruk det når systemet ditt er overbelastet eller under vedlikehold. - Et
410 Gone-svar deaktiverer endepunktet: Send det bare når du ikke vil ha flere leveranser til den adressen. - Etter 10 mislykkede forsøk på rad settes endepunktet på pause, og organisasjonens administratorer får beskjed.
Hendelsene fortsetter å samle seg opp, og når endepunktet gjenopptas, i administrasjonsgrensesnittet eller med
POST /v1/webhooks/{webhook_id}/resume, leveres de i rekkefølge. - Etter det siste forsøket merkes en leveranse som
exhausted. Du kan sende den på nytt manuelt.
Leveransene til et endepunkt, med hvert forsøk, svarkoden din og tidsbruken, vises av
GET /v1/webhooks/{webhook_id}/deliveries og i
administrasjonsgrensesnittet. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
sender én av dem på nytt. En ny leveranse har den samme webhook-id.
6. Rekkefølge og duplikater
- Rekkefølge. Leveranser til samme endepunkt går strengt etter hverandre i rekkefølgen til
sequence. Mens én leveranse prøves på nytt, venter de senere. Ulike endepunkter venter ikke på hverandre. - Duplikater. Den samme hendelsen kan komme mer enn én gang, for eksempel når svaret ditt gikk tapt på veien
tilbake. Lagre
webhook-id(det er ogsåidi brødteksten) for hver hendelse du behandler, og hopp over en du allerede har sett. - Tenk i tilstander. For endringer i samme objekt sammenligner du
created_atellersequencemed det du har lagret, og ignorerer en hendelse som er eldre enn tilstanden du allerede har. Er du i tvil, leser du det nåværende objektet gjennom API-et: Hendelsen forteller deg at noe er endret, API-et forteller deg hvordan det er nå.
7. Avstem med hendelsesfeeden
Hver hendelse lagres også i 30 dager i hendelsesfeeden, uansett om et endepunkt abonnerte på den eller ikke. Hvis
mottakeren din var nede lenger enn de nye forsøkene dekker, eller du vil kontrollere at ingenting gikk tapt, leser du
feeden med en nøkkel som har rollen admin og scopet 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"En hendelse i feeden har samme id og form som i en leveranse, uten leveringsfeltene sequence, payload og test.
Fordi mottakeren din hopper over hendelser den allerede har sett, kan du kjøre avstemmingen så ofte du vil, for
eksempel én gang i timen.
GET /v1/webhook-events/{event_id} leser én hendelse etter ID-en.
8. Roter hemmeligheten
POST /v1/webhooks/{webhook_id}/rotate-secret utsteder en ny
hemmelighet og lar den forrige være gyldig i en overlappsperiode, 24 timer som standard og opptil 7 dager. Under
overlappet signeres hver leveranse med begge hemmelighetene.
- Roter, og lagre den nye hemmeligheten ved siden av den gamle i mottakeren din.
- Godta en leveranse hvis den kan valideres med en av hemmelighetene.
- Fjern den gamle hemmeligheten etter overlappet.
Hvis den gamle hemmeligheten har lekket, avslutter du overlappet med en gang med
POST /v1/webhooks/{webhook_id}/revoke-previous-secret, eller
du roterer med "overlap_hours": 0.
Sjekkliste
- Valider én av signaturene på den rå brødteksten, og kontroller tidsstempelet.
- Lagre hendelsen med
webhook-idsom nøkkel, og svar deretter2xxinnen 10 sekunder. - Hopp over duplikater og utdaterte hendelser; les det nåværende objektet gjennom API-et når det har betydning.
- Svar
429eller503medRetry-Afternår Sursa skal holde igjen; svar aldri410ved et uhell. - Hopp over hendelsestyper og felt du ikke kjenner.
- Avstem med hendelsesfeeden etter en fast plan.