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.created | O învelișă este creată (prin UI, API sau template) utilă pentru a sincroniza o înregistrare pe partea CRM de la crearea. |
envelope.sent | Putumul este trimis semnatarilor (primul e-mail trimis). Marchează începutul ciclului de semnare activ. |
envelope.completed | Toate semnatarele au semnat. PDF-ul sigilat eIDAS și auditul traseului sunt disponibile prin `proof_pdf_url` în payload. |
envelope.declined | Un semnatar a refuzat plicul. Motivul refuzului (dacă este furnizat de semnatar) este în `data.decline_reason`. |
envelope.voided | Putumul a fost anulat de către emitent înainte de semnarea completă. |
envelope.expired | Data expirării plicului a trecut fără semnătura completă. Lista semnatarilor lipsiți este în `data.missing_signers`. |
recipient.signed | Utilizat pentru fluxurile de lucru secvențiale: declanșarea trecerii la următorul semnatar. |
recipient.viewed | Un 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.
| Încercare | Perioada de timp cumulată | Timpul trecut |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 30 min | 36 min |
| #5 | + 2 h | 2h 36 |
| #6 | + 6 h | 8h 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ă).