网络网络即时收到签名活动
在您的 Certyneo 仪表板中配置 HTTPS URL,每当您的信封上发生事件时(签名、拒绝、过期),即可接收 HMAC-SHA256 签名的 POST。支持 11 个事件,5 次指数退避传递尝试,密码验证只需 8 行代码。
< 5s
事件后的平均交付时间
5x
总传递尝试次数,分散在大约 1 小时 20 分钟内
HMAC-SHA256
每个请求的签名算法
活动目录
下面的 12 个事件涵盖了 Certyneo 信封生命周期的全部内容。在设置 → Webhooks 中启用您感兴趣的事件,忽略其他事件——订阅按事件粒度划分。
| 活动 | 发作 |
|---|---|
envelope.created | 创建一个包裹 (通过UI,API或模板) 很有用,可以在创建时同步CRM侧记录. |
envelope.sent | 封信发送给签名者 (首次发送电子邮件).标志着活跃签名循环的开始. |
envelope.completed | 所有签署人都已签署,密封的 eIDAS PDF 已存储。负载包含 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" }
],
"fieldValues": [
{ "fieldId": "cm7x2k9p40005qz8h1a2b3c4d", "fieldType": "RADIO_GROUP", "recipientEmail": "alice@acme.com", "value": "Option B" }
],
"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。如果预签名失败,或在没有存储文件的已完成 QES 信封上,则不存在该链接——而不是 null;此时请重新使用 GET /v1/envelopes/id/signed-document,这仍然是真实来源。还要注意,在事件发生 7 天后从死信队列手动重放:负载中的链接已过期,API 端点没有过期。
收件符号是UTF-8编码,没有BOM.HMAC签名计算在原始体内,如发送不要改变空格,JSON重新解析经常改变密钥的顺序,破坏验证.
检查HMAC的签名
每个请求都使用您的webhook密钥签名(在创建订阅时仅显示一次)。签名在`X-Certyneo-Signature`请求头中传输:这是请求原始正文的HMAC-SHA256,以十六进制编码,不带前缀或时间戳。处理payload前请ALWAYS验证签名——没有这一步骤,任何人都可以伪造事件并调用您的端点。
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)常见的错误
不要使用 `===`或 `==`来比较预期和接收的签名.使用一个时间安全函数 (`crypto.timingSafeEqual`在Node中, `hmac.compare_digest`在Python中).否则,两个签名之间的比较时间差距会逐渐向耐心的攻击者揭示秘密 (时间攻击).
试用政策
如果您的端点响应耗时过长、拒绝连接或返回5xx(或429),我们将按指数退避重试:总共5次尝试,耗时约1小时20分。第五次后,事件进入死信队列——可从您的仪表板查询和重放,但不再自动重试。明确拒绝(401、403、404、410、422…)则从不重试:响应不会改变,事件直接进入死信队列。
| 试图 | 重试前的延迟 | 事件发生后经过的时间 |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 15 min | 21 min |
| #5 | + 1 h | 1 h 21 |
指定的延迟时间是最小值:重试扫描由cron控制,因此重试可能会在理论时间之后稍微才开始。放弃的事件列在Webhooks → 失败中,带有手动重放按钮且无保留期限。连续失败五次的端点——或响应404/410的端点——会自动停用,您将收到电子邮件通知:修正后重新激活,计数器重置为零(任何成功交付也会将其重置为零)。
试验没有发送真正的信封
首先创建一个订阅——响应包含HMAC验证所需的`secret`,仅显示一次。从设置 → Webhooks,「测试」按钮随后会发送一个签名的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_ 密钥创建的 webhook 仅接收测试信封的事件,使用 sk_live_ 密钥创建的 webhook 仅接收真实信封的事件。因此,测试信封永远不会到达您的生产 URL,每个负载都指示"sandbox"(true 或 false)。
六个需要遵守的做法
- 检查HMAC签名在读取任何体内之前 使用时间安全的比较.
- 在`X-Certyneo-Delivery-Id`请求头上进行去重(与请求体中的`id`相同),通过在数据库中存储已见过的标识符——如果您的2xx响应丢失了,重放可能会重新发送您已处理过的事件,每次尝试时使用相同的标识符。
- 在最多10秒内响应HTTP 2xx,然后异步处理(队列)。超过此时间,交付将被切断并计为失败。
- ,HMAC检查通常会失败,因为BOM或空白空间是看不见的.
- 监控 Webhooks → 失败页面:如果您的端点被禁用,您会收到电子邮件通知,但不会逐个事件通知——如果事件用尽了重试次数,您需要手动重新播放该事件。
- 在订阅时过滤事件而不是在您的处理程序中过滤,并保持在每个账户 5 个订阅的限制以下。
您不需要托管一个端点来接收这些事件:连接器会为您设置订阅,并直接在信封事件上启动流程。查看 Certyneo for Power Automate 和 Microsoft 365.
Power Automate:订阅自动管理
使用「当信封事件发生时」触发器,无需复制或更新任何地址。Power Automate 会使用您创建的连接自动在 Certyneo 注册订阅。
- 您在触发器卡中选择事件,然后保存并激活流:此时会创建订阅。
- 当您停用或删除流时,Power Automate 会自动删除订阅。
- 连接决定哪个账户(及哪个工作区)发送事件。在生产环境中,使用与正确账户相关联的连接。
- 如果您修改触发器(例如更改事件),请再次保存流:订阅会更新。
- 如果流不再触发,请停用它,然后重新激活:订阅会重新创建。
从开发环境迁移到生产环境
- 将您的解决方案导入生产环境。
- 打开流并将其关联到生产环境中要使用的 Certyneo 连接,该连接需用实际密钥(sk_live_)创建。
- 激活流。新的生产订阅被创建:无需在 Certyneo 端配置任何内容,也无需修改任何地址。
- 进行测试:签署或完成信封,然后检查流历史中是否显示执行记录。
为了更进一步
准备好连接系统吗?
Webhooks 和 REST API 包含在标准计划及以上版本中。创建您的账户,生成一个 `sk_test_` 密钥,并在沙箱环境中连接您的端点,然后再进入生产环境。