Webhooks få signaturhändelser i realtid
Ställ in en HTTPS-URL i din Certyneo-dashboard och få en HMAC-SHA256-signerad POST när en händelse inträffar på dina kuvert: signatur, avslag, utgång. 8 händelser som stöds, exponentiell retry på 6 försök, krypteringsverifiering i 8 rader kod.
< 5s
Median leveranstid efter händelsen
5x
Försök om de misslyckas (upp till ~ 9 timmar senare)
HMAC-SHA256
Signaturalgoritm för varje begäran
Händelsernas katalog
De 8 händelserna nedan omfattar hela Certyneo-upplånets livscykel. Aktivera de du är intresserad av i Inställningar → Webhooks, ignorera de andra prenumerationen är granulär 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 undertecknare har skrivit under. eIDAS-förseglade PDF och audit trail finns tillgängliga via `proof_pdf_url` i payload. |
envelope.declined | En signatör har vägrat att ta emot kuvertet. Anledningen till vägran (om den har lämnats av signaten) finns i `data.decline_reason`. |
envelope.voided | Det är olik `expired` (människa vs timeout). |
envelope.expired | Enklaffret har gått ut utan fullständig underskrift. |
recipient.signed | En enskild undertecknare har skrivit under (men inte alla). |
recipient.viewed | En undertecknare öppnade signaturlänken utan att ha skrivit under, vilket är bra för riktade affärsuppskott. |
Payloadformat
Alla händelser har samma top-level JSON-schema: `event`, `envelope_id`, `occurred_at`, `data`. Innehållet i `data` varierar mellan händelser (dokumenterade fält per händelse i API-referensen).
{
"event": "envelope.completed",
"envelope_id": "env_01HG3K8X9Y2N4P5Q6R7S8T9V0W",
"occurred_at": "2026-05-27T08:42:13.521Z",
"data": {
"title": "Contrat de prestation Acme Corp",
"status": "completed",
"created_at": "2026-05-24T14:12:00.000Z",
"completed_at": "2026-05-27T08:42:13.000Z",
"signers": [
{
"email": "client@acme.example",
"name": "Alex Client",
"signed_at": "2026-05-27T08:42:13.000Z",
"method": "eidas_aes",
"ip": "203.0.113.42"
}
],
"proof_pdf_url": "https://certyneo.com/api/envelopes/env_01HG.../proof.pdf"
}
}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 förfrågan signeras med din webhook-hemlighet (synlig en gång när den skapas i instrumentpanelen och sedan krypterad i vila).<timestamp>,v1=<hex_hmac>`. Kontrollera alltid signaturen innan du behandlar payload utan det här steget kan vem som helst skapa en händelse och ringa din slutpunkt.
Node.js / TypeScript
import crypto from "node:crypto";
export function verifyCertyneoSignature(
rawBody: string,
signatureHeader: string,
secret: string,
): boolean {
// Header format: t=<timestamp>,v1=<hex_hmac>
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=")),
);
const ts = parts.t;
const sig = parts.v1;
if (!ts || !sig) return false;
// Reject events older than 5 minutes (replay protection).
const age = Math.abs(Date.now() / 1000 - Number(ts));
if (age > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
// timingSafeEqual to mitigate timing attacks.
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(sig, "hex"),
);
}Python
import hashlib
import hmac
import time
def verify_certyneo_signature(
raw_body: bytes, signature_header: str, secret: str
) -> bool:
"""Verify a Certyneo webhook signature.
Header format: `t=<timestamp>,v1=<hex_hmac>`
Rejects events older than 5 minutes (replay protection).
"""
parts = dict(p.split("=") for p in signature_header.split(","))
ts = parts.get("t")
sig = parts.get("v1")
if not ts or not sig:
return False
if abs(time.time() - int(ts)) > 300:
return False
expected = hmac.new(
secret.encode("utf-8"),
f"{ts}.{raw_body.decode('utf-8')}".encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)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 slutpunkt svarar annat än en HTTP 2xx (timeout, 5xx, connection refused), vi ringer med exponentiell backoff. Totalt 6 försök på ~9 timmar. Efter 6 markeras händelsen `failed` i din webhook-dashboard och ett varningsmail skickas.
| Försök | Sammanlagd tid | Tid som gått |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 30 min | 36 min |
| #5 | + 2 h | 2h 36 |
| #6 | + 6 h | 8h 36 |
Om du vill manuellt vidarebefordra en missad händelse, erbjuder webhook-dashbordet en Re-deliver -knapp på varje misslyckad leverans tillgänglig i 7 dagar efter det slutliga misslyckandet.
Provning utan att skicka ett riktigt kuvert
Endpunkten `/v1/webhooks/test` accepterar en URL och en händelsetyp, och POST en fiktiv payload signerad precis som en riktig.
# Trigger a test webhook to your endpoint
curl -X POST https://api.certyneo.com/v1/webhooks/test \
-H "Authorization: Bearer $CERTYNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"event": "envelope.completed", "url": "https://your.app/webhooks/certyneo"}'6 metoder att följa
- Kontrollera HMAC-signaturen FÖR varje läsande av kroppen använd en tidssäker jämförelse.
- Behandla varje `envelope_id` × `event` en gång och lagra de ID:er som redan har visats i basen (återkopplingar kan återupprätta samma händelse).
- Svar HTTP 200 inom 5 sekunder, och sedan asynkront (sporad) om du inte gör det, så kommer du att få timeout och räknas som ett omprövningssök.
- Logga brutthuset + fullständig signatur i debug HMAC-kontrollen misslyckas ofta på en BOM eller osynlig white space.
- Anslut en dead-letter-kö på händelserna `failed` för att manuellt vidarebefordra om en incident inträffar på din tjänst.
- Tolerera en fem minuters fördröjning mellan `t=` och -upptagningstid, avvisa för att blockera replay.
För att gå vidare
Är ni redo att koppla in era system?
Skapa ett gratis konto på 2 minuter webhook-inställningen finns redan på Starter-planen (gratis, 5 kuvert/månad).