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

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.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.completedToț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.declinedUn semnatar a refuzat plicul. Adresa celui care a refuzat este în `data.declinedBy` și motivul, dacă a fost introdus, în `data.reason`.
envelope.voidedPutumul a fost anulat de către emitent înainte de semnarea completă.
envelope.expiredData 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_senderUn semnatar a returnat plicul emitentului pentru corectare, fără a îl refuza. Motivul este în `data.reason` și autorul returului în `data.returnedBy`.
envelope.resubmittedExpediatorul a corectat și apoi a returnat un plic anterior returnat. Marchează reluarea ciclului de semnare.
recipient.signedUn 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.viewedUn semnatar a deschis legătura de semnare fără să semneze încă, utilă pentru revizuirea comercială ţintită.
recipient.approvedUn aprobator a validat plicul fără a-i aplica o semnătură (workflow de validare internă). Adresa se află în `data.approvedBy`.
recipient.bouncedServerul 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.

ÎncercarePerioadă înainte de încercareTimpul trecut de la eveniment
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 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.