Gå til hovedindhold
Certyneo
Udviklerdokumentation

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.

BegivenhedUdbrud
envelope.createdEn konvolut er oprettet (ved brug af UI, API eller template) nyttig til at synkronisere en CRM-side registrering fra oprettelsen.
envelope.sentKonvolutten sendes til underskriverne (første e-mail sendt).
envelope.completedAlle underskrivere har underskrevet. eIDAS-stemplet PDF og audit trail er tilgængelige via `proof_pdf_url` i payload.
envelope.declinedEn underskriver har afvist konvolutten. Grunden til afvisningen (hvis den er angivet af underskriveren) er i `data.decline_reason`.
envelope.voidedKonvolutten blev annulleret af udstederen før fuld underskrift.
envelope.expiredEnklopets udløbsdato er gået uden fuld underskrift.
recipient.signedEn enkelt underskriver har underskrevet (men ikke alle).
recipient.viewedEn 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øgSamlet tidsrumTid tilbage
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 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).