# Create a webhook endpoint

`POST /v1/webhooks`

Adds a webhook endpoint that Soosh sends events to, signed with a new secret.

Needs `webhooks:write`.

> Over the API the `secret` is returned only here and when you rotate it, so store it now. People who can edit webhooks can also see it in the app.

## Body

- `url` (string, required): An `https://` URL on the internet. It can't have a username or password, or point to an internal or private address.
- `events` (array of strings, required): One of `message.received`, `message.sent`, `message.status`, `contact.created`, `contact.updated`, `contact.deleted`, `template.status`, `chat.assigned`, `chat.closed`, `chat.reopened`, `broadcast.status`. At least 1 item.
- `description` (string): A note to tell your endpoints apart. Up to 500 characters.
- `headers` (object): Custom headers sent with every request. You can set at most 10. Names are 1 to 64 letters, digits or hyphens. Values are up to 1024 characters, with no line breaks. The API returns every value as `********`. A map; each value is a string. Up to 10 keys.

## Headers

- `Idempotency-Key` (string): Send one so a retry doesn't create the endpoint twice. See [Retrying safely](/reference/api/#retrying-safely). 1 to 255 characters.

## Choosing the events

A key needs the read scope of every event the endpoint subscribes to: `messages:read` for `message.*`, `contacts:read` for `contact.*`, `templates:read` for `template.*` and `broadcasts:read` for `broadcast.*`. The API has no read-only chat scope, so `chat.*` events need `chats:write`.

Every event is listed in [Webhook events](/reference/api/webhook-events/).

## Headers you can't set

`Host`, `Content-Length`, `Content-Type`, `Content-Encoding`, `Transfer-Encoding`, `TE`, `Trailer`, `Connection`, `Keep-Alive`, `Upgrade`, `Expect`, `User-Agent`, `Proxy-*` and `Webhook-*`.

## After you create it

Check each request's signature with the secret. See [Verify webhooks](/guides/api/verify-webhooks/).

## Example

```sh
curl -X POST https://app.soosh.io/v1/webhooks \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Idempotency-Key: create-webhook-message-statuses-and-1" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/soosh",
    "description": "Order updates",
    "events": [
      "message.status",
      "contact.created"
    ],
    "headers": {
      "X-Api-Token": "your-own-token"
    }
  }'
```

Response 201:

```json
{
  "id": "4c6e8a0c-2e4b-4d6f-b8a0-1c3e5a7c9e4b",
  "url": "https://hooks.example.com/soosh",
  "description": "Order updates",
  "events": [
    "message.status",
    "contact.created"
  ],
  "headers": {
    "X-Api-Token": "********"
  },
  "status": "active",
  "disabled_reason": null,
  "paused_until": null,
  "previous_secret_expires_at": null,
  "created_at": "2026-10-14T09:20:05.631Z",
  "updated_at": "2026-10-14T09:20:05.631Z",
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSwQ3x7Vn5hT0c="
}
```

## Returns

201. The endpoint, with its `secret`.

## Errors

Besides the ones every request can get (https://docs.soosh.io/reference/api/errors/):

- 403 `scope_missing`: The key lacks the read scope of an event the endpoint subscribes to. `error.details.scope` says which.
- 403 `feature_not_in_plan`: An event isn't on your plan.
- 422 `limit_reached`: The workspace already has its `webhook_endpoints` limit.
- 422 `invalid_field`: The `url`, a header or the `events` break a rule: for example an `http://` URL, a private address, more than 10 headers or an unknown event.
- 409 `idempotency_in_progress`: A request with the same `Idempotency-Key` is still running.
- 422 `idempotency_mismatch`: The `Idempotency-Key` was used before for a different request, or by another of your API keys.