Преход към основното съдържание
Certyneo
Документация на разработчика

Webhooks получавайте събития за подписване в реално време

Настрои HTTPS URL в таблото за данни Certyneo и получи подписан POST HMAC-SHA256 веднага щом се случи нещо на вашите пликове: подписване, отказ, изтичане на срока. 8 събития, поддържани, експоненциален ретроу на 6 опита, криптографска проверка с 8 реда код.

< 5s

Средна продължителност на доставката след събитието

5x

Опити при неуспех (до ~ 9 часа по-късно)

HMAC-SHA256

Алгоритм за подписване на всяко заявление

Каталог на събитията

8 събития по-долу покриват целия жизнен цикъл на един Certyneo плик.

СъбитиеИзбухване
envelope.createdСъздава се плик (по UI, API или шаблон) полезен за синхронизиране на запис на CRM страницата още при създаването му.
envelope.sentПолучава се първия имейл, който започва активния цикъл на подписване.
envelope.completedВсички подписали са подписали. eIDAS запечатаният PDF и аудитният следа са достъпни чрез `proof_pdf_url` в полезния товар.
envelope.declinedПричината за отказ (ако е предоставена от подписалия) е в `data.decline_reason`.
envelope.voidedПощенският плик е бил анулиран от издателя преди да е подписан.
envelope.expiredИзтичането на срока на валидност на плика е минало без пълен подпис.
recipient.signedПолезно за последователни работни потоци: задействане на преминаване към следващия подписал.
recipient.viewedЕдин от подписалите е отворил връзката, без да е подписал, което е полезно за целеви търговски подбуди.

Формат на полезния товар

Всички събития споделят една и съща JSON схема: `event`, `envelope_id`, `occurred_at`, `data`. Съдържанието на `data` варира в зависимост от събитието (полетата са документирани по събитие в 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"
  }
}

Пайлоудът е кодиран UTF-8, без BOM. Подписът HMAC се изчислява върху грубото тяло, както е изпратено не променяйте пространствата, JSON ре-парсирането често променя реда на ключовете и нарушава проверката.

Проверка на HMAC подписа

Всеки запитване се подписва с вашия уебхуков секрет (видим само веднъж при създаване в таблото, след което е криптиран в почивка).<timestamp>,v1=<hex_hmac>Винаги проверявайте подписа преди да обработите полезния товар. Без тази стъпка всеки може да измисли събитие и да се обади на крайната точка.

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)

Често срещана грешка

Използвайте функция за време-безопасност (`crypto.timingSafeEqual` в Node, `hmac.compare_digest` в Python). В противен случай, разликата в времето на сравнение между двата подписа постепенно разкрива тайната на пациент атакуващ (тиминг атака).

Политика за повторни опити

Ако крайната точка отговори с нещо друго, освен HTTP 2xx (timeout, 5xx, connection refused), ние ще се включим в експоненциален backoff. Общо 6 опити за ~9 часа. След 6 опита събитието се маркира `failed` в таблото на вашия webhook и се изпраща предупредителен имейл.

ОпитСтрува се, чеИзминало време
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 36

Ако искате да предадете ръчно пропуснато събитие, в таблото на уебхука има бутон Re-deliver за всяка пропусната доставка до 7 дни след окончателния провал.

Изпитване без изпращане на истински плик

Крайната точка `/v1/webhooks/test` приема URL и тип на събитие, а ПОСТИВА фиктивен полезен товар, подписан точно като истински.

# 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 практически правила

  • Проверка на HMAC преди всяко четене на тялото използвайте време-безопасно сравнение.
  • Процесиране на всеки `envelope_id` × `event` само веднъж, като се съхраняват вече видяните ID в базата (възвръщането може да възстанови същия събитие).
  • Отговори на HTTP 200 в рамките на 5 секунди, а след това обработи асинхронно (в опашка).
  • Logger raw body + full debug signature HMAC проверката често се проваля при BOM или невидими бели пространства.
  • Включете опашка за "неуспешни" събития, за да прехвърляте ръчно в случай на инцидент на вашия отдел.
  • Търпи 5 минути откъсване между `t=` и времето на приемане и след това, отхвърляне, за да блокира ретрансляциите.

Да продължим

Готови ли сте да свържете системите си?

Създайте безплатен акаунт за 2 минути настройката на webhook е налична от стартиращия план (безплатно, 5 пликове/месец).