Перейти до основного вмісту
Certyneo
Документація розробника

Веб-буки отримуйте підпис події в реальному часі

Налаштуйте URL HTTPS у вашій інформаційній панелі Certyneo та отримуйте підписаний HMAC-SHA256 POST щоразу, коли на ваших конвертах відбувається подія: підпис, відмова, закінчення. Підтримується 11 подій, 5 спроб доставки з експоненціальним відступом, криптографічна перевірка за 8 рядків коду.

< 5s

Середній термін доставки після події

5x

Спроби доставки всього, розподілені приблизно на 1 год 20 хв

HMAC-SHA256

Алгоритм підписання кожного запиту

Каталог подій

12 подій нижче охоплюють весь життєвий цикл конверта Certyneo. Активуйте цікавлять вас у Параметри → Вебхуки, ігноруйте інші — підписка гранульована за подіями.

ПодіяВибух
envelope.createdСтворюється конверт (за UI, API або шаблоном) корисний для синхронізації запису на CRM-сторі з моменту створення.
envelope.sentЗаписка надсилається підписникам (перший електронний лист відправлений).
envelope.completedУсі підписувачі підписали й запечатаний PDF eIDAS зберіганий. 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Окремий підписувач підписав (але не обов'язково всі). Корисно для відстеження прогресу та переходу до наступного етапу послідовного робочого процесу. Увага: запечатаний PDF ще не існує на цьому етапі, навіть для останнього підписувача — завантаження, запущене звідси, повертає HTTP 409. Перейдіть через envelope.completed для документа.
recipient.viewedПідписант відкрив підписний посилання, не підписуючи його, що дуже корисно для цілеспрямованого відновлення торгівлі.
recipient.approvedАвторизатор затвердив конверт без накладення підпису (робочий процес внутрішньої валідації). Адреса знаходиться в `data.approvedBy`.
recipient.bouncedПоштовий сервер одержувача назавжди відхилив запрошення або повторну спробу (скринька не існує, домен мертвий). Одержувач переходить у статус BOUNCED і автоматичні повторні спроби припиняються. Адреса знаходиться в `data.recipientEmail`: виправте її та повторно надішліть конверт.

recipient.signed не означає «документ доступний»

recipient.signed випускається для КОЖНОГО підписувача в момент, коли він завершує свій підпис — включаючи останнього, до того як запечатаний PDF буде зібраний і збережений. Завантаження, запущене з цього обробника, завжди отримує 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, який залишається джерелом істини. Також зверніть увагу на ручне відтворення з черги недоставлених повідомлень більш ніж за 7 днів після події: посилання в корисному навантаженню має закінчитися, кінцева точка API — ні.

Підпис HMAC обчислюється на грубому тілі, як відправлено не змінюйте пробіли, перепараси JSON часто змінюють порядок ключів і порушують перевірку.

Перевірка підпису HMAC

Кожен запит підписується вашим webhook-секретом (показаний лише один раз під час створення підписки). Підпис передається в заголовку `X-Certyneo-Signature` : це HMAC-SHA256 від сирого тіла запиту, закодований у шістнадцятковому форматі, без префіксу чи часової мітки. ЗАВЖДИ перевіряйте підпис перед обробкою корисного навантаження — без цього кроку будь-хто може підробити подію та викликати вашу endpoint.

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)

Часточутні помилки

Не використовуйте `===` або `==` для порівняння очікуваного та отриманого підписів. Використовуйте функцію timing-safe (`crypto.timingSafeEqual` в Node, `hmac.compare_digest` в Python).

Політика перепробування

Якщо ваш endpoint надто довго відповідає, відмовляє в з'єднанні або повертає 5xx (або 429), ми повторюємо спроби за експоненціальною затримкою: 5 спроб усього, протягом приблизно 1 год 20 хв. Після п'ятої спроби подія потрапляє в чергу dead-letter — її можна переглянути та повторити з вашої панелі керування, але автоматичних повторів не буде. Явна відмова (401, 403, 404, 410, 422…) ніколи не повторюється: відповідь не зміниться, подія одразу потрапляє в чергу dead-letter.

СпробаПеріод перед спробоюЧас минуло з події
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 h 21

Вказані затримки є мінімальними: перевірка повторів здійснюється за розкладом cron, тому спроба може піти трохи пізніше за теоретичну час. Закинуті події перелічені в Webhooks → Помилки з кнопкою ручного повтору і без обмеження терміну зберігання. Endpoint, який зазнав п'ять остаточних помилок — або який повертає 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 секунд, потім обробляйте асинхронно (черга). Після цього доставка обривається та рахується як невдача.
  • Зверніть до нього повний документ, який ви маєте.
  • Стежте за сторінкою Webhooks → Помилки: вам надходить повідомлення електронною поштою, якщо ваш endpoint деактивується, але не подія за подією — якщо подія вичерпує свої спроби, вам потрібно самостійно її повторити.
  • Фільтруйте eventi під час підписки, а не в обробнику, і залишайтеся під лімітом 5 підписок на акаунт.

Вам не потрібно розміщувати кінцеву точку для отримання цих подій: конектор робить підписку за вас і запускає потік безпосередньо під час події конверту. Див. Certyneo для Power Automate та Microsoft 365.

Щоб піти далі

Готові підключити свої системи?

Webhooks та REST API включені, починаючи з плану Standard. Створіть акаунт, згенеруйте ключ `sk_test_` та під'єднайте вашу endpoint в sandbox перед переходом у production.