Chuyển đến nội dung chính
Certyneo
Tài liệu phát triển

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ệnBắt đầu
envelope.createdMộ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.sentBảng thư được gửi đến những người ký (thông tin điện tử đầu tiên được gửi).
envelope.completedTấ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.declinedMộ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.voidedMẫu giấy đã bị hủy bởi người gửi trước khi ký hoàn toàn.
envelope.expiredNgà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.signedMộ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.viewedMộ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ắngThời gian tổng hợpThời gian trôi qua
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 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).