Webhooks nhận các sự kiện đăng ký trong thời gian thực
Thiết lập một URL HTTPS trong bảng điều khiển Certyneo của bạn và nhận được một POST được ký HMAC-SHA256 ngay khi một sự kiện xảy ra trên phong bì của bạn: ký kết, từ chối, hết hạn. 8 sự kiện được hỗ trợ, thử lại theo cấp số nhân trên 6 lần, xác minh mật mã trong 8 dòng mã.
< 5s
Thời gian trung bình để giao hàng sau sự kiện
5x
Các nỗ lực nếu thất bại (cho đến 9h sau)
HMAC-SHA256
Định thuật số ký tên cho mỗi yêu cầu
Danh mục sự kiện
8 sự kiện dưới đây bao gồm toàn bộ vòng đời của một gói tin Certyneo.
| Sự kiện | Bắt đầu |
|---|---|
envelope.created | Một bao bì được tạo ra (bằng UI, API hoặc mẫu) hữu ích để đồng bộ hóa một bản ghi phía CRM ngay từ khi tạo. |
envelope.sent | Bảng thư được gửi đến những người ký (thông tin điện tử đầu tiên được gửi). |
envelope.completed | Tất cả các bên ký đã ký. PDF eIDAS niêm phong và đường mòn kiểm toán có sẵn qua `proof_pdf_url` trong payload. |
envelope.declined | Một người ký đã từ chối phong bì. Lý do từ chối (nếu được cung cấp bởi người ký) được ghi trong `data.decline_reason`. |
envelope.voided | Mẫu giấy đã bị hủy bởi người gửi trước khi ký hoàn toàn. |
envelope.expired | Ngày hết hạn của phong bì đã qua mà không có chữ ký đầy đủ. Danh sách những người ký bị mất tích có trong `data.missing_signers`. |
recipient.signed | Một người ký đơn lẻ đã ký (nhưng không nhất thiết là tất cả). hữu ích cho các luồng công việc liên tục: kích hoạt chuyển sang người ký tiếp theo. |
recipient.viewed | Một người ký đã mở liên kết ký mà chưa ký, rất hữu ích cho các cuộc khởi động thương mại có mục tiêu. |
Định dạng tải trọng
Tất cả các sự kiện đều chia sẻ cùng một kiểu JSON cấp cao: `event`, `envelope_id`, `occurred_at`, `data`. Nội dung của `data` khác nhau tùy theo sự kiện (các trường được ghi lại theo sự kiện trong tham chiếu 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 được tính trên body raw như đã gửi không thay đổi khoảng trống, việc phân tích lại JSON thường thay đổi thứ tự của các khóa và phá vỡ xác minh.
Kiểm tra chữ ký HMAC
Mỗi yêu cầu được ký bằng bí mật webhook của bạn (có thể nhìn thấy một lần khi tạo trong bảng điều khiển, sau đó được mã hóa khi nghỉ ngơi).<timestamp>,v1=<hex_hmac>`. Luôn luôn kiểm tra chữ ký trước khi xử lý payload không có bước này, bất cứ ai cũng có thể tạo ra một sự kiện và gọi đến điểm cuối của bạn.
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)Lỗi thường gặp
Không sử dụng `===` hoặc `==` để so sánh chữ ký mong đợi với chữ ký nhận. Sử dụng hàm timing-safe (`crypto.timingSafeEqual` trong Node, `hmac.compare_digest` trong Python). Nếu không, khoảng thời gian so sánh giữa hai chữ ký sẽ dần dần tiết lộ bí mật cho một kẻ tấn công kiên nhẫn (timing attack).
Chính sách thử lại
Nếu điểm cuối của bạn trả lời không phải là HTTP 2xx (timeout, 5xx, connection refused), chúng tôi sẽ quay lại theo cách sao lưu theo cấp số nhân. Tổng cộng 6 lần trong ~ 9 giờ. Sau 6 lần, sự kiện được đánh dấu là `failed` trong bảng điều khiển webhook của bạn và một email cảnh báo được gửi.
| Cố gắng | Thời gian tổng hợp | Thời gian trôi qua |
|---|---|---|
| #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 |
Nếu bạn muốn chuyển tiếp một sự kiện bị thất bại bằng tay, bảng điều khiển webhook cung cấp một nút Re-deliver trên mỗi giao hàng thất bại có sẵn trong 7 ngày sau khi thất bại cuối cùng.
Kiểm tra mà không gửi phong bì thật
Điểm cuối `/v1/webhooks/test` chấp nhận một URL và một loại sự kiện, và POSTe một payload giả ký giống như một thực.
# 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 cách làm
- Kiểm tra chữ ký HMAC trước khi đọc thân sử dụng so sánh an toàn thời gian.
- Xử lý mỗi `envelope_id` × `event` chỉ một lần bằng cách lưu trữ các ID đã được xem trong cơ sở (các lần truy cập có thể tái cung cấp cùng một sự kiện).
- Trả lời HTTP 200 trong tối đa 5 giây, sau đó xử lý không đồng bộ (đợi đợi). Nếu không bạn sẽ bị thời gian và được tính là thử lại.
- Log body brut + chữ ký hoàn chỉnh trong debugging kiểm tra HMAC thường thất bại trên BOM hoặc không gian trắng vô hình.
- Kết nối một dead-letter queue trên các sự kiện `failed` để chuyển tiếp bằng tay trong trường hợp xảy ra sự cố bên cạnh dịch vụ của bạn.
- Chống sự trễ 5 phút giữa `t=` và thời gian nhận , từ chối để chặn các bản phát lại.
Để đi xa hơn
Sẵn sàng kết nối hệ thống của các bạn?
Tạo tài khoản miễn phí trong 2 phút thiết lập webhook có sẵn ngay trong gói Starter (miễn phí, 5 phong bì/tháng).