Gå til hovedindhold
Certyneo
Udviklerdokumentation

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.

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, 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.declinedEn underskriver har afvist kuverten. Adressen på den, som afviste, er i `data.declinedBy`, og årsagen, hvis den blev angivet, er i `data.reason`.
envelope.voidedKonvolutten blev annulleret af udstederen før fuld underskrift.
envelope.expiredKuvertens udløbsdato er passeret uden fuldstændig underskrift. Forespørg GET /v1/envelopes/id for at se, hvilke underskrivere der mangler.
envelope.returned_to_senderEn 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.resubmittedAfsenderen har korrigeret og genomdelt en konvolut, der tidligere blev returneret. Markerer genstart af underskriftscyklussen.
recipient.signedEn 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.viewedEn underskriver har åbnet signaturlinket uden at have underskrevet det, hvilket er nyttigt for målrettede handelsopkøbsprogrammer.
recipient.approvedEn godkender har valideret kuverten uden at sætte sin underskrift på den (intern valideringsarbejdsgang). Adressen er i `data.approvedBy`.
recipient.bouncedEn 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øgTidsbegrænsning før prøveprøveTidslinje siden begivenhed
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 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.