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

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

Конфигурирайте HTTPS URL в вашия dashboard на Certyneo и получавайте подписана HMAC-SHA256 POST всеки път, когато възникне събитие на вашите пликове: подпис, отказ, изтичане на срок. 11 поддържани събития, 5 опита за доставка с експоненциален backoff, криптографска верификация в 8 реда код.

< 5s

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

5x

Общ брой опити за доставка, разпределени в около 1 ч 20 мин

HMAC-SHA256

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

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

12-те събития по-долу покриват целия жизнен цикъл на плик Certyneo. Активирайте интересуващите вас събития в Параметри → Webhooks, игнорирайте останалите — абонаментът е детайлен по събитие.

СъбитиеИзбухване
envelope.createdСъздава се плик (по UI, API или шаблон) полезен за синхронизиране на запис на CRM страницата още при създаването му.
envelope.sentПолучава се първия имейл, който започва активния цикъл на подписване.
envelope.completedВсички подписващи са подписали и запечатаният eIDAS PDF е съхранен. Payload съдържа signedDocumentUrl, предварително подписана връзка валидна 7 дни; в противния случай GET /v1/envelopes/id/signed-document и пътеката на одит чрез GET /v1/envelopes/id/audit-trail.
envelope.declinedПодписващ е отказал плика. Адресът на отказалия е в `data.declinedBy` и причината, ако е въведена, е в `data.reason`.
envelope.voidedПощенският плик е бил анулиран от издателя преди да е подписан.
envelope.expiredДатата на изтичане на плика е минала без пълен подпис. Попитайте GET /v1/envelopes/id, за да разберете кои подписващи липсват.
envelope.returned_to_senderПодписващ е върнал плика на издателя за корекция, без да го отказва. Причината е в `data.reason` и авторът на връщането е в `data.returnedBy`.
envelope.resubmittedИздателят е коригирал и след това върнал плик, който е бил предварително върнат. Маркира възобновяването на цикъла на подписване.
recipient.signedОтделен подписващ е подписал (но не непременно всички). Полезно е за следене на напредъка и преход към следващия етап на последователен workflow. Внимание: запечатаният PDF все още не съществува на този етап, дори за последния подписващ — изтегляне, инициирано тук, връща HTTP 409. Използвайте envelope.completed за документа.
recipient.viewedЕдин от подписалите е отворил връзката, без да е подписал, което е полезно за целеви търговски подбуди.
recipient.approvedОдобритель е валидирал пликът без да постави подпис (вътрешен работен поток на валидация). Адресът е в `data.approvedBy`.
recipient.bouncedПоща сървърът на получателя окончателно отказа покана или повторно изпращане (несъществуваща пощенска кутия, мъртва домейн). Получателят получава статус BOUNCED и автоматичното повторно изпращане спира. Адресът е в `data.recipientEmail`: коригирайте го и след това преизпратете пликът.

recipient.signed не означава "документът е достъпен"

recipient.signed се издава за ВСЕКИ подписващ, в момента, когато завърши подписа си — включително и последния, преди запечатаният PDF да бъде събран и съхранен. Изтегляне, задействано от този handler, винаги получава HTTP 409 "Signed document not available until the envelope is COMPLETED". Това не е грешка — това е "още не е готово". Абонирайте се за envelope.completed, за да получите документа, и запазете recipient.signed, за да следите напредъка (кой е подписал и кога).

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

Всички доставки споделят същата схема JSON на най-високо ниво: `event`, `data` и `timestamp`. Името на събитието е повторено и в заглавката `X-Certyneo-Event`, което позволява маршрутизиране дори преди разбора на тялото. Съдържанието на `data` варира в зависимост от събитието, но винаги остава плосък обект с прости стойности — никога масив или вложен обект. Ето една пълна доставка `envelope.completed`.

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 е предварително подписана връзка, валидна 7 дни от събитието: тя избягва второ удостоверено повикване за получаване на PDF. Тя отсъства — а не null — ако предварителното подписване е неудачно, или на пликът QES, завършен без съхранен документ; тогава преминете през GET /v1/envelopes/id/signed-document, което остава източникът на истина. Внимание и при ручното повтаряне от dead-letter queue повече от 7 дни след събитието: връзката в payload е изтекла, API крайната точка не е.

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

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

