Developer platform · 4 / 9

Local integration: providers, pages and webhooks

Themes and rule packs are prechecked locally; plugins with a service must get protocol, signatures and idempotency right locally before a customer installs them. Four kinds of credential exist and none replaces another.

← All tutorials

Four credentials

PurposeWho keeps the secretWhat the other side needs
Manifest signatureThe developer, offlineThe publisher key registered with the platform
Requests from the control planeThe customer’s control plane (installation key)The installation public key the administrator gives you
Plugin response signatureYour service (response key)provider_response_key in the manifest
Webhook messagesBoth sides keep the subscription secretThe HMAC secret picked by key ID

Only the customer’s administrator hands you the installation public key, over a trusted channel: the plugin detail page’s “Installation signing key” shows the key and its key ID. Never trust a key carried in a request and never ask for the installation private key.

Provider plugins: pass the sandbox contract first

Third parties can provide dns, sms, mail, icp and payment; notify can be declared but is not enabled yet, and cdn is official only. The control plane calls POST {base_url}/cdnca-provider/v1/{kind}/{action}: request up to 256 KiB, response up to 1 MiB, 15 s timeout, public HTTPS only, no proxy, no redirects.

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":{}}
  • Bad signature, a skew over 300 s or a repeated nonce: HTTP 401.
  • Other failures: HTTP 200 with the error envelope, e.g. {"protocol":"cdnca.provider/v1","ok":false,"error":{"code":"unavailable"}}.
  • Persist the idempotency_key of writes and return the first result on a repeat; the network nonce changes every call while the business key stays the same across retries of one operation.
  • sandbox=true uses sandbox data only: no real SMS or mail, no customer DNS change, no charge.

The reference server and contract tests ship with the development source; ask through “Contact” for the provider protocol samples matching your control plane version. For a local contract run, use a sandbox installation identity you generate yourself, point the client at your test service and run the full contract: signatures, tampering, time window, repeated nonce, idempotency and each kind’s business cases. When a customer installs, the control plane runs the installation contract against your service again and enables it only if everything passes; a 200 health check is not enough. Signed message formats, action fields and sandbox values are in the provider guide.

Page plugins: origin, SDK and backend verification

  1. Deploy the page on an HTTPS domain, plus an API domain if you have a backend.
  2. Seller site “Page origins” → “Register origin”, put cdnca-plugin-origin=<token> as shown at /.well-known/cdnca-plugin-origin.txt on that origin and click “Verify”. Both the page origin and the api_base_url origin must be verified.
  3. Write the ui_page entrypoint in spec.entrypoint, add platform.ui_pages.provide, sign and upload (no file or package).
{
  "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": "报表"}
    }
  ]
}

Origins must be public HTTPS on the default port without redirects, and belong to one publisher. Pages run in an isolated iframe without the control plane’s cookies, storage or parent DOM. After connect() the SDK offers only context(), navigate() and request(); request forwards only to your own api_base_url, at most 120 per user per plugin per minute. Opening the page on its own gets no context; test it mounted in a control plane where it is installed.

Your backend verifies X-Cdnca-Plugin-Context (base64url JSON, 60 s, with a nonce), X-Cdnca-Plugin-Key-Id and X-Cdnca-Plugin-Signature with the installation public key and remembers the nonce; with several replicas keep nonces in shared storage, and check the tenant, plugin and resources in the context. The SDK source, backend verifier and test vector are published on the ui_page SDK page.

Webhooks: receive the test event first

The customer creates the subscription in the target tenant: managing needs a control plane session and plugin_webhooks.manage, reading plugin_webhooks.read; an API key cannot do it. The smallest test subscription selects only io.cdnca.webhook.v1.test and no installation authorisation. The target must be a public HTTPS domain on port 443, without query or fragment, not an 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

Success is 201 with the subscription and a one-time secret; move the secret into the receiver’s secret store and keep the receipt out of ordinary logs. Then POST .../{subscription_id}/test-deliveries (empty object body, a new Idempotency-Key); 202 only means accepted, so check .../deliveries for status delivered and confirm your receiver stored it.

  1. Check POST, path, Content-Type and the required headers.
  2. Hash the raw body with SHA-256 and compare with X-CDNCA-Webhook-Content-Sha256.
  3. Pick the secret by X-CDNCA-Webhook-Key-Id, base64url-decode it strictly to 32 bytes and compute HMAC-SHA256; the signature looks like v2=<unpadded base64url>.
  4. The timestamp is within 300 s of your clock (inclusive).
  5. Parse per the event schema and bind event ID, tenant and subscription; dedupe by delivery ID and use the event ID for business idempotency.
  6. Persist or commit first, then return 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>

Automatic delivery makes at most 5 attempts; a retry keeps the delivery ID and increments attempt, an explicit replay keeps the event ID with a new delivery ID. Replay one with POST .../deliveries/{delivery_id}/replays, a time range with POST .../delivery-replays (up to 31 days, 100 per batch). Rotation and revocation need an Idempotency-Key and the subscription’s If-Match: missing gives 428, stale gives 412; read back, then decide. Real domain events need an enabled installation authorisation with the matching events.*.read permission approved; a subscription only receives events after it was created. A reference receiver is available through “Contact” (Go 1.27, Unix file locks; on Windows run it in WSL or Linux).