# Register a webhook endpoint (secret returned once)

> Register a webhook endpoint (secret returned once): CustomDomain™ API reference. Covers request parameters, the request body, responses and error codes.

Source: https://docs.customdomain.ai/docs/api-reference/webhooks/createWebhook

## `POST https://api.customdomain.ai/v1/webhooks`

Registers an HTTPS receiver under the calling application. The `secret` (`whsec_…`) is returned ONCE. Store it to verify signatures. An empty `events` array subscribes to all events; `"*"` also matches all.

### Authentication

`widgetJWT`

### Request body (JSON)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes |  |
| `events` | array of `*`, `domain.record_missing`, `domain.record_restored`, `domain.purchased`, `purchase.error`, `purchase.confirmation.expired`, `secure_status`, `power_status`, `connection.created`, `connection.applied`, `connection.live`, `connection.failed`, `connection.records.updated`, `connection.reapply.failed`, `connection.records_outdated`, `connection.disconnected`, `certificate.issued`, `certificate.renewed`, `certificate.failed`, `certificate.expiring`, `credential.created`, `credential.expiring`, `credential.failed`, `credential.removed`, `nameservers.started`, `nameservers.dnssec_cleared`, `nameservers.changed`, `nameservers.active`, `nameservers.failed`, `nameservers.rolled_back`, `nameservers.drifted` | no | Event types to subscribe to; empty subscribes to all events, and `"*"` also matches all. Every value listed here has a producer: the enum is asserted against the emit sites in CI, so you can never subscribe to an event that cannot arrive. `connection.records.updated` / `connection.reapply.failed` / `connection.disconnected` cover the managed (async one-click) re-apply and disconnect lifecycle; `connection.disconnected` carries a `removal` object saying what happened to the DNS records. `certificate.issued` / `certificate.renewed` / `certificate.failed` follow the edge's certificate reports, and `certificate.expiring` fires 14, 7 and 3 days before an unrenewed certificate expires and when it expired; each carries a `certificate` object. `domain.record_missing` / `domain.record_restored` carry a `drift` object with the record, what public DNS shows and what was done about it. `connection.records_outdated` fires once per domain a routing change moves (saving a connect target, or `POST /v1/connections/{id}:retarget`), carrying `records.previous` and `records.current`; the domain needs the new records. `certificate.failed` fires once per failure episode, when the edge first reports that issuing or renewing the certificate failed. `secure_status` is sent only where the deployment has Secure on (`SECURE_ENABLED`), once each time a certificate's status changes. Every connection, certificate and routing event also appears on the domain's timeline (`GET /v1/connections/{id}/events`) as a row of the same kind. The `nameservers.*` events follow a nameserver migration (`POST /v1/connections/{id}/nameservers:migrate`), each with a `nameservers` object (`NameserverEventData`): `started`, `dnssec_cleared` (the registry stopped publishing the DS record), `changed` (the registrar took the new nameservers), `active` (the registry lists every one of them), `failed` (with `failure_code`), `rolled_back`, and `drifted` (an active migration's nameservers were changed away; `observed_nameservers` says to what). They go to the migration's own application only. The `credential.created`, `credential.expiring`, `credential.failed` and `credential.removed` events follow the lasting API key the cloud browser's agent made at the domain's DNS provider (made and kept, renewed by a reconnect, 30 and 7 days before its end, refused by the provider or ended, removed); each carries a `credential` object with the key's record, never the key, its `rights` (`transfer`, `billing` or `account`: what it can do beyond DNS), and `reconnect: true` when the end user should sign in again. |

### Responses

| Status | Description |
| --- | --- |
| `201` | Created (secret returned once) |
| `400` | Error envelope: {code, title, details}. |
| `401` | Error envelope: {code, title, details}. |
| `503` | Error envelope: {code, title, details}. |

### `201` response body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `application_id` | string | no |  |
| `url` | string | yes |  |
| `events` | array of string | no |  |
| `secret` | string or null | no | Present ONLY in the creation response (whsec_…). Deliveries are signed with it: each POST carries X-JE-Timestamp (unix seconds) and X-JE-Signature (sha256=hex of HMAC-SHA256 over "<timestamp>.<rawBody>"). Reject deliveries whose timestamp is outside a ~5-minute window. |
| `created_at` | string | no |  |

The full specification is served at `GET https://api.customdomain.ai/v1/openapi.json`.
