Webhooks modtage signatur begivenheder i realtid
Du kan konfigurere en HTTPS-URL i dit Certyneo-dashboard og modtage en HMAC-SHA256-signeret POST, når en begivenhed sker på dine konvolutter: signatur, afvisning, udløbsdato. 8 events understøttet, eksponentielt genprøvning på 6 forsøg, krypteringsverifikation på 8 kodelinjer.
< 5s
Median leveringstid efter begivenheden
5x
Forsøg ved fiasko (indtil ~ 9 timer senere)
HMAC-SHA256
Algorithm til at underskrive hver enkelt anmodning
Events katalog
De 8 begivenheder nedenfor dækker hele livscyklusen af en Certyneo-kuvert. Aktivér de, du er interesseret i Indstillinger → Webhooks, ignorer de andre 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. eIDAS-stemplet PDF og audit trail er tilgængelige via `proof_pdf_url` i payload. |
envelope.declined | En underskriver har afvist konvolutten. Grunden til afvisningen (hvis den er angivet af underskriveren) er i `data.decline_reason`. |
envelope.voided | Konvolutten blev annulleret af udstederen før fuld underskrift. |
envelope.expired | Enklopets udløbsdato er gået uden fuld underskrift. |
recipient.signed | En enkelt underskriver har underskrevet (men ikke alle). |
recipient.viewed | En underskriver har åbnet signaturlinket uden at have underskrevet det, hvilket er nyttigt for målrettede handelsopkøbsprogrammer. |
Laddens format
Alle begivenhederne deler samme top-level JSON-sema: `event`, `envelope_id`, `occurred_at`, `data`. Indholdet i `data` varierer fra begivenhed til begivenhed (feltet dokumenteres for begivenhed i API-referencen).
{
"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"
}
}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 forespørgsel underskrives med dit webhook- hemmelighed (synlig én gang ved oprettelse i dashboarden, derefter krypteret ved hvile).<timestamp>,v1=<hex_hmac>`. Tjek ALTID signaturen, før du behandler payload uden dette trin kan alle forske en begivenhed og ringe til dit 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)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 din slutpunkt svarer andet end en HTTP 2xx (timeout, 5xx, connection refused), ringer vi med en eksponentiel backoff. 6 forsøg i ~9 timer. Efter 6 bliver begivenheden markeret som `failed` i dit webhook-dashboard og en advarselsmail sendes.
| Forsøg | Samlet tidsrum | Tid tilbage |
|---|---|---|
| #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 |
Hvis du vil manuelt videresende en fejlet levering, har webhook-dashboardet en knap Re-deliver på hver fejlet levering tilgængelig i 7 dage efter den endelige fiasko.
Test uden at sende en ægte konvolut
Endpunktet `/v1/webhooks/test` accepterer en URL og en begivenhedstype, og POST en fiktiv payload, der er underskrevet nøjagtigt som en ægte.
# 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 gode metoder
- Kontroller HMAC-signaturen FØR enhver læsning af kroppen brug en timing-safe sammenligning.
- Behandler hver `envelope_id` × `event` kun én gang ved at gemme de ID'er, der allerede er set i basen (genindslag kan genindsætte den samme begivenhed).
- Svar HTTP 200 inden for 5 sekunder, og derefter behandle asynkront (tilbage), ellers går du i timeout og tæller som genforsøg.
- Logge brut body + fuld debug-signatur HMAC-verifikation fejler ofte på en BOM eller usynlig hvidrum.
- Tilslut en dead-letter-æske på hændelser `failed` for at manuelt aflevere i tilfælde af en hændelse ved din side af tjenesten.
- Tolererer en forsinkelse på 5 minutter mellem `t=` og modtagelsestidspunktet , afviser for at blokere for replay.
For at gå videre
Er I klar til at tilslutte jeres systemer?
Opret en gratis konto på 2 minutter webhook-opstillingen er tilgængelig allerede på Starter-planen (gratis, 5 konvolutter/måned).