Přejít na hlavní obsah
Certyneo
Dokumenty vývojáře

Webhooks přijímat signály v reálném čase

Nastavte HTTPS URL na svém Certyneo dashboardu a obdržíte POST podepsaný HMAC-SHA256 hned, jakmile se na vašich obálkách stane událost: podpis, odmítnutí, vypršení platnosti. 8 podporovaných událostí, exponenciální retry na 6 pokusů, 8 řádků šifrovacího ověření.

< 5s

Průměrná doba dodání po události

5x

Zkoušky v případě selhání (do ~ 9 hodin později)

HMAC-SHA256

Podpisový algoritmus pro každou žádost

Katalog událostí

8 následujících událostí pokrývá celý životní cyklus obalu Certyneo. Aktivujte ty, které vás zajímají v Nastavení → Webhooks, ostatní ignorujte předplatné je granulované na událost.

UdálostVypuknutí
envelope.createdVytvoří se obálka (v rámci UI, API nebo šablony) užitečná pro synchronizaci záznamu na straně CRM hned po jeho vytvoření.
envelope.sentObálka je zaslána signatářům (první e-mail odeslán).
envelope.completedVšechny signatáře podepsali. eIDAS zapečetěný PDF a auditní stopa jsou k dispozici prostřednictvím `proof_pdf_url` v nákladu.
envelope.declinedDůvod pro odmítnutí (pokud je podepsaný signatářem) je uveden v `data.decline_reason`.
envelope.voidedObálka byla zrušena před úplným podpisem.
envelope.expiredDatum vypršení platnosti obálky uplynulo bez úplného podpisu. Seznam chybějících signatářů je v `data.missing_signers`.
recipient.signedPoužitelné pro postupné pracovní postupy: spouštět přechod na dalšího signatáře.
recipient.viewedJeden z signatářů otevřel podpisový odkaz, aniž by to ještě podepsal, což je užitečné pro cílené obchodní revize.

Formát užitečného zatížení

Všechny události sdílejí stejný top-level JSON šablon: `event`, `envelope_id`, `occurred_at`, `data`. Obsah `data` se liší podle události (poly dokumentované podle události v odkazu 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"
  }
}

HMAC podpis je vypočítán na hrubém těle, jak byl odeslán.

Zkontrolujte podpis HMAC

Každá žádost je podepsána vaším webhookovým tajemstvím (je viditelná pouze jednou při vytvoření v přístrojovém panelu a pak šifrována na odpočinek).<timestamp>,v1=<hex_hmac>`. Vždy zkontrolujte podpis před zpracováním užitečného zatížení bez tohoto kroku může kdokoliv vytvořit událost a zavolat váš koncový bod.

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)

Časté chyby

Nepoužívejte `===` nebo `==` k porovnání očekávané a přijaté podpisy. Používejte funkci time-safe (`crypto.timingSafeEqual` v Node, `hmac.compare_digest` v Pythonu).

Zkouška

Pokud váš koncový bod odpoví jinak než HTTP 2xx (timeout, 5xx, connection refused), budeme zvonit podle exponenciálního back-offu. Celkem 6 pokusů za ~9 hodin. Po 6 je událost označena jako `failed` ve vašem webhooku a bude zaslán e-mail s upozorněním.

ZkusCelková lhůtaČas uplynulý
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 36

Pokud chcete manuálně odeslat neúspěšnou dodávku, webhookový panel nabízí tlačítko Re-deliver pro každou neúspěšnou dodávku k dispozici po dobu 7 dnů po konečném neúspěchu.

Testování bez zaslání skutečné obálky

Koncový bod `/v1/webhooks/test` přijímá URL a typ události a POSTe fiktivní payload podepsaný přesně jako skutečný.

# 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 postupů, které je třeba dodržovat

  • Zkontrolujte HMAC podpis PŘED každým čtením těla použít časově bezpečné srovnání.
  • Zpracovat každý `envelope_id` × `event` pouze jednou a uložit ID již viděné v bázi (zpětné přenosy mohou znovu poskytnout stejnou událost).
  • Odpovězte na HTTP 200 nejvýše za 5 sekund a pak zpracujte asynchronně (v řadě).
  • Logování hrubého těla + úplná debugovaná podpisnost HMAC ověření často selhává na BOM nebo neviditelném bílém prostoru.
  • Zapojte dead-letter do čáry událostí `failed`, aby se ručně přenesl v případě incidentu na straně vaší služby.
  • Tolerovat 5 minutové prodlení mezi `t=` a příjemnou dobou a dále, odmítnout, aby se blokovaly replay.

Pro další krok.

Připraveni na připojení systémů?

Vytvořte si bezplatný účet za 2 minuty nastavení webhooku je k dispozici již na Starterovém plánu (bezplatné, 5 obálek/měsíc).