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

Веб-хоки получать подписи событий в режиме реального времени

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

< 5s

Среднее время доставки после события

5x

Всего попыток доставки, распределённые примерно на 1 час 20 минут

HMAC-SHA256

Алгоритм подписи каждого запроса

Каталог событий

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

СобытиеВспышка
envelope.createdСоздается конверт (по UI, API или шаблону) полезный для синхронизации записи на стороне CRM с момента создания.
envelope.sentЗаявление отправляется подписантам (первое отправленное электронное письмо).
envelope.completedВсе подписанты подписали документ, и запечатанный PDF eIDAS хранится. Полезная нагрузка содержит 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 дней после события: ссылка в полезной нагрузке истекла, endpoint 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). Иначе разрыв времени сравнения между двумя подписями постепенно раскрывает секрет пациентскому атакующему (timing attack).

Политика повторного просмотра

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

ПопыткаПериод до попыткиВремя прошедшее с момента события
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 h 21

Указанные сроки являются минимальными: сканирование повторных попыток синхронизируется cron-заданием, поэтому попытка может отправиться немного позже теоретического времени. Заброшенные события отображаются в разделе Webhooks → Ошибки с кнопкой ручного воспроизведения и без ограничения срока хранения. Endpoint, который получает пять окончательных сбоев подряд — или который отвечает 404 / 410 — автоматически отключается, и вы получите уведомление по email: повторно активируйте его после исправления, счётчик обнулится (любая успешная доставка также обнулит его).

Проверка без отправки настоящего конверта

Сначала создайте подписку — ответ содержит `secret`, необходимый для проверки HMAC, отображаемый один раз. Из Параметры → Webhooks кнопка «Test» (Тестировать) затем отправляет 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 секунд, затем обрабатывайте асинхронно (очередь). Сверх этого доставка прерывается и считается ошибкой.
  • Регистрация грубого тела + полная подпись в дебгу проверка HMAC часто терпит неудачу на BOM или невидимом белом пространстве.
  • Следите за страницей Webhooks → Ошибки: вы получите email-уведомление, если ваша endpoint отключена, но не за каждым событением — если событие исчерпает свои попытки, вам нужно будет самостоятельно его воспроизвести.
  • Фильтруйте события при подписке, а не в обработчике, и оставайтесь под лимитом в 5 подписок на аккаунт.

Вы не обязаны размещать конечную точку для получения этих событий: соединитель создает подписку за вас и запускает поток непосредственно при событии конверта. См. Certyneo для Power Automate и Microsoft 365.

Чтобы пойти дальше

Готовы подключить свои системы?

Вебхуки и API REST включены начиная с плана Standard. Создайте свой аккаунт, сгенерируйте ключ `sk_test_` и подключите ваш endpoint в режиме песочницы перед переходом в продакшн.