开发者平台 · 4 / 9

本地联调:服务商、页面与 Webhook

主题和规则包能在本地预检;带服务的插件要先在本地把协议、签名与幂等跑通,再交给客户在主控里安装。四类凭据互不替代,先分清楚。

← 全部教程

四类凭据

用途私密部分谁保管对方需要什么
清单签名开发者离线保管发布私钥平台登记的发布者公钥
主控调用插件的请求签名客户主控保管安装私钥客户管理员交给你的安装公钥
插件的响应签名开发者服务保管响应私钥清单里声明的 provider_response_key
Webhook 消息认证双方各自保管订阅 secret按 key ID 选用的 HMAC secret

安装公钥只能由客户管理员经可信渠道交给你:主控插件详情页“安装签名公钥”给出公钥和 key ID。不要信任请求里随附的公钥,也不要向客户索要安装私钥。

服务商插件:先过沙箱契约

第三方可接入的类型为 dns、sms、mail、icp、payment;notify 可以声明但主控暂不启用,cdn 只限官方。主控调用 POST {base_url}/cdnca-provider/v1/{kind}/{action},请求体最大 256 KiB,响应最大 1 MiB,超时 15 秒,只连公网 HTTPS,不走代理、不跟随重定向。

POST /cdnca-provider/v1/dns/verify HTTP/1.1
Host: provider.example.com
Content-Type: application/json
X-Cdnca-Provider-Installation: <installation-id>
X-Cdnca-Provider-Timestamp: <unix-seconds>
X-Cdnca-Provider-Nonce: <32-hex>
X-Cdnca-Provider-Signature: <base64url-ed25519>

{"protocol":"cdnca.provider/v1","sandbox":true}
{"protocol":"cdnca.provider/v1","ok":true,"data":{}}
  • 签名错误、时间差超过 300 秒或 nonce 重复:返回 HTTP 401。
  • 其他业务失败:HTTP 200 加协议错误信封,例如 {"protocol":"cdnca.provider/v1","ok":false,"error":{"code":"unavailable"}}。
  • 写操作的 idempotency_key 要持久保存,同一个键重复到达返回第一次的结果;网络层 nonce 每次都换,业务幂等键在同一操作的重试中保持不变。
  • sandbox=true 只用沙箱数据:不发真实短信邮件、不改客户 DNS、不扣款。

协议参考实现和契约测试随开发源码提供,可通过“联系我们”申请与主控版本匹配的提供商协议示例。本地契约测试用你自己生成的沙箱安装身份,把客户端指向你的测试服务后跑完整契约,检查签名、篡改、时间窗、重复 nonce、幂等及各类型业务用例。客户安装时主控会对你的服务再跑一次安装契约,全部通过才能启用,只让健康检查返回 200 不够。签名消息格式、各动作字段和沙箱固定值见服务商接口指南。

带页面的插件:来源、SDK 与后端验签

  1. 部署页面的 HTTPS 域名;有后端时再准备 API 域名。
  2. 卖家站“页面来源”→“登记来源”,按页面提示把 cdnca-plugin-origin=<令牌> 原样放到该来源的 /.well-known/cdnca-plugin-origin.txt,点“验证”。origin 和 api_base_url 所属来源都要验证。
  3. 清单 spec.entrypoint 写 ui_page 入口,权限加 platform.ui_pages.provide,签名并上传(不带任何文件或包)。
{
  "type": "ui_page",
  "origin": "https://ui.example.com",
  "api_base_url": "https://api.example.com/v1",
  "pages": [
    {
      "id": "reports",
      "path": "/reports",
      "mount": "menu",
      "menu_parent": "monitor",
      "role": "user",
      "title": {"zh-CN": "报表"}
    }
  ]
}

来源必须是公网 HTTPS、默认端口、不跟随重定向;同一来源只属于一个发布者。页面在隔离 iframe 里运行,拿不到主控的 Cookie、存储和父页面 DOM。SDK 的 connect() 完成握手后只有 context()、navigate() 和 request() 三个能力;request 只转发到你自己的 api_base_url,每用户每插件每分钟最多 120 次。直接单独打开页面拿不到主控上下文,要在已安装并挂载的主控里测试。

