# Create a webhook endpoint

> Subscribes an HTTPS url to events.

`POST /v1/webhook-endpoints`

Subscribes an HTTPS url to events. The endpoint belongs to THIS key: its events are read with this key's permissions, and it stops if the key is revoked or expires. The url must resolve to a public address. The response carries the signing secret (`whsec_…`) ONCE — so this request is never replayed for an Idempotency-Key; a retry of a request that did land answers 409 `endpoint_exists`. At most 10 endpoints per key, one per url. Every delivery is an HTTPS POST signed with Standard Webhooks: headers `webhook-id` (the event id — identical on every retry; drop duplicates by it), `webhook-timestamp` and `webhook-signature` (`v1,<base64 HMAC-SHA256 of "{id}.{timestamp}.{raw body}">`). See the Webhooks section of these docs.

**Permissions:** any valid key · **Kind:** write · **Cost:** 2 units

Cannot be undone.

## Path parameters

_None._

## Body

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | string | yes | max 2048 chars |
| `description` | string |  | max 200 chars |
| `eventTypes` | "trip.created" \| "trip.updated" \| "trip.archived" \| "trip.restored" \| "trip.published" \| "trip.unpublished" \| "trip.deleted" \| "trip.itinerary.updated" \| "quote.created" \| "quote.updated" \| "quote.sent" \| "quote.accepted" \| "quote.rejected" \| "quote.deleted" \| "traveler.added" \| "traveler.updated" \| "traveler.removed" \| "trip.contact.updated" \| "document.created" \| "document.updated" \| "document.deleted"[] | yes | The event types to receive. Only types this key can READ are accepted (e.g. traveler.* needs travelers:read). max 21 items |

## Response `201`

| Field | Type | Required | Description |
|---|---|---|---|
| `object` | "webhook_endpoint" | yes |  |
| `id` | uuid | yes |  |
| `url` | string | yes |  |
| `description` | string | yes |  |
| `eventTypes` | "trip.created" \| "trip.updated" \| "trip.archived" \| "trip.restored" \| "trip.published" \| "trip.unpublished" \| "trip.deleted" \| "trip.itinerary.updated" \| "quote.created" \| "quote.updated" \| "quote.sent" \| "quote.accepted" \| "quote.rejected" \| "quote.deleted" \| "traveler.added" \| "traveler.updated" \| "traveler.removed" \| "trip.contact.updated" \| "document.created" \| "document.updated" \| "document.deleted"[] | yes |  |
| `enabled` | boolean | yes |  |
| `disabledReason` | "manual" \| "failing" \| "key_invalid" \| null | yes | Why it is off: switched off by a person, failing for 3 days, or its key is no longer usable. |
| `keyId` | uuid | yes | The API key it belongs to; its events are read with this key's permissions. |
| `previousSecretExpiresAt` | string \| null | yes | After a rotation, the old secret keeps signing until this moment. |
| `lastSuccessAt` | string \| null | yes |  |
| `failingSince` | string \| null | yes |  |
| `createdAt` | string | yes |  |
| `updatedAt` | string | yes |  |
| `secret` | string | yes | The signing secret (`whsec_…`). Shown ONLY in this response — store it now. |

## Errors

Errors are [problem details](https://api.bymundi.com/docs/guides/errors.md). Besides the refusals described above, any call like this one can return:

- [`invalid_request`](https://api.bymundi.com/problems/invalid_request.md) — Invalid request
- [`unauthorized`](https://api.bymundi.com/problems/unauthorized.md) — Missing or invalid API key
- [`insufficient_scope`](https://api.bymundi.com/problems/insufficient_scope.md) — Missing permission
- [`rate_limited`](https://api.bymundi.com/problems/rate_limited.md) — Rate limit reached
- [`idempotency_key_reused`](https://api.bymundi.com/problems/idempotency_key_reused.md) — Idempotency-Key reused
- [`request_in_progress`](https://api.bymundi.com/problems/request_in_progress.md) — Request in progress

## Examples

#### curl

```bash
curl -X POST https://api.bymundi.com/v1/webhook-endpoints \
  -H "Authorization: Bearer $BYMUNDI_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url":"string","eventTypes":["trip.created"]}'
```

#### JavaScript

```javascript
const res = await fetch("https://api.bymundi.com/v1/webhook-endpoints", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BYMUNDI_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"url":"string","eventTypes":["trip.created"]}),
});
if (!res.ok) throw new Error((await res.json()).detail);
const data = await res.json();
```

#### Python

```python
import os, uuid, requests

res = requests.post(
    "https://api.bymundi.com/v1/webhook-endpoints",
    headers={"Authorization": f"Bearer {os.environ['BYMUNDI_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
    json={"url":"string","eventTypes":["trip.created"]},
)
res.raise_for_status()
data = res.json()
```
