Custom Domain docs
Webhooks

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

EventFires when
connection.createdA connection is created.
connection.appliedRecords were written through a rail (data.via = automatic for OAuth, async for one-click setup, unset for API-token apply).
connection.liveEvery desired record resolved; the connection is live.
connection.failedA propagating connection's records never appeared within 24h.
connection.records.updatedA managed connection was re-applied (POST /v1/connections/{id}:reapply); its desired records were re-written and the connection re-verified.
connection.reapply.failedA managed re-apply push failed; if the stored grant was revoked, the connection is flagged for re-consent.
connection.disconnectedA connection was disconnected (DELETE /v1/connections/{id}); for a managed connection the template was reverted through the grant first.
connection.records_outdatedA fleet-wide reconciliation pass found a non-managed connection whose applied records were computed against a superseded edge target; the envelope carries a connect_link to re-run setup (see Managed connections).
domain.record_missingMonitor saw a baseline record disappear or get repointed (drift).
domain.record_restoredA previously missing baseline record is observed again.
domain.purchasedA domain purchase completed — the domain is registered (and only then is the card captured).
purchase.errorA purchase failed; the card authorization was released.
purchase.confirmation.expiredA pending purchase's checkout session timed out unpaid — the buyer never confirmed. Release the cart.
secure_statusA certificate's provisioning status changed (secure_status on the envelope).
power_statusA reverse-proxy host's provisioning status changed (power_status on the envelope).

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 where MONITOR_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 (and purchase.error) require the Sell purchase path, which is live on the hosted service (gated by SELL_PURCHASE_ENABLED).

Payload envelope

Deliveries are a flat JSON object:

{
  "id": "evt_…",
  "type": "connection.live",
  "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.

Two more conditional fields ride connection events:

  • updated_objects — the records a write actually created or changed. Present on connection.applied and connection.records.updated only; the events that just observe a connection never claim to have written anything.
  • redirection_statuspending, success or failed for the apex↔www redirect, and present only on connections created with www_redirect: true.

Delivery & retries

  • A delivery succeeds on any 2xx. Each attempt is persisted.
  • Failures retry with exponential backoff (30s, 1m, 2m, doubling, capped at 6h between attempts) for up to 12 attempts total, spanning up to about a day, before the delivery is dead-lettered (given up on). A non-transient 4xx (other than 429) is not retried at all — it's a permanent misconfiguration a retry can't fix.
  • After a 429, or three consecutive failed attempts, deliveries to that endpoint URL are suppressed for about 5 minutes (a cooldown) so a struggling receiver isn't hammered while it recovers.
  • 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.

On this page