> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bbrands.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> Create a webhook, keep the one-shot secret, choose events, activate, deactivate and rotate.

A **webhook** is a subscriber: a callback URL, a signing secret and a scope
(global or one account). A **webhook event** links that subscriber to one
catalogue event. Deliveries are recorded per subscriber in
`webhook_dispatch`.

<Note>
  Every call below requires a bearer token whose user holds the matching
  `webhook:*` / `webhook-event:*` permission. In the first release those
  permissions are granted to the `administrator` role only.
</Note>

## 1. Create the webhook

```bash theme={null}
curl -X POST https://api.bbrands.io/api/v3/webhook/webhook \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account": "8f3b1c2e-0d4a-4f2b-9c1e-6a7b8c9d0e1f",
    "description": "ERP sync for Maihue Chile",
    "is_global": false,
    "name": "erp-maihue-cl",
    "url": "https://erp.example.com/hooks/bbrands"
  }'
```

| Field         | Rules                                                                                                                                                                   |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`         | Required. HTTPS, public host, max 2048 chars. Loopback, private, link-local, metadata and `*.internal` hosts are rejected (`http://` is allowed only in `development`). |
| `is_global`   | Optional, default `false`. `true` receives every subscribed event regardless of owner.                                                                                  |
| `account`     | Required when `is_global` is `false`; ignored when `true`. Must be an existing account id.                                                                              |
| `name`        | Optional, max 100 chars.                                                                                                                                                |
| `description` | Optional, max 500 chars.                                                                                                                                                |

The response is the public webhook row **plus `secret`**, returned once:

```json theme={null}
{
  "data": {
    "account": "8f3b1c2e-0d4a-4f2b-9c1e-6a7b8c9d0e1f",
    "consecutive_failures": 0,
    "description": "ERP sync for Maihue Chile",
    "disabled_at": null,
    "disabled_reason": null,
    "document_id": "2f9a7c41-5d3e-4b8a-9f01-c2d3e4f5a6b7",
    "is_actived": true,
    "is_global": false,
    "name": "erp-maihue-cl",
    "secret": "whsec_Qm4sZ3JhbmRzTGFiX3NlY3JldF9leGFtcGxlXzMyYnl0ZXM",
    "url": "https://erp.example.com/hooks/bbrands"
  },
  "message": "Webhook record created successfully"
}
```

<Warning>
  `secret` is **never returned again**: `GET`, list, update and export omit
  it. Store it in your secret manager immediately. If it is lost, call
  [rotate-secret](#4-rotate-the-secret).
</Warning>

## 2. Choose events

Look up the catalogue ids you need, then replace the subscription set in one
call. The body is the **full** desired list — events not included are
unsubscribed (soft-deleted), events already present are kept, new ones are
inserted.

```bash theme={null}
# Find the ids
curl "https://api.bbrands.io/api/v3/webhook/event?filters=internal|in|payment-debt.created,payment-debt.updated&page_size=50" \
  -H "Authorization: Bearer $TOKEN"

# Replace the subscription set
curl -X PUT https://api.bbrands.io/api/v3/webhook/webhook/2f9a7c41-5d3e-4b8a-9f01-c2d3e4f5a6b7/events \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "events": ["<event id 1>", "<event id 2>"] }'
```

`events` must contain at least one id. `webhook.test` is always kept on
the set (create and replace-set prepend it) so the **Test** action has a
subscription to record against.

<Tip>
  Account-scoped webhooks only receive events whose `account_scope` is
  `account` **and** whose resolved owner matches. Subscribing an
  account-scoped webhook to a `none` event (e.g. `general.address.updated`)
  is allowed but will never fire — use a global webhook for those.
</Tip>

## 3. Activate and deactivate

Webhooks are created active. Toggle with:

```bash theme={null}
curl -X PATCH https://api.bbrands.io/api/v3/webhook/webhook/2f9a7c41-5d3e-4b8a-9f01-c2d3e4f5a6b7/active \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "is_actived": false }'
```

Inactive webhooks are skipped at routing time (no dispatch rows are written
for them). Activating a webhook also **clears** `consecutive_failures`,
`disabled_at` and `disabled_reason`, which is how an automatically disabled
subscriber is brought back — see
[Retries and failures](/webhooks/retries-and-failures#automatic-disable).

## 4. Rotate the secret

```bash theme={null}
curl -X POST https://api.bbrands.io/api/v3/webhook/webhook/2f9a7c41-5d3e-4b8a-9f01-c2d3e4f5a6b7/rotate-secret \
  -H "Authorization: Bearer $TOKEN"
```

Returns the public row plus the **new** `secret`, again only once. There is
no overlap window: every delivery signed after the call uses the new value.
Deploy the new secret on your side first, or expect a short burst of `401`
responses that the platform will retry on the backoff schedule.

## 5. Update or remove

* `PATCH /api/v3/webhook/webhook/{id}` — partial update of `url`, `name`,
  `description`, `is_global`, `account`. URL rules apply again.
* `DELETE /api/v3/webhook/webhook/{id}` — soft delete; the ledger is kept.
* `DELETE /api/v3/webhook/webhook/{id}/purge` — hard delete.

## From Horizon Enterprise

The same flows are available under **Webhooks** in Horizon Enterprise
(`administrator` role): the form shows the secret in a one-shot alert after
create and rotate, the event picker groups the catalogue by domain, and the
detail screen lists deliveries with filters, `last_error` and a **Replay**
action.
