Mergeți la conținutul principal
Certyneo
Documentația dezvoltatorului

Webhooks primiți evenimentele de semnare în timp real

Configurează o adresă HTTPS în panoul de bord Certyneo și primește un POST semnat HMAC-SHA256 de îndată ce un eveniment apare pe plicurile tale: semnătură, refuz, expirare. 8 evenimente susținute, reîncercare exponențială la 6 încercări, verificare criptografică în 8 linii de cod.

< 5s

Durata medie de livrare după eveniment

5x

Încercări în caz de eșec (până la ~ 9h mai târziu)

HMAC-SHA256

Algorithm de semnare pentru fiecare cerere

Catalogul evenimentelor

Cele 8 evenimente de mai jos acoperă întregul ciclu de viață al unei plicuri Certyneo.

EvenimentÎnceputul
envelope.createdO învelișă este creată (prin UI, API sau template) utilă pentru a sincroniza o înregistrare pe partea CRM de la crearea.
envelope.sentPutumul este trimis semnatarilor (primul e-mail trimis). Marchează începutul ciclului de semnare activ.
envelope.completedToate semnatarele au semnat. PDF-ul sigilat eIDAS și auditul traseului sunt disponibile prin `proof_pdf_url` în payload.
envelope.declinedUn semnatar a refuzat plicul. Motivul refuzului (dacă este furnizat de semnatar) este în `data.decline_reason`.
envelope.voidedPutumul a fost anulat de către emitent înainte de semnarea completă.
envelope.expiredData expirării plicului a trecut fără semnătura completă. Lista semnatarilor lipsiți este în `data.missing_signers`.
recipient.signedUtilizat pentru fluxurile de lucru secvențiale: declanșarea trecerii la următorul semnatar.
recipient.viewedUn semnatar a deschis legătura de semnare fără să semneze încă, utilă pentru revizuirea comercială ţintită.

Formatul încărcăturii utile

Toate evenimentele au aceeași schemă JSON de nivel superior: `event`, `envelope_id`, `occurred_at`, `data`. Conținutul `data` variază de la eveniment la eveniment (câmpuri documentate pe eveniment în referința API).

{
  "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"
  }
}

Semnătura HMAC este calculată pe corpul brut așa cum a fost trimis nu modificați spațiile, re-parsingul JSON schimbă adesea ordinea cheilor și rupe verificarea.

Verifică semnătura HMAC

Fiecare cerere este semnată cu secretul webhook (vizibil o singură dată la crearea în dashboard, apoi criptat la repaus).<timestamp>,v1=<hex_hmac>Verificaţi întotdeauna semnătura înainte de a procesa încărcătura fără acest pas, oricine poate falsifica un eveniment şi să-ţi sune punctul de terminare.

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)

Greșeală frecventă

Foloseste o functie de timing-safe (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python). Altfel, diferenta de timp de comparare intre doua semnaturi va dezvaluie secretul unui atacator pacient (timing attack).

Politica de retry

Dacă punctul de terminare răspunde altfel decât un HTTP 2xx (timeout, 5xx, connection refused), sunăm după un backoff exponențial. În total 6 încercări în ~9 ore. La sfârșitul celor 6, evenimentul este marcat `failed` în dashboardul webhook și un e-mail de alertă este trimis.

ÎncercarePerioada de timp cumulatăTimpul trecut
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 36

Dacă doriți să releazați manual un eveniment ratat, dashboardul webhook oferă un buton Re-deliver pe fiecare livrare ratată disponibil timp de 7 zile după eşecul final.

Testarea fără trimiterea unei plicuri reale

Endpointul `/v1/webhooks/test` acceptă un URL și un tip de eveniment, și POSTe o payload fictiv semnat exact ca un real.

# 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 practici de respectat

  • Verifică semnătura HMAC ÎN PRECÂTĂ orice citire a corpului folosește o comparație timing-safe.
  • Procesarea fiecărei `envelope_id` × `event` o singură dată, stocând ID-urile deja văzute în bază (reîntoarcerile pot reveni la același eveniment).
  • Răspunde la HTTP 200 în maximum 5 secunde, apoi procesează asincron (coadă). Altfel, vei fi în timpul liber și va fi numărat ca o reîncercare.
  • Logarea corpului brut + semnătura completă în debug verificarea HMAC eşuează adesea pe un BOM sau spaţiu alb invizibil.
  • Conectaţi o coadă de dead-letter la evenimentele `failed` pentru a releaza manual în cazul în care un incident se întâmplă în partea dvs. de serviciu.
  • Tolerați o diferență de 5 minute între `t=` și ora de recepție și mai departe, respingeți pentru a bloca replicile.

Pentru a merge mai departe

Sunteţi gata să vă conectaţi sistemele ?

Creați un cont gratuit în 2 minute setarea webhook este disponibilă de la planul Starter (gratuit, 5 plicuri/lună).