Overview
Register HTTPS endpoints to receive signed events as connections progress.
Register HTTPS endpoints to receive signed events as connections progress and as monitored domains drift.
Register an endpoint
curl -X POST http://localhost:8080/v1/webhooks \
-H "Authorization: Bearer <WIDGET_JWT>" \
-H "Content-Type: application/json" \
-d '{"url":"https://api.acme.example/webhooks/cd","events":["connection.live","domain.record_missing"]}'
# → { "id": "whk_…", "url": "...", "events": [...], "secret": "whsec_…", "created_at": "..." }The secret (whsec_…) is returned once — store it to verify signatures. An
empty events array subscribes to all events; "*" also matches all.
Manage endpoints: GET /v1/webhooks lists them (secrets omitted; tenant-wide
with an sk_ key, app-scoped with a widget JWT) and
DELETE /v1/webhooks/{id} removes one.
Event catalog
| Event | Fires when |
|---|---|
connection.created | A connection is created. |
connection.applied | Records were written through a rail (data.via = automatic for OAuth, async for one-click setup, unset for API-token apply). |
connection.live | Every desired record resolved; the connection is live. |
connection.failed | A propagating connection's records never appeared within 24h. |
connection.records.updated | A managed connection was re-applied (POST /v1/connections/{id}:reapply); its desired records were re-written and the connection re-verified. |
connection.reapply.failed | A managed re-apply push failed; if the stored grant was revoked, the connection is flagged for re-consent. |
connection.disconnected | A connection was disconnected (DELETE /v1/connections/{id}); for a managed connection the template was reverted through the grant first. |
connection.records_outdated | A non-managed connection's applied records were computed against a superseded edge target; the envelope carries a connect_link to re-run setup. |
domain.record_missing | Monitor saw a baseline record disappear or get repointed (drift). |
domain.record_restored | A previously missing baseline record is observed again. |
domain.purchased | A domain purchase completed — the domain is registered (and only then is the card captured). |
purchase.error | A purchase failed; the card authorization was released. |
purchase.confirmation.expired | A pending purchase's checkout session timed out unpaid — the buyer never confirmed. Release the cart. |
secure_status | A certificate's provisioning status changed (secure_status on the envelope). |
power_status | A reverse-proxy host's provisioning status changed (power_status on the envelope). |
When the connection carries a recorded diagnosis at emit time, the envelope also includes error_code and error_message (for example propagation_timeout, access_denied, ProviderAuthenticationError, or setup_incomplete on a stalled pending connection), so a receiver need not fetch the connection to learn why.
That table is the complete vocabulary: it is exactly the set the platform can
emit, and the events enum in the OpenAPI spec matches it.
An unknown event string is still accepted at registration, but it can never be
delivered — subscribe only to the events above.
Status: the drift events (
domain.record_missing/domain.record_restored) are produced by the hourly Monitor sweep, which delivers them only whereMONITOR_ALERTS_ENABLED=1. With the flag off (the hosted default) the sweep still runs in shadow mode but no drift webhooks are sent.domain.purchased(andpurchase.error) require the Sell purchase path, which is live on the hosted service (gated bySELL_PURCHASE_ENABLED).
Payload envelope
Deliveries are a flat JSON object:
{
"id": "evt_…",
"type": "connection.live",
"user_id": "tenant-or-end-user-ref",
"domain": "app.customer.com",
"subdomain": "app",
"provider": "cloudflare",
"setup_type": "automatic",
"propagation_status": "success",
"data": {
"records_propagated": [ { "type": "CNAME", "host": "app.customer.com", "value": "edge.customdomain.ai" } ],
"records_non_propagated": []
},
"created_at": "2026-07-07T12:00:00Z"
}propagation_status is pending, success, or timeout. On propagation
events, data carries the per-record propagated / non-propagated arrays. Fields
that don't apply to an event are omitted.
Additional conditional fields ride connection events:
user_id— the connection'send_user_ref, echoed so integrators can associate lifecycle events with the originating tenant or end user. Omitted when the connection was created without anend_user_ref.updated_objects— the records a write actually created or changed. Present onconnection.appliedandconnection.records.updatedonly; the events that just observe a connection never claim to have written anything.redirection_status—pending,successorfailedfor the apex↔www redirect, and present only on connections created withwww_redirect: true.
Delivery & retries
- A delivery succeeds on any
2xx. Each attempt is persisted. - Failures retry with exponential backoff, up to 3 attempts total.
- Inspect attempts:
GET /v1/webhook-deliveries(attempts,status_code,delivered,error; filter with?app_id=and?type=).
Endpoints should be idempotent — the same event can arrive more than once on
retry. Deduplicate on the envelope id, or on domain + type.
Next: verify signatures.