Create a webhook endpoint
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. Besides the refusals described above, any call like this one can return:
invalid_request— Invalid requestunauthorized— Missing or invalid API keyinsufficient_scope— Missing permissionrate_limited— Rate limit reachedidempotency_key_reused— Idempotency-Key reusedrequest_in_progress— Request in progress