Webhooks få signaturhändelser i realtid
Konfigurera en HTTPS-URL i din Certyneo-instrumentpanel och ta emot ett HMAC-SHA256-signerat POST så snart en händelse inträffar på dina kuvert: signering, vägran, förfallodatum. 11 händelser stöds, 5 leveransförsök med exponentiell backoff, kryptografisk verifiering på 8 kodlinjer.
< 5s
Median leveranstid efter händelsen
5x
Leveransförsök totalt, fördelade över cirka 1 h 20
HMAC-SHA256
Signaturalgoritm för varje begäran
Händelsernas katalog
De 12 händelserna nedan täcker hela livscykeln för ett Certyneo-kuvert. Aktivera de som intresserar dig i Inställningar → Webhooks, ignorera de andra — abonnemanget är granulerat per händelse.
| Händelse | Utbrott |
|---|---|
envelope.created | Ett omslag skapas (via UI, API eller mall) användbart för att synkronisera en CRM-sida från skapandet. |
envelope.sent | Det är då som det första e-postmeddelandet skickas till undertecknarna. |
envelope.completed | Alla signerare har undertecknat och det förseglade eIDAS PDF är lagrat. Nyttolasten bär signedDocumentUrl, en försignerad länk giltig 7 dagar; annars GET /v1/envelopes/id/signed-document, och granskningsspåret via GET /v1/envelopes/id/audit-trail. |
envelope.declined | En signerare vägrade kuvertet. Adressen för den som vägrade är i `data.declinedBy` och anledningen, om den angavs, i `data.reason`. |
envelope.voided | Det är olik `expired` (människa vs timeout). |
envelope.expired | Kuvertets utgångsdatum har passerat utan fullständig signering. Fråga GET /v1/envelopes/id för att känna till de signerare som saknas. |
envelope.returned_to_sender | En signerare returnerade kuvertet till avsändaren för rättning utan att vägra det. Anledningen är i `data.reason` och författaren till returen i `data.returnedBy`. |
envelope.resubmitted | Avsändaren korrigerade och skickade återigen ett kuvert som tidigare returnerats. Markerar återupptagningen av signeringscykeln. |
recipient.signed | En enskild signerare undertecknade (men inte nödvändigtvis alla). Användbart för att spåra framsteg och länka till nästa steg i ett sekventiellt arbetsflöde. Varning: det förseglade PDF:en finns ännu inte i det här stadiet, även för den sista signeraren — en nedladdning som startas här returnerar HTTP 409. Gå via envelope.completed för dokumentet. |
recipient.viewed | En undertecknare öppnade signaturlänken utan att ha skrivit under, vilket är bra för riktade affärsuppskott. |
recipient.approved | En godkännare validerade kuvertet utan att bifoga en signatur (internt valideringsarbetsflöde). Adressen är i `data.approvedBy`. |
recipient.bounced | En mottagares e-postserver nekade permanent inbjudan eller påminnelse (befintlig brevlåda, död domän). Mottagaren får statusen BOUNCED och automatiska påminnelser stoppas. Adressen finns i `data.recipientEmail`: korrigera den och skicka kuvertet igen. |
recipient.signed betyder inte "dokument tillgängligt"
recipient.signed sänds för VARJE signerare när han/hon avslutar sin signering — inklusive den sista, innan det förseglade PDF:en är monterad och lagrad. En nedladdning som utlöses från denna handler får därför alltid en HTTP 409 "Signed document not available until the envelope is COMPLETED". Det är inte ett fel: det är en "ännu inte klar". Prenumerera på envelope.completed för att hämta dokumentet, och behåll recipient.signed för att spåra framsteg (vem som undertecknade och när).
Payloadformat
Alla leveranser delar samma JSON-schema på högsta nivå: `event`, `data` och `timestamp`. Händelsens namn upprepas också i headern `X-Certyneo-Event`, vilket gör att man kan dirigera innan man ens tolkar kroppen. Innehållet i `data` varierar beroende på händelse, men förblir alltid ett platt objekt med enkla värden — aldrig en matris eller ett kapslat objekt. Här är en komplett `envelope.completed` leverans.
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 är en försignerad länk giltig 7 dagar från händelsen: den undviker ett andra autentiserat samtal för att hämta PDF:en. Den är frånvarande — och inte null — om försigneringen misslyckades, eller på ett QES-kuvert slutfört utan lagrat dokument; gå sedan tillbaka till GET /v1/envelopes/id/signed-document, som förblir sanningens källa. Var också försiktig med manuell omspelning från dead-letter queue mer än 7 dagar efter händelsen: länken i nyttolasten är utgången, API:et är inte det.
Payload är kodad UTF-8, utan BOM. HMAC-signaturen beräknas på den råa kroppen som skickas ändra inte med tomrummen, JSON-reparsering ändrar ofta ordningen på nycklarna och bryter verifieringen.
Kontrollera HMAC-signaturen
Varje begäran är signerad med din webhook-hemlighet (visad endast en gång när prenumerationen skapas). Signaturen överförs i rubriken `X-Certyneo-Signature`: det är HMAC-SHA256 för begärans råa brödtext, kodad i hexadecimalt, utan prefix eller tidsstämpel. Verifiera ALLTID signaturen innan du behandlar nyttolasten — utan detta steg kan vem som helst förfalska en händelse och anropa din 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)Vanliga misstag
Använd inte `===` eller `==` för att jämföra den förväntade signaturen med den mottagna. Använd en timing-safe funktion (`crypto.timingSafeEqual` i Node, `hmac.compare_digest` i Python). Annars avslöjar tidsskillnaden mellan två signaturer sakta hemligheten för en patient angripare (timing attack).
Återförsökspolicy
Om din endpoint tar för lång tid att svara, vägrar anslutningen eller returnerar ett 5xx (eller ett 429), försöker vi igen enligt exponentiell backoff: 5 försök totalt, under cirka 1 h 20. Efter det femte försöket går händelsen till dead-letter queue — den kan visas och spelas upp igen från din instrumentpanel, men försöks inte automatiskt igen. Ett explicit vägran (401, 403, 404, 410, 422…) försöks däremot aldrig igen: svaret skulle inte förändras, händelsen går direkt till dead-letter queue.
| Försök | Tidsgräns innan försök | Tid som gått sedan händelsen |
|---|---|---|
| #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 angivna tiderna är minimier: omförsökssökningen styrs av ett cron-jobb, så ett försök kan därför starta något senare än den teoretiska tiden. Övergivna händelser visas i Webhooks → Fel, med en knapp för manuell omspelning och utan gräns för lagringstid. En endpoint som får fem definitiva fel i rad — eller som svarar 404/410 — inaktiveras automatiskt, och du meddelas per e-post: återaktivera den när den är åtgärdad, räknaren börjar om från noll (varje lyckad leverans återställer den också till noll).
Provning utan att skicka ett riktigt kuvert
Skapa först en prenumeration — svaret innehåller `secret` som behövs för HMAC-verifikation, visad endast en gång. Från Inställningar → Webhooks skickar knappen "Testa" sedan ett signerat POST exakt som en verklig leverans och visar dig det råa svaret från din server: det är det snabbaste sättet att validera din handler, 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 måste vara offentligt nåbar via HTTPS: en privat adress eller localhost avvisas av SSRF-skyddet när prenumerationen skapas.
Prenumerationer är isolerade per miljö, precis som nycklar: en webhook skapad med en sk_test_-nyckel mottar endast händelser från testkuvert, en webhook skapad med en sk_live_-nyckel mottar endast händelser från riktiga kuvert. Ett testkuvert når därför aldrig din produktions-URL, och varje nyttolast anger "sandbox" (true eller false).
6 metoder att följa
- Kontrollera HMAC-signaturen FÖR varje läsande av kroppen använd en tidssäker jämförelse.
- Deduplisera på rubriken `X-Certyneo-Delivery-Id` (identisk med `id` i texten) genom att lagra redan sedda identifierare i databasen — en omuppspelning kan leverera en händelse som du redan har behandlat om din 2xx gick förlorad, med samma identifierare vid varje försök.
- Svara med HTTP 2xx inom maximalt 10 sekunder, behandla sedan asynkront (kö). Därefter avbryts leveransen och räknas som ett misslyckande.
- Logga brutthuset + fullständig signatur i debug HMAC-kontrollen misslyckas ofta på en BOM eller osynlig white space.
- Övervaka sidan Webhooks → Fel: du meddelas per e-post om din endpoint är inaktiverad, men inte händelse för händelse — en händelse som uttömmer sina försök måste du spela upp manuellt.
- Filtrera händelser vid prenumeration snarare än i din handler, och håll dig under gränsen på 5 prenumerationer per konto.
Du är inte obligerad att vara värd för en slutpunkt för att ta emot dessa händelser: kopplingen anger prenumerationen för dig och startar flödet direkt på en kuverthändelse. Se Certyneo för Power Automate och Microsoft 365.
För att gå vidare
Är ni redo att koppla in era system?
Webhooks och REST API ingår från och med planen Standard. Skapa ditt konto, generera en `sk_test_`-nyckel och anslut din endpoint i sandlåda innan du går till produktion.