Gå til hovedinnhold
Certyneo
Utvikler dokumentasjon

Webhooks motta signatur hendelser i sanntid

Konfigurer en HTTPS-URL i Certyneo-dashbordet ditt og motta en HMAC-SHA256-signert POST så snart en hendelse skjer på konvoluttene dine: signatur, avvisning, utløp. 11 hendelser støttet, 5 leveringsforsøk i eksponentiell backoff, kryptografisk verifisering på 8 linjer med kode.

< 5s

Median leveringstid etter hendelsen

5x

Leveringsforsøk totalt, spredt over omtrent 1 t 20 min

HMAC-SHA256

Signaturalgoritme for hver forespørsel

Events katalog

De 12 hendelsene nedenfor dekker hele livssyklusen til en Certyneo-konvolutt. Aktiver de som interesserer deg i Innstillinger → Webhooks, ignorer de andre — abonnementet er granulært per hendelse.

BegivenhetUtbrudd
envelope.createdEn konvolutt er opprettet (ved UI, API eller template) nyttig for å synkronisere en CRM-side registrering fra opprettelsen.
envelope.sentKonvolutten sendes til underskriverne (første e-post sendt).
envelope.completedAlle underskrivere har signert og den forseglede eIDAS PDF-en er lagret. Payloaden inneholder signedDocumentUrl, en forhåndssignert lenke gyldig i 7 dager; ellers, GET /v1/envelopes/id/signed-document, og revisjonsspor via GET /v1/envelopes/id/audit-trail.
envelope.declinedEn underskriver avviste konvolutten. Adressen til avviseren er i `data.declinedBy` og årsaken, hvis den ble oppgitt, i `data.reason`.
envelope.voidedKonvolutten ble kansellert av utsteder før full signatur.
envelope.expiredUtløpsdatoen for konvolutten har passert uten fullstendig signatur. Spør GET /v1/envelopes/id for å finne ut hvilke underskrivere som mangler.
envelope.returned_to_senderEn underskriver sendte konvolutten tilbake til avsenderes for korreksjon, uten å avvise den. Årsaken er i `data.reason` og forfatteren av tilbakesendingen i `data.returnedBy`.
envelope.resubmittedAvsenderes korrigerte og sendte deretter en konvolutt som tidligere ble returnert. Markerer gjenopptakelsen av signatursyklusen.
recipient.signedEn individuell underskriver har signert (men ikke nødvendigvis alle). Nyttig for å spore fremgang og knytte til neste trinn i en sekvensielt arbeidsflyts. Advarsel: den forseglede PDF-en finnes ikke ennå på dette stadiet, selv for den siste underskriveren — en nedlasting som startes her returnerer en HTTP 409. Gå gjennom envelope.completed for dokumentet.
recipient.viewedEn underskriver åpnet signaturkoblingen uten å signere, noe som er nyttig for målrettede kommersielle oppstart.
recipient.approvedEn godkjenner har validert konvolutten uten å sette en signatur (intern valideringsarbeidsflyt). Adressen er i `data.approvedBy`.
recipient.bouncedE-postserveren til en mottaker har avslått invitasjonen eller påminnelsen permanent (ikke-eksisterende postkasse, død domene). Mottakeren får statusen BOUNCED og automatiske påminnelser stopper. Adressen finnes i `data.recipientEmail`: korriger den og send omslaget på nytt.

recipient.signed betyr ikke « dokument tilgjengelig »

recipient.signed sendes for HVER underskriver, når de avslutter signaturen sin — inkludert den siste, før det forseglede PDF-dokumentet blir satt sammen og lagret. Et nedlasting utløst fra denne handleren mottar derfor alltid en HTTP 409 « Signed document not available until the envelope is COMPLETED ». Dette er ikke en feil: det er « ikke helt klart ennå ». Abonner på envelope.completed for å hente dokumentet, og behold recipient.signed for å følge fremgangen (hvem som signerte, og når).

Payloadformat

