Primiți webhookuri
Un webhook anunță sistemul dumneavoastră despre o modificare din Sursa în momentul în care se produce: o fișă este
publicată, verificată sau arhivată, o întrebare a rămas fără răspuns. Sursa trimite un POST HTTPS cu evenimentul la
o adresă pe care o înregistrați. Acest ghid construiește un receptor care funcționează corect la reîncercări,
schimbări de ordine și dubluri.
Pentru ce înseamnă și ce conține fiecare eveniment, vedeți prezentarea generală a webhookurilor și catalogul evenimentelor din referința API.
1. Înregistrați un webhook
Un administrator înregistrează webhookuri în Sursa, în Administration → Integrations → Webhooks and API
(administrare → integrări → webhookuri și API), sau cu POST /v1/webhooks și o
cheie cu rolul admin și domeniul 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"]
}'Răspunsul conține secretul de semnare în secret, care începe cu whsec_. Acesta este afișat o singură dată.
Stocați-l împreună cu receptorul, de exemplu în variabila de mediu SURSA_WEBHOOK_SECRET.
Adresa trebuie să folosească https și să se rezolve la adrese IP publice: adresele din rețele private, de loopback și
din alte rețele rezervate sunt respinse la înregistrare și la fiecare livrare. Pentru a dezvolta pe propriul
calculator, expuneți receptorul local printr-un serviciu de tunelare care îi oferă o adresă HTTPS publică.
Trimiteți oricând un eveniment de test cu POST /v1/webhooks/{webhook_id}/test.
Răspunsul arată ce a răspuns receptorul.
2. Cum arată o livrare
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}
| Antet | Semnificație |
|---|---|
webhook-id | Identificatorul evenimentului. Același la fiecare reîncercare. Folosiți-l pentru a elimina dublurile. |
webhook-timestamp | Momentul în care a fost semnată această încercare, în secunde Unix. |
webhook-signature | Semnătura în formatul Standard Webhooks. |
X-KB-Signature | Aceeași livrare, semnată în formatul anterior al Sursa. |
X-KB-Event | Tipul evenimentului. |
X-KB-Attempt | Numărul încercării, de la 1. |
X-KB-Sequence | Poziția acestei livrări în ordinea livrărilor webhookului. |
Numele antetelor nu țin cont de majuscule; framework-ul folosit le poate afișa ca X-Kb-Signature sau
x-kb-signature.
Corpul este evenimentul. data lui depinde de tip. Un webhook setat pe conținutul thin primește evenimentul fără
data; în acest caz, citiți obiectul prin API cu propria cheie.
3. Verificați semnătura
Verificați semnătura fiecărei livrări înainte de a avea încredere în ea. Ambele formate semnează octeții exacți ai corpului, așa că citiți corpul brut înainte de orice analiză JSON: o nouă serializare a JSON-ului schimbă octeții și strică verificarea.
| Format | Șirul semnat | Cheie | Semnătură |
|---|---|---|---|
Standard Webhooks (webhook-signature) | <webhook-id>.<webhook-timestamp>.<body> | Partea secretului de după whsec_, decodificată din base64 | v1, + base64 al HMAC-SHA256 |
Formatul anterior (X-KB-Signature) | <t>.<body> | Întregul șir al secretului, ca text | v1= + hex al HMAC-SHA256 |
Este suficient să verificați unul dintre ele. Preferați antetul Standard Webhooks: aceeași verificare există în multe
biblioteci gata făcute. În timpul reînnoirii secretului, fiecare antet conține câte o semnătură pentru fiecare secret
valid (separate prin spațiu în webhook-signature, cu v1= repetat în X-KB-Signature). O livrare este validă dacă
oricare dintre ele corespunde.
Respingeți și un marcaj de timp aflat la mai mult de 5 minute față de ceasul dumneavoastră. Astfel, nimeni nu poate retrimite mai târziu o livrare înregistrată.
# 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ți funcția de verificare
Rulați codul cu aceste valori înainte să îl conectați la livrări reale. Cu ceasul setat la marcajul de timp, ambele verificări trebuie să reușească și trebuie să eșueze când se schimbă un singur octet al corpului.
| Intrare | Valoare |
|---|---|
| Secret | whsec_test_secret_0123456789abcdef |
webhook-id | evt_0123456789abcdef0123456789abcdef |
webhook-timestamp și t | 1791194400 |
| Corp | {"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. Răspundeți rapid, lucrați după aceea
Sursa așteaptă răspunsul cel mult 10 secunde. Orice status 2xx înseamnă că livrarea a reușit; orice altceva,
inclusiv o redirecționare, o expirare sau o conexiune închisă, înseamnă că a eșuat și va fi reîncercată.
Așadar, receptorul trebuie doar să verifice, să înregistreze evenimentul și să răspundă. Faceți prelucrarea propriu-zisă după aceea, din propria coadă.
// 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);Răspundeți 2xx doar după ce evenimentul este stocat în siguranță. Dacă răspundeți mai întâi și apoi procesul se
oprește brusc, evenimentul este pierdut pentru dumneavoastră, pentru că Sursa l-a socotit deja livrat.
5. Reîncercări
O livrare eșuată este reîncercată cu pauze din ce în ce mai lungi: după aproximativ 1 minut, 5 minute, 30 de minute, 2 ore, 5 ore, 10 ore, 24 de ore și 24 de ore, adică nouă încercări în aproape trei zile. Fiecare pauză variază cu până la 20%, astfel încât reîncercările de la multe webhookuri să nu sosească în același moment.
- Un răspuns
429sau503cuRetry-Afteramână următoarea încercare cel puțin până la acel moment, dar nu mai mult de 24 de ore. Folosiți-l când sistemul este supraîncărcat sau în mentenanță. - Un răspuns
410 Gonedezactivează webhookul: trimiteți-l doar când nu mai vreți livrări la acea adresă. - După 10 încercări eșuate consecutive, webhookul este pus în pauză, iar administratorii organizației sunt
anunțați. Evenimentele continuă să se acumuleze și, când webhookul este reluat, în interfața de administrare sau cu
POST /v1/webhooks/{webhook_id}/resume, sunt livrate în ordine. - După ultima încercare, o livrare este marcată
exhausted. O puteți trimite din nou manual.
Livrările unui webhook, cu fiecare încercare, codul răspunsului și durata lui, sunt enumerate de
GET /v1/webhooks/{webhook_id}/deliveries și în interfața de
administrare. POST /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver
trimite din nou o livrare. O livrare nouă are același webhook-id.
6. Ordine și dubluri
- Ordine. Livrările către un webhook se fac strict una după alta, în ordinea
sequence. Cât timp o livrare este reîncercată, cele ulterioare așteaptă. Webhookurile diferite nu se așteaptă unele pe altele. - Dubluri. Același eveniment poate sosi de mai multe ori, de exemplu când răspunsul dumneavoastră s-a pierdut pe
drumul înapoi. Stocați
webhook-id(este șiiddin corp) al fiecărui eveniment prelucrat și ignorați un eveniment pe care l-ați văzut deja. - Gândiți în stări. Pentru modificările aceluiași obiect, comparați
created_atsausequencecu ce ați stocat și ignorați un eveniment mai vechi decât starea pe care o aveți deja. Când aveți îndoieli, citiți obiectul curent prin API: evenimentul vă spune că ceva s-a schimbat, API-ul vă spune cum este acum.
7. Reconcilierea cu fluxul de evenimente
Fiecare eveniment se păstrează și 30 de zile în fluxul de evenimente, indiferent dacă vreun webhook era abonat la
el sau nu. Dacă receptorul a fost oprit mai mult decât acoperă reîncercările sau vreți să verificați că nu s-a
pierdut nimic, citiți fluxul cu o cheie care are rolul admin și domeniul 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"Un eveniment din flux are același id și aceeași formă ca într-o livrare, fără câmpurile de livrare sequence,
payload și test. Pentru că receptorul ignoră evenimentele văzute deja, puteți rula reconcilierea cât de des
doriți, de exemplu o dată pe oră.
GET /v1/webhook-events/{event_id} citește un eveniment după
identificatorul lui.
8. Reînnoirea secretului
POST /v1/webhooks/{webhook_id}/rotate-secret emite un secret nou și
păstrează valabil secretul anterior pe o perioadă de suprapunere, implicit 24 de ore și cel mult 7 zile. În timpul
suprapunerii, fiecare livrare este semnată cu ambele secrete.
- Reînnoiți secretul și stocați-l pe cel nou lângă cel vechi în receptor.
- Acceptați o livrare dacă se verifică cu oricare dintre secrete.
- După suprapunere, eliminați secretul vechi.
Dacă secretul vechi a fost divulgat, încheiați imediat suprapunerea cu
POST /v1/webhooks/{webhook_id}/revoke-previous-secret sau
reînnoiți secretul cu "overlap_hours": 0.
Listă de verificare
- Verificați una dintre semnături pe corpul brut și verificați marcajul de timp.
- Stocați evenimentul după
webhook-id, apoi răspundeți2xxîn cel mult 10 secunde. - Ignorați dublurile și evenimentele învechite; citiți obiectul curent prin API când contează.
- Răspundeți
429sau503cuRetry-Aftercând aveți nevoie ca Sursa să încetinească; nu răspundeți niciodată410din greșeală. - Ignorați tipurile de evenimente și câmpurile necunoscute.
- Reconciliați periodic cu fluxul de evenimente.