Webhooks otrzymuj wydarzenia podpisu w czasie rzeczywistym
Ustaw adres URL HTTPS w pulpitie Certyneo i otrzymasz podpisany HMAC-SHA256 POST, gdy tylko zdarzy się coś na twoich kopertach: podpis, odmowa, wygaśnięcie. 8 obsługiwanych zdarzeń, wykładnicza próbka ponowna na 6 próbek, 8 linii kodu do weryfikacji kryptograficznej.
< 5s
Średni czas do dostarczenia po zdarzeniu
5x
Próby w przypadku niepowodzenia (do ~ 9h później)
HMAC-SHA256
Algorytm podpisu każdego żądania
Katalog wydarzeń
8 poniższych zdarzeń obejmuje cały cykl życia koperty Certyneo. Włącz te, które Cię interesują w Ustawieniach → Webhooks, ignoruj pozostałe subskrypcja jest granulowana na zdarzenie.
| Wydarzenie | Wyzwanie |
|---|---|
envelope.created | Powstaje otoczka (za pomocą UI, API lub szablonu) użyteczna do synchronizacji zapisu po stronie CRM od momentu jego utworzenia. |
envelope.sent | Koperta jest wysyłana do sygnatariuszy (pierwszy wysłany e-mail). |
envelope.completed | Wszystkie podpisywane dokumenty są dostępne w eIDAS oraz w ścieżce audytu za pośrednictwem `proof_pdf_url` w payloadie. |
envelope.declined | Podpisujący odrzucił kopertę. |
envelope.voided | Księga została anulowana przez wystawcę przed podpisaniem. |
envelope.expired | Data wygaśnięcia koperty minęła bez pełnego podpisu. Lista brakujących sygnatariuszy znajduje się w `data.missing_signers`. |
recipient.signed | Użyteczne w kolejnych przepływach pracy: uruchomienie przejścia do następnego sygnatariusza. |
recipient.viewed | Podpisujący otworzył linki do podpisu, nie podpisując jeszcze, co jest przydatne w celu ukierunkowanego wprowadzania w życie. |
Format ładunku użytecznego
Wszystkie zdarzenia mają ten sam schemat JSON top-level: `event`, `envelope_id`, `occurred_at`, `data`. Zawartość `data` różni się w zależności od zdarzenia (pole udokumentowane według zdarzenia w odniesieniu 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"
}
}Podpis HMAC jest obliczany na ciele surowym wysyłanym nie zmieniaj spacji, re-parsing JSON często zmienia kolejność kluczy i łamię weryfikację.
Sprawdź podpis HMAC
Każdy żądanie jest podpisywane z sekretem webhooka (widoczny tylko raz podczas tworzenia w pulpicie, a następnie szyfrowany w spoczynku).<timestamp>,v1=<hex_hmac>Zawsze sprawdzaj podpis przed przetworzeniem ładunku użytecznego. Bez tego kroku każdy może sfałszować zdarzenie i wezwać punkt końcowy.
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)Częste błędy
Nie używaj `===` lub `==` do porównania spodziewanego podpisu z otrzymanym. Użyj funkcji time-safe (`crypto.timingSafeEqual` w Node, `hmac.compare_digest` w Python). W przeciwnym razie różnica czasu porównania między dwoma podpisami stopniowo ujawnia sekret cierpliwemu atakującemu (timing attack).
Polityka ponownego próbkowania
Jeśli punkt końcowy odpowie inaczej niż HTTP 2xx (timeout, 5xx, connection refused), odbijamy w oparciu o wykładnicze backoff. W sumie 6 próbek w ~9 godzin. Po 6 próbach zdarzenie jest oznaczone jako `failed` w paneli internetowym webhooka i wysyłany jest e-mail z ostrzeżeniem.
| Próba | Czas skumulowany | Czas minął |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 30 min | 36 min |
| #5 | + 2 h | 2h 36 |
| #6 | + 6 h | 8h 36 |
Jeśli chcesz ręcznie przekazać nieudane zdarzenie, w dashboodzie webhooka znajduje się przycisk Re-deliver dla każdej nieudanej dostawy dostępny przez 7 dni po ostatecznej niepowodzenia.
Testy bez wysyłania prawdziwej koperty
Punkt końcowy `/v1/webhooks/test` akceptuje URL i typ zdarzenia, a POSTe ma fikcyjny ładunek, podpisany dokładnie jak prawdziwy.
# 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 praktyk, które należy przestrzegać
- Sprawdź sygnaturę HMAC PREZ każdej lekturze ciała użyj porównania bezpiecznego w czasie.
- Przetwarzanie każdego `envelope_id` × `event` tylko raz, przechowując ID już widziane w bazie (przywracanie może ponownie dostarczyć to samo zdarzenie).
- Odpowiedz na HTTP 200 w maksymalnie 5 sekund, a następnie przetwarzaj asynchronicznie (w kolejce).
- Logging body brut + pełny podpis w debugu weryfikacja HMAC często nie działa na BOM lub niewidocznym białym przestrzeni.
- Podłącz do `failed` zdarzeń dead-letter queue, aby ręcznie przekazać w razie incydentu na stronie swojego działu.
- Tolerować 5-minutowy odstęp między `t=` a czasem odbioru i dalej, odrzucać, aby blokować powtórzenia.
Do dalszego rozwoju
Gotowi do podłączenia systemów?
Załóż konto za darmo w 2 minuty konfiguracja webhooka jest dostępna już w planie Starter (darmo, 5 kopert/miesiąc).