Webhooks modtage signatur begivenheder i realtid
Konfigurér en HTTPS-URL i dit Certyneo-dashboard, og modtag en HMAC-SHA256-signeret POST, så snart en begivenhed opstår på dine kuverts: underskrift, afvisning, udløb. 11 understøttede begivenheder, 5 leveringsforsøg med eksponentiel backoff, kryptografisk verifikation på 8 kodelinjier.
< 5s
Median leveringstid efter begivenheden
5x
Leveringsforsøg i alt, spredt over cirka 1 time 20 minutter
HMAC-SHA256
Algorithm til at underskrive hver enkelt anmodning
Events katalog
De 12 begivenheder nedenfor dækker hele livscyklussen for en Certyneo-kuvert. Aktiver dem, der interesserer dig, i Indstillinger → Webhooks, ignorer de øvrige — abonnementet er granulært pr. begivenhed.
| Begivenhed | Udbrud |
|---|---|
envelope.created | En konvolut er oprettet (ved brug af UI, API eller template) nyttig til at synkronisere en CRM-side registrering fra oprettelsen. |
envelope.sent | Konvolutten sendes til underskriverne (første e-mail sendt). |
envelope.completed | Alle underskrivere har underskrevet, og den eIDAS-forseglede PDF er gemt. Payloaden indeholder signedDocumentUrl, et forhåndssigneret link gyldigt i 7 dage; ellers GET /v1/envelopes/id/signed-document, og revisionsloggen via GET /v1/envelopes/id/audit-trail. |
envelope.declined | En underskriver har afvist kuverten. Adressen på den, som afviste, er i `data.declinedBy`, og årsagen, hvis den blev angivet, er i `data.reason`. |
envelope.voided | Konvolutten blev annulleret af udstederen før fuld underskrift. |
envelope.expired | Kuvertens udløbsdato er passeret uden fuldstændig underskrift. Forespørg GET /v1/envelopes/id for at se, hvilke underskrivere der mangler. |
envelope.returned_to_sender | En underskriver har returneret kuverten til afsenderen til rettelse uden at afvise den. Årsagen er i `data.reason`, og hvem der returnerede den, er i `data.returnedBy`. |
envelope.resubmitted | Afsenderen har korrigeret og genomdelt en konvolut, der tidligere blev returneret. Markerer genstart af underskriftscyklussen. |
recipient.signed | En enkelt underskriver har underskrevet (men ikke nødvendigvis alle). Nyttigt til at følge fremdriften og gå videre til næste trin i en sekventiel arbejdsgang. Advarsel: den forseglede PDF findes endnu ikke på dette tidspunkt, selv for den sidste underskriver — en download startet herfra returnerer HTTP 409. Brug envelope.completed til dokumentet. |
recipient.viewed | En underskriver har åbnet signaturlinket uden at have underskrevet det, hvilket er nyttigt for målrettede handelsopkøbsprogrammer. |
recipient.approved | En godkender har valideret kuverten uden at sætte sin underskrift på den (intern valideringsarbejdsgang). Adressen er i `data.approvedBy`. |
recipient.bounced | En modtagers e-mailserver har permanent afvist invitationen eller påmindelsen (postboks findes ikke, domæne er dødt). Modtageren får status BOUNCED og automatiske påmindelser stoppes. Adressen er i `data.recipientEmail`: ret den og send kuverten igen. |
recipient.signed betyder ikke « dokument er tilgængeligt »
recipient.signed udstedes for HVER underskriver, når han/hun afslutter sin underskrift — inklusive den sidste, før den forseglede PDF monteres og gemmes. En download udløst fra denne handler modtager derfor altid HTTP 409 « Signed document not available until the envelope is COMPLETED ». Dette er ikke en fejl: det er « ikke helt klar endnu ». Abonnér på envelope.completed for at hente dokumentet, og hold recipient.signed for at følge fremdriften (hvem der har underskrevet, og hvornår).
Laddens format
Alle leveringer deler det samme top-level JSON-skema: `event`, `data` og `timestamp`. Begivenhedsnavnet gentages også i `X-Certyneo-Event`-headeren, hvilket tillader routing, før kroppen engang parses. Indholdet af `data` varierer efter begivenhed, men er altid et fladt objekt af simple værdier — aldrig et array eller indlejret objekt. Her er en fuldstændig `envelope.completed`-levering.
POST /webhooks/certyneo HTTP/1.1
Host: your.app
Content-Type: application/json
X-Certyneo-Event: envelope.completed
X-Certyneo-Delivery-Id: cm8f2h6a10004qr9k5p2wm3xt
X-Certyneo-Signature: 4f3d1c8b2a9e7f60d5c4b3a2918e7f6d5c4b3a2918e7f6d5c4b3a2918e7f6d5c{
"id": "cm8f2h6a10004qr9k5p2wm3xt",
"event": "envelope.completed",
"data": {
"envelopeId": "cm7x2k9p40001qz8h3f7bn2ld",
"sandbox": false,
"subject": "Contrat de prestation Acme Corp",
"status": "COMPLETED",
"signatureLevel": "ADVANCED",
"aesSignerCount": 2,
"completedAt": "2026-05-27T08:42:13.000Z",
"recipientCount": 2,
"recipients": [
{ "email": "alice@acme.com", "name": "Alice Martin", "role": "SIGNER", "status": "SIGNED" },
{ "email": "bob@acme.com", "name": "Bob Durand", "role": "SIGNER", "status": "SIGNED" }
],
"signedDocumentUrl": "https://storage.certyneo.com/signed/cm7x2k9p4.../contrat.pdf?X-Amz-Expires=604800&X-Amz-Signature=..."
},
"timestamp": "2026-05-27T08:42:13.521Z"
}signedDocumentUrl er et forhåndssignet link, der er gyldigt i 7 dage fra begivenheden: det undgår et second autentificeret opkald for at hente PDF'en. Det er fraværende — og ikke null — hvis forhåndssigneringen mislykkedes, eller på en QES-konvolut udfyldt uden lagret dokument; gå derefter tilbage til GET /v1/envelopes/id/signed-document, som er den ultimative kilde til sandhed. Vær også opmærksom på manuel genafspilning fra dead-letter køen mere end 7 dage efter begivenheden: linket i payload er udløbet, API-slutpunktet ikke.
HMAC-signaturen beregnes på den rå krop som sendt ikke ændre mellemrum, JSON-re-parsing ændrer ofte nøglernes rækkefølge og bryder verifikationen.
Kontroller HMAC-signaturen
Hver anmodning er signeret med dit webhook-hemmelighed (vist kun én gang ved oprettelse af abonnementet). Signaturen transmitteres i headerfeltet `X-Certyneo-Signature`: det er HMAC-SHA256 af det rå anmodningsindhold, kodet i hexadecimal, uden præfiks eller tidsstempel. Verificer ALTID signaturen før behandling af payload — uden dette trin kan hvem som helst forfalske en begivenhed og kalde dit endpoint.
Node.js / TypeScript
import crypto from "node:crypto";
export function verifyCertyneoSignature(
rawBody: string,
signatureHeader: string,
secret: string,
): boolean {
// X-Certyneo-Signature = HMAC-SHA256(secret, rawBody), hex-encoded.
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const received = Buffer.from(signatureHeader, "hex");
const computed = Buffer.from(expected, "hex");
// timingSafeEqual throws when the two buffers differ in length —
// a malformed header must return false, not crash the handler.
if (received.length !== computed.length) return false;
// timingSafeEqual to mitigate timing attacks.
return crypto.timingSafeEqual(computed, received);
}Python
import hashlib
import hmac
def verify_certyneo_signature(
raw_body: bytes, signature_header: str, secret: str
) -> bool:
"""Verify a Certyneo webhook signature.
`X-Certyneo-Signature` holds the hex-encoded HMAC-SHA256 of the
raw request body, computed with your webhook secret.
"""
expected = hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)Fejl, der ofte sker
Brug IKKE `===` eller `==` til at sammenligne den forventede og modtagne signatur. Brug en timing-safe funktion (`crypto.timingSafeEqual` i Node, `hmac.compare_digest` i Python). Ellers afslører sammenligningstiden mellem to signaturer gradvist hemmeligheden for et patientangreb (timing attack).
Retry-politik
Hvis dit endpoint tager for lang tid at svare, nægter forbindelsen eller returnerer en 5xx (eller en 429), forsøger vi igen efter eksponentiel backoff: 5 forsøg i alt over cirka 1 t 20 min. Efter det femte forsøg går begivenheden til dead-letter queue — den kan ses og afspilles igen fra dit dashboard, men bliver ikke automatisk forsøgt igen. En eksplicit nægtelse (401, 403, 404, 410, 422…) bliver derimod aldrig forsøgt igen: svaret ville ikke ændres, begivenheden går direkte til dead-letter queue.
| Forsøg | Tidsbegrænsning før prøveprøve | Tidslinje siden begivenhed |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 15 min | 21 min |
| #5 | + 1 h | 1 h 21 |
De angivne tidsfrister er minimumskrav: retry-scanningen styres af en cron, så et forsøg kan derfor starte lidt efter det teoretiske tidspunkt. Opgive begivenheder vises i Webhooks → Fejl, med en manuel afspilningsknap uden begrænsning på opbevaringsduration. Et endpoint der efterfølges af fem endelige fejl — eller som returnerer 404 / 410 — deaktiveres automatisk, og du får besked pr. email: genaktiver det når det er rettet, tælleren starter forfra (enhver vellykket levering nulstiller det også).
Test uden at sende en ægte konvolut
Opret først et abonnement — svaret indeholder det `hemmelighed`, der er nødvendigt til HMAC-verifikation, vist kun én gang. Fra Indstillinger → Webhooks sender knappen « Test » derefter en signeret POST nøjagtigt som en ægte levering og viser dig det rå svar fra serveren: det er den hurtigste måde at validere din handler på, lokalt via ngrok eller i continuous integration.
# Create a subscription — "secret" is returned once, store it now.
curl -X POST https://api.certyneo.com/v1/webhooks \
-H "Authorization: Bearer $CERTYNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your.app/webhooks/certyneo",
"events": ["envelope.completed", "recipient.signed"]
}'URL'en skal være offentligt tilgængelig via HTTPS: en privat adresse eller localhost afvises af SSRF-beskyttelsen på oprettelsestidspunktet for abonnementet.
Abonnementer er isoleret efter miljø, ligesom nøgler: en webhook, der er oprettet med en sk_test_-nøgle, modtager kun begivenheder fra testkonvolutter, en webhook, der er oprettet med en sk_live_-nøgle, modtager kun begivenheder fra rigtige konvolutter. En testkonvolut når derfor aldrig din produktions-URL, og hver payload angiver "sandbox" (true eller false).
6 gode metoder
- Kontroller HMAC-signaturen FØR enhver læsning af kroppen brug en timing-safe sammenligning.
- Deduplikér baseret på `X-Certyneo-Delivery-Id`-headeren (identisk med `id` i brødteksten) ved at lagre allerede sete identifikatorer i databasen — en genudsendelse kan levere en begivenhed igen, som du allerede har behandlet, hvis dit 2xx gik tabt, med samme identifikator ved hvert forsøg.
- Svar HTTP 2xx inden for maksimalt 10 sekunder, behandl derefter asynkront (kø). Herover afbrydes leveringen og tælles som en fejl.
- Logge brut body + fuld debug-signatur HMAC-verifikation fejler ofte på en BOM eller usynlig hvidrum.
- Overvåg siden Webhooks → Fejl: du får besked pr. email hvis dit endpoint er deaktiveret, men ikke begivenhed for begivenhed — en begivenhed der opbruger sine forsøg, det er op til dig at afspille den igen.
- Filtrer begivenheder ved abonnement i stedet for i din handler, og hold dig under grænsen på 5 abonnementer per konto.
Du er ikke forpligtet til at være vært for et slutpunkt for at modtage disse begivenheder: connectoren placerer abonnementet for dig og starter flowet direkte ved en kuvertbegivenhed. Se Certyneo til Power Automate og Microsoft 365.
For at gå videre
Er I klar til at tilslutte jeres systemer?
Webhooks og REST API er inkluderet fra Standard-planen og opefter. Opret din konto, generer en `sk_test_`-nøgle og tilslut dit endpoint i sandbox inden du går i produktion.