Всяка заявка е подписана с вашата тайна на webhook (показана само веднъж при създаване на абонамента). Подписът се предава в заглавката `X-Certyneo-Signature` : това е HMAC-SHA256 на сурово тяло на заявката, кодирано в шестнадесетична система, без префикс или времеви печат. ВСЕГДА проверявайте подписа преди обработка на полезния товар — без тази стъпка всеки може да подправи събитие и да извика вашия крайна точка.

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)

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

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

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

Ако вашата крайна точка отнема твърде много време да отговори, откаже връзката или върне 5xx (или 429), ние повтаряме със експоненциален backoff: 5 опита общо, за приблизително 1 ч 20 мин. След петия, събитието отива в dead-letter queue — видимо и възпроизводимо от вашия панел управление, но вече не се повтаря автоматично. Явен отказ (401, 403, 404, 410, 422…) никога не се повтаря: отговорът не би се променил, събитието отива директно в dead-letter queue.

ОпитДелай преди опитВреме изминуло от събитие
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 h 21

Посочените времеви периоди са минимални: сканирането на повторни опити е синхронизирано чрез cron, така че опитът може да се осъществи малко след теоретичното време. Напуснатите събития са в списък в Webhooks → Неуспехи, с бутон за ръчно възпроизвеждане и без ограничение на периода на съхранение. Крайна точка, която има пет последователни окончателни неуспеха — или която отговаря с 404 / 410 — се деактивира автоматично и вие сте уведомени по имейл: реактивирайте я след коригирането, счетчикът се върача към нула (всяко успешно доставяне също го връща към нула).

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

Първо създайте абонамент — отговорът съдържа `secret` необходимата за HMAC проверка, показана само веднъж. От Параметри → Webhooks бутонът « Тест » след това изпраща POST подписан точно като истинска доставка и ви показва сурова отговора от вашия сървър : това е най-бързия начин за валидиране на вашия обработчик, локално чрез ngrok или в непрекъсната интеграция.

# 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 адресът трябва да бъде публично достъпен чрез HTTPS : частен адрес или localhost се отхвърля от защитата на SSRF при създаване на абонамента.

Абонаментите са разделени по среда, както и ключовете : уеб кука, създадена с ключ sk_test_, получава само събитията от тестови пликове, уеб кука, създадена с ключ sk_live_, получава само събитията от реални пликове. Тестов пликове следователно никога не достигат вашия URL адрес за производство, а всеки товар указва "sandbox" (true или false).

6 практически правила

  • Проверка на HMAC преди всяко четене на тялото използвайте време-безопасно сравнение.
  • Дедупликирайте на заглавката `X-Certyneo-Delivery-Id` (идентична с `id` в тялото) като съхранявате вече видени идентификатори в база данни — преиграване може да преизпрати събитие, което вече сте обработили, ако вашият 2xx се е загубил, със същия идентификатор при всеки опит.
  • Отговорете HTTP 2xx в рамките на максимум 10 секунди, след това обработете асинхронно (опашка). Отвъд това доставката се прекъсва и се отчита като неуспех.
  • Logger raw body + full debug signature HMAC проверката често се проваля при BOM или невидими бели пространства.
  • Наблюдавайте страницата Webhooks → Неуспехи: вие сте уведомени по имейл, ако вашата крайна точка е деактивирана, но не и събитие по събитие — събитие, което изчерпва своите опити, е ваша отговорност да го възпроизведете.
  • Филтрирайте събитията при абонамента, а не в вашия обработчик, и останете под ограничението от 5 абонамента на акаунт.

Не е необходимо да хостирате крайна точка за получаване на тези събития: конекторът прави абонамента за вас и стартира потока директно при събитие на пликове. Вижте Certyneo за Power Automate и Microsoft 365.

Да продължим

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

Webhooks и REST API са включени от плана Стандарт. Създайте вашия акаунт, генерирайте `sk_test_` ключ и свържете вашата крайна точка в sandbox преди преминаване на производство.