Webhooks primiți evenimentele de semnare în timp real
Configurați un URL HTTPS în tabloul de bord Certyneo și primiți un POST semnat HMAC-SHA256 de îndată ce apare un eveniment pe plicurile dvs.: semnătură, refuz, expirare. 11 evenimente acceptate, 5 tentative de livrare în backoff exponențial, verificare criptografică în 8 linii de cod.
< 5s
Durata medie de livrare după eveniment
5x
Tentative de livrare în total, distribuite pe aproximativ 1 h 20
HMAC-SHA256
Algorithm de semnare pentru fiecare cerere
Catalogul evenimentelor
Cele 12 evenimente de mai jos acoperă ciclul de viață integral al unui plic Certyneo. Activați-le pe acelea care vă interesează în Setări → Webhooks, ignorați pe celelalte — abonamentul este granular pe eveniment.
| 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 | Toți semnatarii au semnat și PDF-ul sigilat eIDAS este stocat. Payload-ul poartă signedDocumentUrl, o legătură pre-semnată valabilă 7 zile; în caz contrar, GET /v1/envelopes/id/signed-document, și pista de audit via GET /v1/envelopes/id/audit-trail. |
envelope.declined | Un semnatar a refuzat plicul. Adresa celui care a refuzat este în `data.declinedBy` și motivul, dacă a fost introdus, în `data.reason`. |
envelope.voided | Putumul a fost anulat de către emitent înainte de semnarea completă. |
envelope.expired | Data de expirare a plicului a trecut fără semnătură completă. Interogați GET /v1/envelopes/id pentru a cunoaște semnatarii lipsă. |
envelope.returned_to_sender | Un semnatar a returnat plicul emitentului pentru corectare, fără a îl refuza. Motivul este în `data.reason` și autorul returului în `data.returnedBy`. |
envelope.resubmitted | Expediatorul a corectat și apoi a returnat un plic anterior returnat. Marchează reluarea ciclului de semnare. |
recipient.signed | Un semnatar individual a semnat (dar nu neapărat toți). Util pentru a urmări progresul și a continua la pasul următor al unui workflow secvențial. Atenție: PDF-ul sigilat nu există încă la această etapă, nici măcar pentru ultimul semnatar — o descărcare lansată aici returnează un HTTP 409. Treceți prin envelope.completed pentru document. |
recipient.viewed | Un semnatar a deschis legătura de semnare fără să semneze încă, utilă pentru revizuirea comercială ţintită. |
recipient.approved | Un aprobator a validat plicul fără a-i aplica o semnătură (workflow de validare internă). Adresa se află în `data.approvedBy`. |
recipient.bounced | Serverul de mesagerie al unui destinatar a refuzat definitiv invitația sau retragerea (cutie inexistentă, domeniu mort). Destinatarul trece la statutul BOUNCED și retragerile automate se opresc. Adresa se află în `data.recipientEmail`: corectați-o și trimiteți din nou plicul. |
recipient.signed nu înseamnă « document disponibil »
recipient.signed este emis pentru FIECARE semnatare, în momentul în care finalizează semnatura — inclusiv ultimul, înainte ca PDF-ul sigilat să fie asamblat și stocat. O descărcare declanșată din acest handler primește deci întotdeauna HTTP 409 « Signed document not available until the envelope is COMPLETED ». Nu este o eroare : este un « nu este încă gata ». Abonați-vă la envelope.completed pentru a recupera documentul, și păstrați recipient.signed pentru a urmări progresul (cine a semnat, și când).
Formatul încărcăturii utile
Toate livrările împărtășesc același schema JSON top-level: `event`, `data` și `timestamp`. Numele evenimentului se repetă și în antetul `X-Certyneo-Event`, ceea ce permite rutarea înainte chiar de a parsa corpul. Conținutul `data` variază în funcție de eveniment, dar rămâne întotdeauna un obiect plat de valori simple — niciodată matrice sau obiect imbricate. Iată o livrare `envelope.completed` completă.
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 este un link pre-semnat valabil 7 zile de la eveniment: evită un al doilea apel autentificat pentru a recupera PDF-ul. Lipsește — și nu este null — dacă pre-semnarea a eșuat, sau pe un plic QES completat fără document stocat; retrageți-vă apoi prin GET /v1/envelopes/id/signed-document, care rămâne sursa de adevăr. Atenție și la redare manuală din coadă de scrisori moarte mai mult de 7 zile după eveniment: linkul din payload este expirat, endpoint-ul API nu.
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 dvs. webhook (afișat o singură dată la crearea abonamentului). Semnătura se transmite în antetul `X-Certyneo-Signature`: este HMAC-SHA256 al corpului brut al cererii, codificat în hexazecimal, fără prefix și fără timestamp. Verificați ÎNTOTDEAUNA semnătura înainte de a procesa payload-ul — fără acest pas, oricine poate falsifica un eveniment și apela endpoint-ul dvs.
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)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ă endpoint-ul dvs. durează prea mult să răspundă, refuză conexiunea sau returnează un 5xx (sau un 429), vom reîncerca conform unui backoff exponențial: 5 încercări în total, pe aproximativ 1 h 20. După a cincea, evenimentul merge în dead-letter queue — consultabil și redisponibil din tabloul dvs., dar nu mai reîncercat automat. Un refuz explicit (401, 403, 404, 410, 422…) nu este niciodată reîncercat: răspunsul nu s-ar schimba, evenimentul merge direct în dead-letter queue.
| Încercare | Perioadă înainte de încercare | Timpul trecut de la eveniment |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 15 min | 21 min |
| #5 | + 1 h | 1 h 21 |
Termenele indicate sunt minime: scanarea retry este cadenată de un cron, deci o încercare poate pleca ușor după ora teoretică. Evenimentele abandonate sunt listate în Webhooks → Eșecuri, cu un buton de redisponibilitate manuală și fără limită de durată de conservare. Un endpoint care înlănțuie cinci eșecuri definitive — sau care răspunde 404 / 410 — este dezactivat automat, și sunteți notificat prin email: reactivați-l odată corectat, contorul restartează de la zero (orice livrare reușită îl resetează și la zero).
Testarea fără trimiterea unei plicuri reale
Creați mai întâi un abonament — răspunsul conține `secret`-ul necesar pentru verificarea HMAC, afișat o singură dată. Din Setări → Webhooks, butonul « Testare » trimite apoi un POST semnat exact ca o livrare adevărată și vă afișează răspunsul brut al serverului dvs.: acesta este cel mai rapid mod de a valida handler-ul dvs., local via ngrok sau în integrare continuă.
# 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-ul trebuie să fie accesibil public prin HTTPS: o adresă privată sau localhost este respingă de protecția SSRF la momentul creării abonamentului.
Abonamentele sunt izolate pe mediu, ca și cheile: un webhook creat cu o cheie sk_test_ primește doar evenimentele din plicurile de test, un webhook creat cu o cheie sk_live_ primește doar cele din plicurile reale. Un plic de test nu ajunge deci niciodată la URL-ul dvs. de producție, și fiecare sarcină utilă indică „sandbox" (true sau false).
6 practici de respectat
- Verifică semnătura HMAC ÎN PRECÂTĂ orice citire a corpului folosește o comparație timing-safe.
- Deduplicați pe antetul `X-Certyneo-Delivery-Id` (identic cu `id` în corp) stocând identificatorii deja văzuți în bază — o reluare poate relivra un eveniment pe care l-ați tratat deja dacă răspunsul dumneavoastră 2xx s-a pierdut, cu același identificator la fiecare încercare.
- Răspundeți HTTP 2xx în maxim 10 secunde, apoi procesați asincron (coadă). Depășit acest termen, livrarea este tăiată și contabilizată ca un eșec.
- Logarea corpului brut + semnătura completă în debug verificarea HMAC eşuează adesea pe un BOM sau spaţiu alb invizibil.
- Monitorizați pagina Webhooks → Eșecuri: sunteți notificat prin email dacă endpoint-ul dvs. este dezactivat, dar nu eveniment cu eveniment — un eveniment care-și epuizează încercările, vă revine vouă să-l redisponibilizați.
- Filtrați evenimentele la abonare mai degrabă decât în handler-ul dvs., și rămâneți sub limita de 5 abonamente per cont.
Nu sunteți obligat să găzduiți un endpoint pentru a primi aceste evenimente: conectorul stabilește abonamentul pentru dvs. și lansează fluxul direct pe un eveniment de plic. Vezi Certyneo pentru Power Automate și Microsoft 365.
Pentru a merge mai departe
Sunteţi gata să vă conectaţi sistemele ?
Webhook-urile și API REST sunt incluse de la planul Standard. Creați-vă contul, generați o cheie `sk_test_` și conectați endpoint-ul dvs. în sandbox înainte de a trece în producție.