Ta emot webhookar
En webhook berättar för ditt system om en ändring i Sursa när den sker: ett kort publiceras, verifieras eller
arkiveras, en fråga förblev obesvarad. Sursa skickar en HTTPS-POST med händelsen till en adress som du registrerar.
Den här guiden bygger en mottagare som fungerar korrekt trots nya försök, ändrad ordning och dubbletter.
Vad varje händelse betyder och innehåller står i översikten över webhookar och i händelsekatalogen i API-referensen.
1. Registrera en webhook
En administratör registrerar webhookar i Sursa under Administration → Integrations → Webhooks and API, eller med
POST /v1/webhooks och en nyckel med rollen admin och omfånget
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 innehåller signeringshemligheten i secret, som börjar med whsec_. Den visas en gång. Förvara den hos din
mottagare, till exempel i en miljövariabel SURSA_WEBHOOK_SECRET.
Adressen måste använda https och peka på offentliga IP-adresser: adresser i privata nätverk, loopback-nätverk och
andra reserverade nätverk avvisas både när du registrerar och vid varje leverans. För att utveckla på din egen dator
exponerar du din lokala mottagare via en tunneltjänst som ger den en offentlig HTTPS-adress.
Skicka en testhändelse när som helst med POST /v1/webhooks/{webhook_id}/test.
Svaret visar vad din mottagare svarade.
2. Hur en leverans ser 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}
| Huvud | Betydelse |
|---|---|
webhook-id | Händelsens id. Detsamma vid varje nytt försök. Använd det för att slänga dubbletter. |
webhook-timestamp | När det här försöket signerades, i Unix-sekunder. |
webhook-signature | Signaturen i formatet Standard Webhooks. |
X-KB-Signature | Samma leverans signerad i Sursas tidigare format. |
X-KB-Event | Händelsetypen. |
X-KB-Attempt | Försökets nummer, från 1. |
X-KB-Sequence | Den här leveransens position i webhookens ordning. |
Huvudenas namn är skiftlägesokänsliga; ditt ramverk kan visa dem som X-Kb-Signature eller x-kb-signature.
Brödtexten är händelsen. Dess data beror på typen. En webhook med nyttolastläget thin tar emot händelsen utan
data; läs i stället objektet via API:et med din egen nyckel.
3. Verifiera signaturen
Kontrollera signaturen för varje leverans innan du litar på den. Båda formaten signerar brödtextens exakta byte, så läs den råa brödtexten före all JSON-tolkning: om du serialiserar om JSON-koden ändras byten och kontrollen misslyckas.
| Format | Signerad sträng | Nyckel | Signatur |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Delen av hemligheten efter whsec_, base64-avkodad | v1, + base64 av HMAC-SHA256 |
Tidigare format (X-KB-Signature) | <t>.<body> | Hela hemlighetens sträng, som text | v1= + hex av HMAC-SHA256 |
Det räcker att verifiera en av dem. Använd hellre huvudet för Standard Webhooks: samma kontroll finns i många färdiga
bibliotek. Under en rotation av hemligheten bär varje huvud en signatur per giltig hemlighet (åtskilda med mellanslag
i webhook-signature, upprepat v1= i X-KB-Signature). En leverans är giltig om någon av dem stämmer.
Avvisa också en tidsstämpel som avviker mer än 5 minuter från din klocka. Det hindrar någon från att spela upp en inspelad leverans igen senare.
# 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' ' -f1Testa din verifierare
Kör din kod mot de här värdena innan du kopplar den till riktiga leveranser. Med klockan satt till tidsstämpeln måste båda kontrollerna godkännas, och de måste misslyckas när en byte i brödtexten ändras.
| Indata | Värde |
|---|---|
| Hemlighet | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp och t | 1791194400 |
| Brödtext | {"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. Svara snabbt, arbeta senare
Sursa väntar upp till 10 sekunder på ditt svar. Varje 2xx-status betyder att leveransen lyckades; allt annat,
även en omdirigering, en tidsgräns som löper ut eller en stängd anslutning, betyder att den misslyckades och kommer att
göras om.
Mottagaren ska alltså bara verifiera, registrera händelsen och svara. Gör det egentliga arbetet efteråt, från 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);Svara 2xx först när händelsen är säkert lagrad. Om du svarar först och sedan kraschar går händelsen förlorad för dig,
eftersom Sursa redan har räknat den som levererad.
5. Nya försök
En misslyckad leverans görs om med allt längre pauser: efter ungefär 1 minut, 5 minuter, 30 minuter, 2 timmar, 5 timmar, 10 timmar, 24 timmar och 24 timmar, nio försök under nästan tre dygn. Varje paus varierar med upp till 20 % så att nya försök från många webhookar inte kommer samtidigt.
- Ett svar
429eller503medRetry-Afterflyttar nästa försök till minst den tiden, upp till 24 timmar senare. Använd det när ditt system är överbelastat eller under underhåll. - Ett svar
410 Goneinaktiverar webhooken: skicka det bara när du inte vill ha fler leveranser till den adressen. - Efter 10 misslyckade försök i rad pausas webhooken, och organisationens administratörer får besked. Händelserna
fortsätter att samlas, och när webhooken återupptas, i administrationsgränssnittet eller med
POST /v1/webhooks/{webhook_id}/resume, levereras de i ordning. - Efter det sista försöket markeras en leverans som
exhausted. Du kan skicka den igen för hand.
En webhooks leveranser, med varje försök, din svarskod och dess tidsåtgång, listas av
GET /v1/webhooks/{webhook_id}/deliveries och i
administrationsgränssnittet. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
skickar en leverans igen. En ny leverans har samma webhook-id.
6. Ordning och dubbletter
- Ordning. Leveranser till en webhook skickas strikt en efter en i
sequence-ordning. Medan en leverans görs om väntar de senare. Olika webhookar väntar inte på varandra. - Dubbletter. Samma händelse kan komma mer än en gång, till exempel när ditt svar gick förlorat på vägen tillbaka.
Spara
webhook-id(det är ocksåidi brödtexten) för varje händelse som du bearbetar och hoppa över en som du redan har sett. - Tänk i tillstånd. Vid ändringar av samma objekt jämför du
created_atellersequencemed det som du har lagrat och ignorerar en händelse som är äldre än det tillstånd som du redan har. Är du osäker läser du det aktuella objektet via API:et: händelsen berättar att något har ändrats, API:et berättar hur det ser ut nu.
7. Stäm av mot händelseflödet
Varje händelse sparas också i 30 dagar i händelseflödet, oavsett om någon webhook prenumererade på den eller inte.
Om din mottagare var nere längre än de nya försöken täcker, eller om du vill kontrollera att inget har missats, läser
du flödet med en nyckel som har rollen admin och omfånget 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 händelse i flödet har samma id och form som i en leverans, utan leveransfälten sequence, payload och test.
Eftersom din mottagare hoppar över händelser som den redan har sett kan du köra avstämningen så ofta du vill, till
exempel en gång i timmen.
GET /v1/webhook-events/{event_id} läser en händelse utifrån dess id.
8. Rotera hemligheten
POST /v1/webhooks/{webhook_id}/rotate-secret utfärdar en ny hemlighet
och låter den föregående gälla under en överlappningsperiod, 24 timmar som standard och upp till 7 dagar. Under
överlappningen signeras varje leverans med båda hemligheterna.
- Rotera och spara den nya hemligheten bredvid den gamla i din mottagare.
- Godta en leverans om den kan verifieras med någon av hemligheterna.
- Ta bort den gamla hemligheten efter överlappningen.
Om den gamla hemligheten har läckt ut avslutar du överlappningen genast med
POST /v1/webhooks/{webhook_id}/revoke-previous-secret, eller
roterar med "overlap_hours": 0.
Checklista
- Verifiera en av signaturerna på den råa brödtexten och kontrollera tidsstämpeln.
- Lagra händelsen med
webhook-idsom nyckel och svara sedan2xxinom 10 sekunder. - Hoppa över dubbletter och inaktuella händelser; läs det aktuella objektet via API:et när det spelar roll.
- Svara
429eller503medRetry-Afternär du behöver att Sursa backar; svara aldrig410av misstag. - Hoppa över händelsetyper och fält som du inte känner till.
- Stäm av mot händelseflödet enligt ett schema.