API referenceWebhooks

Create a webhook subscription

Registers an active webhook and returns it together with a one-time signingSecret (prefixed whsec_, flagged secretShownOnce) that appears only in this response — store it immediately.

Endpoint
POST https://api.dairo.app/v1/webhooks

Registers an active webhook and returns it together with a one-time signingSecret (prefixed whsec_, flagged secretShownOnce) that appears only in this response — store it immediately. url must be an HTTPS endpoint on port 443 with a public host (loopback, private, and link-local addresses are rejected) and at most 2048 characters, and events must list at least one subscribable event type — the synthetic webhook.ping is not one and is rejected. Each URL may be connected once per organization, so a create for a URL already registered returns 409. Dairo delivers each matching event as a JSON POST carrying X-Dairo-Event, X-Dairo-Event-Id, X-Dairo-Timestamp (unix seconds), and X-Dairo-Signature, where the signature is v2=<hex HMAC-SHA256> over <timestamp>.<event id>.<raw body> keyed by the hex SHA-256 of the secret; verify it and the timestamp before trusting the payload, as described in verifying webhooks. Pass an optional label to name the endpoint. The create is not deduplicated by key: a retry for a URL that was registered meanwhile returns 409, so list your webhooks to find it. Only a key whose creator is an organization owner or admin may manage webhooks, and a key restricted to specific inboxes is refused with a 403, because a webhook receives the events of every inbox. Rate limited to 10 creates per hour; requires the webhooks:write scope.

Scope: webhooks:write. Send the API key as a bearer token; Scopes lists what each one reaches.

Request

cURL
curl -X POST "https://api.dairo.app/v1/webhooks" \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "events": [
      "message.received"
    ]
  }'

Headers

NameTypeRequiredDescription
Idempotency-KeystringnoOptional idempotency key for safe retries of side-effecting operations. (max length 128)

Body

Required, application/json: CreateWebhookRequest.

FieldTypeRequiredDescription
urlstring (uri)yes
eventsarray of "message.received", "message.quarantined", "message.sent", "message.delivered", "message.bounced", "message.complained", "message.delayed", "budget.tripped", "compliance.erasure.completed", "object.scanned", "share_link.opened", "letter.status_changed", "domain.verified", "verification.resolved", "verification.expired", "call.status_changed"yes(min items 1)
labelstringnoOptional human label for the endpoint. Surfaced read-only and used as the primary match field by prefix-scoped test cleanup.

Response

201 Created. Body: application/json, CreateWebhookResponse.

FieldTypeRequiredDescription
object"webhook"yes
idstring (uuid)yes
urlstring (uri)yes
eventsarray of "message.received", "message.quarantined", "message.sent", "message.delivered", "message.bounced", "message.complained", "message.delayed", "budget.tripped", "compliance.erasure.completed", "object.scanned", "share_link.opened", "letter.status_changed", "domain.verified", "verification.resolved", "verification.expired", "call.status_changed"yes
labelstring or nullnoOptional human label for the endpoint, or null when unlabeled.
status"active", "paused"yes
createdAtstring (date-time)yes
lastDeliveryAtstring (date-time) or nullnoTimestamp of the most recent successful webhook delivery, or null before the first successful delivery.
secondarySecretExpiresAtstring (date-time) or nullnoEnd of the dual-secret window after a rotation, or null.
secondarySecretActivebooleannoWhether deliveries are still signed with the previous secret too.
consecutiveFailuresintegerno
pausedReasonstring or nullnoauto:delivery_failures when the circuit breaker paused the endpoint, else null.
autoPausedbooleanno
autoPausedAtstring (date-time) or nullno
signingSecretstringyesOne-time signing secret (whsec_...). Store it now; it is never shown again.
secretShownOncetrueyes
201
{
  "object": "webhook",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "url": "https://example.com",
  "events": [
    "message.received"
  ],
  "label": "production",
  "status": "active",
  "createdAt": "2026-10-19T09:30:00Z",
  "lastDeliveryAt": "2026-10-19T09:30:00Z",
  "secondarySecretExpiresAt": "2026-10-19T09:30:00Z",
  "secondarySecretActive": true,
  "consecutiveFailures": 0,
  "pausedReason": null,
  "autoPaused": true,
  "autoPausedAt": "2026-10-19T09:30:00Z",
  "signingSecret": "string",
  "secretShownOnce": true
}

Errors

Every error has the same body, {"error": {"type", "code", "message", "param"}}; branch on code. Errors lists every code.

StatusMeaning
400The request is malformed or a value is invalid (invalid_request, invalid_domain, idempotency_key_mismatch).
401The API key is missing or invalid.
403The key lacks the scope this endpoint needs (scope_missing) or may not do this (not_permitted).
404No such resource in this account (resource_not_found).
422The request is well-formed but a field failed validation (validation_failed).
429Rate limit, plan limit, budget or spend cap reached (rate_limited, plan_limit_reached, budget_exceeded, spend_cap_reached).
500An unexpected error; retry with backoff, then contact support (internal_error).

On this page