---
title: "Register a webhook endpoint"
method: POST
path: "/webhooks"
tags: ["Webhooks"]
---

# Register a webhook endpoint

`POST /webhooks`

## Request

- Base URL: `https://app.arkova.ai/api/v1`
- URL: `https://app.arkova.ai/api/v1/webhooks`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Register a new webhook endpoint programmatically. Returns the HMAC signing secret ONCE — save it immediately, it cannot be retrieved later. The URL must be HTTPS and is validated against private/internal/cloud-metadata IPs (SSRF protection) with full DNS resolution. Pass `verify: true` to require a synchronous verification ping (the endpoint must echo a challenge token before registration succeeds). Registration checks organization connector capacity, independently of GET /usage API-call allowance. A quota_check_failed 503 means that capacity could not be established, not that the call allowance is exhausted. The anchor lifecycle callback schemas below retain chain_tx_id and chain_block_height; verification responses instead use network_receipt_id and bitcoin_block.

## Request body

- object
  - `url` string, uri, required
  - `events` string[]
  - `description` string
  - `verify` boolean: Send a verification ping before persisting
  - `scope` 'self' | 'self_and_descendants': Delivery scope (SCRUM-3972). 'self' (default) delivers only this organization's own events. 'self_and_descendants' additionally delivers events owned by organizations whose parent is this organization and whose affiliation is APPROVED — one hop, never upward; such cross-organization payloads always carry org_public_id. Additive and nullable-safe per CLAUDE.md §1.8: omitting it preserves the pre-existing behaviour exactly. The cross-organization delivery it enables is behind a server-side gate that is currently OFF, so an endpoint set to 'self_and_descendants' today behaves exactly like 'self'.

## Response `201`

Webhook endpoint registered. Secret returned ONCE.

- WebhookEndpointWithSecret: Webhook endpoint metadata. Signing secret is never returned on read.
  - `id` string, uuid
  - `url` string, uri
  - `events` string[]
  - `is_active` boolean
  - `description` string, nullable
  - `scope` 'self' | 'self_and_descendants': Delivery scope (SCRUM-3972). 'self' (default) delivers only this organization's own events. 'self_and_descendants' additionally delivers events owned by organizations whose parent is this organization and whose affiliation is APPROVED — one hop, never upward; such cross-organization payloads always carry org_public_id. Additive and nullable-safe per CLAUDE.md §1.8: omitting it preserves the pre-existing behaviour exactly. The cross-organization delivery it enables is behind a server-side gate that is currently OFF, so an endpoint set to 'self_and_descendants' today behaves exactly like 'self'.
  - `created_at` string, date-time
  - `updated_at` string, date-time
  - `secret` string: 64-char hex HMAC-SHA256 signing secret. Save it now — it is shown once.
  - `warning` string

## Other responses

- `400`: Invalid request
- `401`: Authentication required
- `403`: The API key does not hold the `webhooks:manage` scope (`insufficient_scope`), or the key actor is not an ORG_ADMIN.
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/arkova/apis/arkova-verification-api.md) · [All operations](https://skmtc.dev/arkova/apis/arkova-verification-api/llms.txt) · [OpenAPI document](https://skmtc.dev/arkova/apis/arkova-verification-api/revisions/a14858d1efd4?raw)