后端用安装公钥验证 X-Cdnca-Plugin-Context(base64url JSON,60 秒有效,含 nonce)、X-Cdnca-Plugin-Key-Id 与 X-Cdnca-Plugin-Signature,并记住 nonce 防重放;多副本服务要用共享存储保存 nonce,还要检查上下文里的租户、插件与业务资源。SDK 源码、后端验签示例和固定向量都在 ui_page SDK 页公开。

Webhook:先收测试事件

订阅由客户在目标租户里创建:管理操作要求主控登录会话和 plugin_webhooks.manage,查询要求 plugin_webhooks.read;API Key 不能代替。最小测试订阅只选 io.cdnca.webhook.v1.test,不填安装授权 ID。目标必须是公网 HTTPS 域名、默认 443 端口,不能带查询串或片段,不能是 IP。

CDNCA_CONTROL_ORIGIN='https://control.example.com'
CDNCA_TENANT_SCOPE_ID='<tenant-scope-uuid>'
CDNCA_SUBSCRIPTIONS="${CDNCA_CONTROL_ORIGIN}/v1/tenant-scopes/${CDNCA_TENANT_SCOPE_ID}/plugin-webhook-subscriptions"
# CDNCA_CONTROL_ACCESS_TOKEN comes from the customer's own session
umask 077
curl --fail-with-body --silent --show-error \
  -X POST "$CDNCA_SUBSCRIPTIONS" \
  -H "Authorization: Bearer ${CDNCA_CONTROL_ACCESS_TOKEN:?}" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: <unique-key-for-this-create>" \
  --data-binary '{"name":"test","target_url":"https://hooks.example.com/cdnca/hooks","event_types":["io.cdnca.webhook.v1.test"]}' \
  --output webhook-receipt.json

成功返回 201,回执含 subscription 和一次性 secret;把 secret 转存到接收服务的密钥存储,不要把回执写进普通日志。然后对 .../{subscription_id}/test-deliveries 发 POST(空对象正文、新的 Idempotency-Key),返回 202 只表示任务已接受,要再查 .../deliveries 确认状态为 delivered,并核对接收端已持久记录。

  1. 校验 POST、路径、Content-Type 和必需请求头。
  2. 对收到的原始正文算 SHA-256,与 X-CDNCA-Webhook-Content-Sha256 比较。
  3. 按 X-CDNCA-Webhook-Key-Id 选 secret,严格 base64url 解码为 32 字节后计算 HMAC-SHA256;签名形如 v2=<无填充 base64url>。
  4. 时间戳与本地时钟相差不超过 300 秒(含边界)。
  5. 按事件 schema 解析,绑定事件 ID、租户和订阅;delivery ID 去重,事件 ID 做业务幂等。
  6. 先可靠落盘或提交事务,再返回 2xx。
cdnca.webhook.signature/v2
POST
<canonical-escaped-request-path>
<X-CDNCA-Webhook-Timestamp>
<X-CDNCA-Webhook-Event-Id>
<X-CDNCA-Webhook-Delivery-Id>
<X-CDNCA-Webhook-Key-Id>
<lowercase-hex-sha256-of-raw-body>

自动投递最多 5 次尝试;自动重试保留 delivery ID 并增加 attempt,显式回放保留 event ID、生成新 delivery ID。单条回放 POST .../deliveries/{delivery_id}/replays,按时间段回放 POST .../delivery-replays(最长 31 天,每批最多 100 条)。轮换与撤销需要 Idempotency-Key 和当前订阅的 If-Match,缺版本得 428,版本过期得 412,回读后再判断。真实领域事件要绑定已启用的安装授权且客户已批准对应 events.*.read 权限;订阅只接收创建之后的新事件。参考接收器示例可通过“联系我们”申请(需 Go 1.27,Unix 文件锁,Windows 请在 WSL 或 Linux 里运行)。