Веб-буки отримуйте підпис події в реальному часі
Налаштуйте 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.
| Спроба | Період перед спробою | Час минуло з події |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 15 min | 21 min |
| #5 | + 1 h | 1 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.