Gå till huvudinnehål
Certyneo
Utvecklardokumentation

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ändelseUtbrott
envelope.createdEtt omslag skapas (via UI, API eller mall) användbart för att synkronisera en CRM-sida från skapandet.
envelope.sentDet är då som det första e-postmeddelandet skickas till undertecknarna.
envelope.completedAlla undertecknare har skrivit under. eIDAS-förseglade PDF och audit trail finns tillgängliga via `proof_pdf_url` i payload.
envelope.declinedEn 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.voidedDet är olik `expired` (människa vs timeout).
envelope.expiredEnklaffret har gått ut utan fullständig underskrift.
recipient.signedEn enskild undertecknare har skrivit under (men inte alla).
recipient.viewedEn 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ökSammanlagd tidTid som gått
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 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).