Alle leveranser deler det samme JSON-skjemaet på toppnivå: `event`, `data` og `timestamp`. Hendelsesnavnet gjentas også i `X-Certyneo-Event`-headeren, som gjør det mulig å rute før vi parser kroppen. Innholdet i `data` varierer avhengig av hendelsen, men forblir alltid et flatt objekt med enkle verdier — aldri en matrise eller nestet objekt. Her er en fullstendig `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 en forhåndssignert lenke som er gyldig i 7 dager fra hendelsen: den unngår et andre autentisert anrop for å hente PDF-en. Den er fraværende — og ikke null — hvis forhåndssigneringen mislyktes, eller på en QES-konvolutt som ble fullført uten lagret dokument; gå da gjennom GET /v1/envelopes/id/signed-document, som fortsatt er kilden til sannheten. Vær også oppmerksom på manuell gjenspilling fra dead-letter-køen mer enn 7 dager etter hendelsen: lenken i payloaden er utløpt, API-endepunktet er det ikke.

Payloaden er kodet UTF-8, uten BOM. HMAC-signaturen beregnes på råen kroppsform som sendt ikke endre mellomrom, JSON-reparsing endrer ofte nøkkelordningen og bryter verifikasjonen.

Sjekk HMAC-signaturen

Hver forespørsel signeres med webhook-hemmeligheten din (vises bare én gang ved opprettelsen av abonnementet). Signaturen overføres i `X-Certyneo-Signature`-headeren: det er HMAC-SHA256 av den rå forespørselskroppen, kodet i heksadesimal, uten prefiks eller tidsstempel. ALLTID verifiser signaturen før du behandler payloaden — uten dette trinnet kan hvem som helst forfalske en hendelse og kalle endepunktet ditt.

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)

Vanlig feil

Bruk IKKE `===` eller `==` for å sammenligne den ventede signaturen med den mottatte. Bruk en timing-safe funksjon (`crypto.timingSafeEqual` i Node, `hmac.compare_digest` i Python). Ellers avslører tidsforskjellen mellom to signaturer gradvis hemmeligheten til en pasient angriper (timing attack).

Retry-policy

Hvis endepunktet ditt bruker for lang tid på å svare, nekter tilkoblingen eller returnerer en 5xx (eller en 429), gjør vi forsøk på nytt med eksponentiell backoff: 5 forsøk totalt, over ca. 1 t 20 min. Etter det femte forsøket går hendelsen til dead-letter queue — den kan hentes opp og avspilles på nytt fra dashbordet ditt, men blir ikke automatisk forsøkt på nytt. En eksplisitt nekting (401, 403, 404, 410, 422…) blir derimot aldri forsøkt på nytt: svaret ville ikke endres, hendelsen går direkte til dead-letter queue.

ForsøkTidsfrist før prøveprøveTid som gått siden hendelsen
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 h 21

De angitte tidene er minimumsverdier: retry-skanningen styres av en cron, så et forsøk kan derfor starte litt etter den teoretiske tiden. Oppgitte hendelser vises under Webhooks → Feil, med en knapp for manuell avspilling og uten oppbevaringsbegrensning. Et endepunkt som har fem påfølgende permanente feil — eller som returnerer 404 / 410 — blir automatisk deaktivert, og du blir varslet via e-post: reaktiver det når det er rettet, telleren starter fra null (enhver vellykket levering setter det også til null).

Test uten å sende en ekte konvolut

Opprett først et abonnement — svaret inneholder `secret` som trengs for HMAC-verifisering, vises bare én gang. Fra Innstillinger → Webhooks, sender «Test»-knappen deretter en signert POST nøyaktig som en ekte levering og viser deg råsvaret fra serveren din: det er den raskeste måten å validere handleren din på, lokalt via ngrok eller i kontinuerlig integrasjon.

# 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å være offentlig tilgjengelig over HTTPS: en privat adresse eller localhost blir avvist av SSRF-beskyttelsen når abonnementet opprettes.

Abonnementene er atskilt etter miljø, som nøklene: en webhook opprettet med en nøkkel sk_test_ mottar bare hendelser fra testkonvolutter, en webhook opprettet med en nøkkel sk_live_ mottar bare hendelser fra ekte konvolutter. En testkonvolutt når derfor aldri produksjons-URL-en din, og hver nyttelast indikerer «sandbox» (true eller false).

6 gode metoder

  • Sjekk HMAC-signaturen FØR alle avlesninger av kroppen bruk timing-safe sammenligning.
  • Deduplisering på `X-Certyneo-Delivery-Id`-headeren (identisk med `id` i brødteksten) ved å lagre identifikatorer som allerede er sett i databasen — en gjenspilling kan realisere en hendelse som du allerede har behandlet hvis ditt 2xx gikk tapt, med samme identifikator ved hver forsøk.
  • Svar HTTP 2xx innen maksimalt 10 sekunder, behandle deretter asynkront (kø). Utover det blir leveringen kuttet og tellesmidt som en feil.
  • Logge brutthoden + full signatur i debug HMAC-verifisering mislykkes ofte på en BOM eller usynlig hvitsplass.
  • Overvåk siden Webhooks → Feil: du blir varslet via e-post hvis endepunktet ditt er deaktivert, men ikke event for event — hvis en hendelse bruker opp forsøkene sine, må du selv gå og spille den av på nytt.
  • Filtrer hendelser ved abonnement heller enn i handleren din, og hold deg under grensen på 5 abonnement per konto.

Du er ikke forpliktet til å hoste et endepunkt for å motta disse hendelsene: koblingen setter opp abonnementet for deg og starter flyten direkte ved en kuvertbegivenhet. Se Certyneo for Power Automate og Microsoft 365.

For å gå videre

Klar til å koble sammen systemene?

Webhooks og REST API er inkludert fra Standard-planen. Opprett kontoen din, generer en `sk_test_`-nøkkel og koble endepunktet ditt i sandkasse før du går til produksjon.