# Register a webhook endpoint; the secret is shown once

POST /v1/webhooks: Register a webhook endpoint; the secret is shown once

```http
POST /v1/webhooks
```

|  |  |
|---|---|
| Operation id | `createWebhook` |
| Key scope | `admin`: admin keys only |
| Section | [Webhooks](https://docs.kaab.ist/reference/api#webhooks) |

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string (uri) | yes |  |
| `events` | string[] | yes | min items 1 · max items 50 |
| `description` | string |  | max length 1000 |
| `mode` | `"live"`, `"test"`, `"both"` |  | default `"both"` |
| `format` | `"standard"`, `"discord"`, `"slack"` |  | default `"standard"` |

## Example request

```bash
curl -X POST "https://api.kaab.ist/v1/webhooks" \
  -H "Authorization: Bearer $KAAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.acme.test/kaab","events":["job.succeeded","job.*"],"description":"CI"}'
```

## Responses

### 201

Webhook

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string | yes | pattern `^whk_[0-9a-hjkmnp-tv-z]{26}$` |
| `object` | `"webhook"` | yes |  |
| `url` | string (uri) | yes |  |
| `events` | string[] | yes | min items 1 |
| `mode` | `"live"`, `"test"`, `"both"` | yes |  |
| `format` | `"standard"`, `"discord"`, `"slack"` | yes |  |
| `status` | `"active"`, `"disabled"` | yes |  |
| `description` | string or null | yes |  |
| `disabled_reason` | string or null | yes |  |
| `previous_secret_expires_at` | string or null (date-time) | yes |  |
| `created_at` | string (date-time) | yes |  |
| `secret` | string | yes |  |

## Errors

Every error answers with the [error envelope](https://docs.kaab.ist/reference/api/schemas/error-response); its `code` is one of the [error codes](https://docs.kaab.ist/reference/codes#errors).

| Status | When |
|---|---|
| 400 | `request_invalid`: request validation failed |
| 401 | Missing or invalid API key |
| 402 | `insufficient_credits`: top up to continue |
| 403 | The key's scope does not cover this operation |
| 409 | Conflicting state (see `error.code`) |
| 422 | Semantically invalid request (see `error.code`) |
| 429 | Rate limited; see `Retry-After` |
| 500 | `internal`: unexpected server error |
