Webhooks
Modtag underskrevne begivenhedsnotifikationer, når dokumenter bliver indekseret, leveringer mislykkes, eller endpoints ændrer tilstand. Verifikation, genforsøg og test.
Webhooks sender begivenhedsnotifikationer til dine systemer som HTTPS POST-anmodninger, så du kan reagere på ændringer uden polling. Registrer et endpoint fra dine workspace-indstillinger: URL'en skal bruge HTTPS, og hvert workspace kan registrere op til 10 endpoints. Ved oprettelse modtager du en signeringshemmelighed (præfikset whsec_, Standard Webhooks-formatet) nøjagtigt én gang; den kan aldrig hentes igen bagefter, så kopier den straks til din hemmelige lagring.
Hver levering er en JSON-krop med den samme konvolut:
{
"event": "documents.indexed",
"timestamp": "2026-07-23T09:14:07.000Z",
"tenantId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"sequence": 42,
"data": { "count": 12 }
}Payloads er bevidst tynde: kun id'er, tællinger og tidsstempler. Behandl en webhook som et signal om, at noget er ændret, og hent derefter den aktuelle tilstand via den autentificerede API. Dokumentnavne og indhold vises aldrig i en webhook-payload.
sequence er en per-endpoint-tæller, tæt for denne endpoints egen strøm: på hinanden følgende begivenheder leveret til dette endpoint har på hinanden følgende numre, så et manglende nummer betyder en levering, din tjeneste aldrig modtog. LeveringsRÆKKEFØLGE er ikke garanteret, så brug sekvensen (ikke ankomsttidspunktet) til at sortere begivenheder. To advarsler: genafspilninger ankommer bevidst uden for sekvensrækkefølge (en indhentet begivenhed beholder sit oprindelige, ældre sekvensnummer), så fjern dubletter på webhook-id og kassér aldrig en levering bare fordi dens sekvens er under din high-water mark; og et hul kan også betyde, at den manglende begivenhed stadig forsøger igen eller venter i en genafspilbar tilstand, så behandl huller som "tjek leveringsloggen", ikke som bevis på tab.
Begivenhedskatalog
Abonner et endpoint på enhver kombination af begivenhedstyperne nedenfor. Typer markeret planlagt kan allerede vælges, men der udløses endnu ikke noget for dem, og deres payload-form er ikke endelig; den offentliggøres her, når begivenheden går live.
| Begivenhed | Status | Payload (data) |
|---|---|---|
documents.indexed | Live | Dokumenter blev søgbare. Aggregeret per workspace: højst én begivenhed per aggregeringsvindue, med en tælling. |
documents.failed | Live | Dokumenter fejlede terminalt i behandlingen. Aggregeret per workspace: højst én begivenhed per aggregeringsvindue, med en tælling. |
sync.completed | Planlagt | En connectorsynkronisering blev afsluttet. Payload-form er ikke endelig. |
sync.failed | Planlagt | En connectorsynkronisering fejlede. Payload-form er ikke endelig. |
ingestion.completed | Planlagt | En indtagelseskørsel blev afsluttet. Payload-form er ikke endelig. |
ingestion.failed | Planlagt | En indtagelseskørsel fejlede. Payload-form er ikke endelig. |
audit.recorded | Live | Workspacets revisionsspor fik nye poster. Aggregeret per workspace over et vindue: en tælling og vinduet til at hente, aldrig selve posterne. |
endpoint.disabled | Live | Et webhook-endpoint blev deaktiveret: automatisk efter vedvarende leveringsfejl eller en 410 Gone, eller af en admin. Leveret til workspace'ets andre endpoints. |
delivery.failing | Live | Leveringer til et webhook-endpoint fejler: udløses én gang per fejlvindue, 30 minutter efter dets første mislykkede forsøg, og leveret til workspace'ets andre endpoints. |
endpoint.enabled | Live | Et deaktiveret webhook-endpoint blev tændt igen. Lukker hændelsen endpoint.disabled åbnede; leveret til workspace'ets andre endpoints. |
delivery.recovered | Live | En levering lykkedes på et endpoint, som delivery.failing blev udløst for. Lukker den hændelse; leveret til workspace'ets andre endpoints. |
webhook.ping | Live | Målrettet testbegivenhed sendt af dashboardets send-test-handling til præcis ét endpoint. Aldrig abonnerbar; data er altid tom. |
Eksempel data-objekter for de live-begivenheder:
// documents.indexed
{ "count": 12 }
// documents.failed
{ "count": 2 }
// audit.recorded
{
"count": 34,
"windowStart": "2026-07-23T09:05:00.000Z",
"windowEnd": "2026-07-23T09:10:00.000Z"
}
// endpoint.disabled
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"reason": "sustained_failure",
"disabledAt": "2026-07-23T09:14:07.000Z"
}
// delivery.failing
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"failingSince": "2026-07-23T09:14:07.000Z"
}
// endpoint.enabled
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"enabledAt": "2026-07-24T08:02:51.000Z"
}
// delivery.recovered
{
"endpointId": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
"failingSince": "2026-07-23T09:14:07.000Z",
"recoveredAt": "2026-07-23T11:40:12.000Z"
}
// webhook.ping
{}De operationelle begivenheder (delivery.failing, delivery.recovered, endpoint.disabled, endpoint.enabled) rapporterer om sundheden af andre endpoints: det endpoint, en begivenhed handler om, er altid udelukket fra den pågældende begivenheds levering, da levering der ville være garanteret støj.
Hver live-begivenhed er også beskrevet i OpenAPI-dokumentet på /api/openapi.json, under webhooks: det fulde JSON Schema for kroppen og de tre signaturheaders, så du kan generere modtager-typer i stedet for at kopiere eksemplerne ovenfor. Skemaet håndhæves også på vores side: en payload, der ikke matcher det, sendes aldrig.
Føde et revisionsspor til et SIEM
audit.recorded er push-delen af en SIEM-føde, og den er bevidst ikke posterne: en revisionspost navngiver en person, deres adresse og hvad de rørte ved, og en webhook-payload gemmes i leveringsloggen og sendes til en URL, du kontrollerer, som kan løses hvor som helst. Begivenheden fortæller dig, at et vindue lukkede med poster i det; du henter posterne via den autentificerede API med en nøgle, der bærer audit:read-scopet.
Ved hver begivenhed søger du i sporet med windowStart som startDate og windowEnd som endDate, og bladrer på cursoren, indtil den kommer tilbage som null. Søgningens startgrænse er inklusiv, og vinduets er det ikke, så en side kan gentage posten på præcis det tidspunkt; fjern dubletter på post-id, som du alligevel har brug for, fordi levering er mindst-én-gang.
Katalogendepunktet lister hver handling, sporet kan registrere, med den kategori, hver tilhører, og en version, der ændres, når ordforrådet gør. Læs det én gang, sammenlign versionen ved senere læsninger, og behandl en handling, der mangler fra det, som ukendt snarere end ugyldig: sporet er kun-append og beholder stavemåden, hver post blev skrevet med.
Vinduer er sammenhængende, så længe du forbliver abonneret: hvert starter, hvor den sidste begivenhed, du modtog, sluttede, så en forsinket gennemgang gør det næste vindue bredere i stedet for at miste, hvad der skete imellem. Et vindue uden poster sender intet.
Verificering af leveringer
Hver levering er signeret efter Standard Webhooks-specifikationen. Verificér altid signaturen, før du stoler på en anmodning: din endpoint-URL er tilgængelig for alle på internettet.
Hver anmodning indeholder tre headers:
| Header | Værdi |
|---|---|
webhook-id | Besked-id. Identisk ved hvert genforsøg, gensendelse og genafspilning af den samme begivenhed; fjern dubletter på det. |
webhook-timestamp | Unix-tidsstempel i sekunder ved afsendelsestidspunkt. |
webhook-signature | En eller flere signaturer, adskilt af mellemrum, hver på formen v1,{base64}. |
Sådan verificerer du:
- Rekonstruer det signerede indhold som
{webhook-id}.{webhook-timestamp}.{raw body}. Brug de rå anmodningskropsbytes nøjagtigt som modtaget, før nogen JSON-parsing. - Beregn HMAC-SHA256 over den streng. Nøglen er den base64-afkodede del af hemmeligheden efter
whsec_-præfikset (Standard Webhooks-nøgleafledning, som referencebibliotekerne implementerer). - Base64-kod HMAC og sammenlign den med hver
v1,-signatur i headeren ved hjælp af en konstant-tids-sammenligning. Leveringen er autentisk, hvis én af dem matcher. - Afvis leveringer, hvis
webhook-timestamper mere end 5 minutter fra dit aktuelle tidspunkt, i begge retninger. Dette begrænser genafspilning af optagne anmodninger.
Headeren kan indeholde mere end én signatur: mens et hemmelighedsrotations-nådevindue er åbent, signeres leveringer med både den nye og den tidligere hemmelighed (v1,NEW_SIG v1,OLD_SIG), så verifikation fortsætter med at lykkes, uanset hvilken hemmelighed din tjeneste har rullet til.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 5 * 60;
/**
* @param {string} secret - The whsec_ signing secret from endpoint creation.
* @param {Record<string, string>} headers - Lowercased request headers.
* @param {string} rawBody - The raw request body, unparsed.
*/
export function verifyWebhook(secret, headers, rawBody) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatureHeader = headers["webhook-signature"];
if (!id || !timestamp || !signatureHeader) return false;
const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(skew) || skew > TOLERANCE_SECONDS) return false;
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = createHmac("sha256", key)
.update(`${id}.${timestamp}.${rawBody}`)
.digest();
return signatureHeader.split(" ").some((entry) => {
const comma = entry.indexOf(",");
if (comma === -1 || entry.slice(0, comma) !== "v1") return false;
const received = Buffer.from(entry.slice(comma + 1), "base64");
return received.length === expected.length && timingSafeEqual(received, expected);
});
}Da formatet følger Standard Webhooks-specifikationen, fungerer open source-verifikationsbiblioteker for andre sprog som de er; giv dem hemmeligheden og de tre headers.
Genforsøg og fejl
Dit endpoint har 10 sekunder til at svare, inklusive oprettelse af forbindelse. Et svar med en 2xx-status tæller som leveret; alt andet, inklusive timeout eller en omdirigering (leveringer følger aldrig omdirigeringer), tæller som en fejl og forsøges igen. Den ene undtagelse er 410 Gone: den fortæller os, at ruten blev fjernet med vilje, så den pågældende levering ikke forsøges igen, og endpointet deaktiveres med det samme, med en endpoint.disabled-begivenhed (årsag gone) til workspace'ets andre endpoints og en e-mail til alle ejere og admins. En 410 som svar på Send test eller en manuel gensendelse fejler kun den pågældende levering.
Svar 2xx, så snart du har accepteret begivenheden varigt, og udfør den tunge behandling asynkront.
En mislykket levering forsøges igen efter denne tidsplan:
| Forsøg | Forsinkelse efter den forrige fejl |
|---|---|
| 1 | øjeblikkelig |
| 2 | 5 sekunder |
| 3 | 5 minutter |
| 4 | 30 minutter |
| 5 | 2 timer |
| 6 | 5 timer |
| 7 | 10 timer |
| 8 | 10 timer |
Det er 8 forsøg over cirka 27,5 timer, så en hel dags nedetid på din side mister ikke begivenheder. Hver forsinkelse har op til 20% tilfældig jitter i begge retninger, hvilket forhindrer synkroniserede genforsøgsbølger mod et gendannende endpoint. Hvis dit endpoint returnerer en Retry-After-header, respekteres den anmodede forsinkelse inden for grænser: den klemmes mellem den planlagte forsinkelse og dobbelt så lang tid som den planlagte forsinkelse. Svar med status 429 og tidsudløbte forsøg gentages ikke før end 60 sekunder, uanset hvor tidligt det planlagte tidsrum falder.
Automatisk deaktivering
Et endpoint, der fortsætter med at fejle, deaktiveres til sidst i stedet for at blive ved med at blive forsøgt:
- Når leveringer har fejlet i 30 minutter, udløses en
delivery.failing-begivenhed én gang (leveret til workspace'ets andre endpoints), og hver workspace-ejer og -admin modtager en notifikation i appen. Dette er den tidlige advarsel; en kortvarig fejl, som en succes lukker inden for de 30 minutter, udløser intet. - Efterhånden som fejlvinduet fortsætter, eskalerer advarslen: efter 3 dage og igen efter 4,5 dage modtager hver ejer og admin en notifikation i appen og en e-mail, der angiver tidspunktet, hvorefter endpointet deaktiveres, hvis leveringer stadig fejler. Hver advarsel når hver person én gang per fejlvindue.
- En succesfuldt leveret begivenhed nulstiller fejlvinduet. En testbegivenhed gør ikke: den beviser, at endpointet svarer, ikke at det behandler begivenheder. Hvis en advarsel allerede var sendt ud, udløses en
delivery.recovered-begivenhed, og ejere og admins modtager en notifikation i appen om, at endpointet er kommet sig. - Efter 5 dage med på hinanden følgende fejl deaktiveres endpointet: en
endpoint.disabled-begivenhed udløses (igen til andre endpoints), og hver workspace-ejer og -admin får en e-mail.
Deaktiveringen kræver en mislykket levering efter den endelige advarsel. Hvis der ikke var sendt noget til endpointet på det tidspunkt, den endelige advarsel angav, udløser det næste fejl en frisk endelig advarsel i stedet, og endpointet deaktiveres kun, hvis leveringer stadig fejler 12 timer senere.
Hvis du selv slukker for et endpoint, udløses endpoint.disabled med årsag manual til workspace'ets andre endpoints. Et deaktiveret endpoint modtager ikke flere leveringer, og dets send-test- og gensend-handlinger er blokeret. Når din modtager er sund igen, genaktiver endpointet fra workspace-indstillingerne (klik på dens statusmærke): genaktivering rydder fejlhistorikken, så endpointet starter et nyt 5-dages vindue, og en endpoint.enabled-begivenhed udløses til workspace'ets andre endpoints. Begivenheder, der opstår, mens et endpoint er deaktiveret, registreres i dens leveringslog med status droppet i stedet for at blive sendt. Efter genaktivering skal du bruge Send test til at bekræfte, at endpointet er tilgængeligt, og derefter Genafspil mislykkede + droppede for at indhente alt, hvad nedetiden kostede, i én handling.
Manuel genlevering og test
Leveringsloggen i workspace-indstillingerne viser hvert leveringsforsøg med dens statuskode og svar. Derfra:
- Gensend genleverer den originale begivenhed fra en tidligere levering (samme
webhook-id, samme JSON-indhold) som ét forsøg. Den indgår ikke i genforsøgsplanen og tæller aldrig med til automatisk deaktivering, så det er sikkert at bruge, mens du fejlfinder en ustabil modtager. Gensendelsen bærer det oprindeligewebhook-id, så en modtager, der allerede har behandlet begivenheden før den svarede med en fejl, behandler den som den samme besked. - Genafspil mislykkede + droppede genafspiller alle terminalt mislykkede og droppede leveringer for endpointet, ældst først (leveringer, der stadig er på deres automatiske genforsøgsplan, er udelukket, så en genafspilning aldrig kan duplikere et genforsøg, der alligevel ville lykkes). Genafspillede leveringer bruger den fulde genforsøgsplan og bærer det oprindelige
webhook-id: for din tjeneste er en genafspilning den samme besked igen, så idempotenshåndtering påwebhook-idgør indhentning sikker, uanset om originalen nogensinde ankom. En begivenhed, som en gensendelse eller en tidligere genafspilning allerede har leveret, springes over, og det samme gør en, der stadig bliver leveret. Vælg, hvor langt tilbage du vil gå (de sidste 24 timer, de sidste 7 dage eller alt, der er bevaret), og dialogen viser forhåndsvisning af, hvor mange begivenheder det interval vil sende, før du starter; store intervaller bladres automatisk igennem, med fremskridt vist undervejs. Genafspilninger ankommer uden for rækkefølge; sorter de indhentede begivenheder efter payload'enssequence. - Send test leverer en signeret
webhook.ping-begivenhed med et tomtdata-objekt til præcis det pågældende endpoint, uanset dets begivenhedsabonnementer, og viser resultatet (statuskoden eller hvorfor det fejlede), så snart forsøget er afsluttet. Brug det til at bekræfte tilgængelighed og til at teste din signaturverifikation fra ende til anden. En testbegivenhed åbner, fremskynder eller nulstiller aldrig fejlvinduet.
Bedste praksis
- Fjern dubletter på
webhook-id. Automatiske genforsøg, gensendelser og genafspilninger genbruger det samme id, så lagring af behandlede id'er giver dig præcis-én-gang-behandling oven på mindst-én-gang-levering. - Behandl aldrig en payload som aktuel tilstand. Payloads er tynde og kan ankomme sent; brug begivenheden som en udløser og læs den aktuelle tilstand fra API'en.
- Stol ikke på rækkefølge. Genforsøg og parallel levering betyder, at begivenheder kan ankomme uden for rækkefølge.
- Returner 2xx før tungt arbejde. Sæt begivenheden i kø og behandl den asynkront; en handler, der udfører sit arbejde inline, risikerer at ramme 10-sekunders timeout og blive forsøgt igen, hvilket forvandler én begivenhed til flere duplikatforsøg.
- Roter hemmeligheder med et nådevindue. Rotation fra workspace-indstillingerne bevarer den tidligere hemmelighed gyldig i et vindue, du vælger, og leveringer signeres med begge hemmeligheder i løbet af det, så en rullende udrulning af dine tjenester aldrig taber en levering. Et endpoint har én tidligere hemmelighed, så en anden rotation med et nådevindue afvises, indtil det første vindue er slut; en rotation uden nådevindue accepteres altid og tilbagekalder alle gamle hemmeligheder med det samme, hvilket er vejen at gå ved en lækket hemmelighed.