# Webhooks (/en/docs/referencia/webhooks)

Base: `https://api.facturia.cl/v1`. Every endpoint in this chapter authenticates with `Authorization: Bearer` unless stated otherwise, and the error envelope is the one in [Errors](/en/docs/errores).

| Method   | Path                                                                                                                                                  | What it does                                |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `GET`    | [`/webhook_endpoints`](#get-webhook-endpoints)                                                                                                        | List webhook endpoints                      |
| `POST`   | [`/webhook_endpoints`](#post-webhook-endpoints)                                                                                                       | Register a webhook endpoint                 |
| `GET`    | [`/webhook_endpoints/{webhook_endpoint_id}`](#get-webhook-endpoints-webhook-endpoint-id)                                                              | Retrieve a webhook endpoint                 |
| `PATCH`  | [`/webhook_endpoints/{webhook_endpoint_id}`](#patch-webhook-endpoints-webhook-endpoint-id)                                                            | Update a webhook endpoint                   |
| `DELETE` | [`/webhook_endpoints/{webhook_endpoint_id}`](#delete-webhook-endpoints-webhook-endpoint-id)                                                           | Delete a webhook endpoint                   |
| `POST`   | [`/webhook_endpoints/{webhook_endpoint_id}/ping`](#post-webhook-endpoints-webhook-endpoint-id-ping)                                                   | Send a synthetic event to verify plumbing   |
| `POST`   | [`/webhook_endpoints/{webhook_endpoint_id}/rotate_secret`](#post-webhook-endpoints-webhook-endpoint-id-rotate-secret)                                 | Rotate the signing secret                   |
| `GET`    | [`/webhook_endpoints/{webhook_endpoint_id}/deliveries`](#get-webhook-endpoints-webhook-endpoint-id-deliveries)                                        | Inspect delivery attempts                   |
| `POST`   | [`/webhook_endpoints/{webhook_endpoint_id}/deliveries/{delivery_id}/retry`](#post-webhook-endpoints-webhook-endpoint-id-deliveries-delivery-id-retry) | Redeliver an event                          |
| `GET`    | [`/events`](#get-events)                                                                                                                              | Query the event ledger (retained 12 months) |
| `GET`    | [`/events/{event_id}`](#get-events-event-id)                                                                                                          | Retrieve an event                           |

## `GET /webhook_endpoints` [#get-webhook-endpoints]

**List webhook endpoints**

Webhook endpoints configured for the account, with their `enabled_events` and delivery health, secrets never included.

```bash
curl -sS https://api.facturia.cl/v1/webhook_endpoints \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter | In    | Required | Type    | Notes                                                   |
| --------- | ----- | -------- | ------- | ------------------------------------------------------- |
| `limit`   | query | —        | integer |                                                         |
| `cursor`  | query | —        | string  | Opaque cursor from a previous response's `next_cursor`. |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body                     |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `200`     | A list of endpoints.                                                                                                                                                                                                                                                                                    | lista de WebhookEndpoint |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse            |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse            |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse            |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse            |

## `POST /webhook_endpoints` [#post-webhook-endpoints]

**Register a webhook endpoint**

The `secret` is returned once, on creation, and never again.

```bash
curl -sS -X POST https://api.facturia.cl/v1/webhook_endpoints \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://api.miempresa.cl/hooks/facturia"}'
```

**Parameters**

| Parameter         | In     | Required | Type   | Notes                                                                                                                                                                                                                         |
| ----------------- | ------ | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Idempotency-Key` | header | —        | string | Client-generated key, unique per logical operation, retained 24h and scoped to (account, empresa, endpoint). Replays return the original response with `Facturia-Idempotent-Replay: true`. Same key + different body → `409`. |

**Body**

| Field            | Type      | Required | Notes                                                                            |
| ---------------- | --------- | -------- | -------------------------------------------------------------------------------- |
| `url`            | string    | yes      |                                                                                  |
| `description`    | string    | —        |                                                                                  |
| `enabled_events` | string\[] | —        | Event types, or `["*"]` for everything including future types. Default: `["*"]`. |
| `empresas`       | Rut\[]    | —        | Restrict deliveries to these empresas. Defaults to all the key can see.          |
| **Responses**    |           |          |                                                                                  |

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body                      |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `201`     | The created endpoint, including its signing secret.                                                                                                                                                                                                                                                     | WebhookEndpointWithSecret |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse             |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse             |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse             |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse             |

## `GET /webhook_endpoints/{webhook_endpoint_id}` [#get-webhook-endpoints-webhook-endpoint-id]

**Retrieve a webhook endpoint**

One webhook endpoint's configuration and delivery health, without its signing secret.

```bash
curl -sS https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter             | In   | Required | Type   | Notes |
| --------------------- | ---- | -------- | ------ | ----- |
| `webhook_endpoint_id` | path | yes      | string |       |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `200`     | The endpoint.                                                                                                                                                                                                                                                                                           | WebhookEndpoint |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse   |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse   |

## `PATCH /webhook_endpoints/{webhook_endpoint_id}` [#patch-webhook-endpoints-webhook-endpoint-id]

**Update a webhook endpoint**

Update `url`, `enabled_events` or `description`. The signing secret is unaffected — use `rotate_secret` to change it.

```bash
curl -sS -X PATCH https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://api.miempresa.cl/hooks/facturia","description":"…"}'
```

**Parameters**

| Parameter             | In   | Required | Type   | Notes |
| --------------------- | ---- | -------- | ------ | ----- |
| `webhook_endpoint_id` | path | yes      | string |       |

**Body**

| Field            | Type                   | Required | Notes |
| ---------------- | ---------------------- | -------- | ----- |
| `url`            | string                 | —        |       |
| `description`    | string                 | —        |       |
| `enabled_events` | string\[]              | —        |       |
| `empresas`       | Rut\[]                 | —        |       |
| `status`         | `enabled` · `disabled` | —        |       |
| **Responses**    |                        |          |       |

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `200`     | The updated endpoint.                                                                                                                                                                                                                                                                                   | WebhookEndpoint |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse   |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse   |

## `DELETE /webhook_endpoints/{webhook_endpoint_id}` [#delete-webhook-endpoints-webhook-endpoint-id]

**Delete a webhook endpoint**

Delete the endpoint and destroy its signing secret (current and previous) in the vault; both are removed together so no orphaned secret survives the row.

```bash
curl -sS -X DELETE https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter             | In   | Required | Type   | Notes |
| --------------------- | ---- | -------- | ------ | ----- |
| `webhook_endpoint_id` | path | yes      | string |       |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body          |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `204`     | Deleted.                                                                                                                                                                                                                                                                                                | —             |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |

## `POST /webhook_endpoints/{webhook_endpoint_id}/ping` [#post-webhook-endpoints-webhook-endpoint-id-ping]

**Send a synthetic event to verify plumbing**

Send a synthetic `ping` event to the endpoint's current URL, so an integrator can verify signature verification before relying on a real event.

```bash
curl -sS -X POST https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W/ping \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter             | In   | Required | Type   | Notes |
| --------------------- | ---- | -------- | ------ | ----- |
| `webhook_endpoint_id` | path | yes      | string |       |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `202`     | The delivery attempt.                                                                                                                                                                                                                                                                                   | WebhookDelivery |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse   |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse   |

## `POST /webhook_endpoints/{webhook_endpoint_id}/rotate_secret` [#post-webhook-endpoints-webhook-endpoint-id-rotate-secret]

**Rotate the signing secret**

The previous secret keeps signing for 24 hours so you can deploy without downtime.

```bash
curl -sS -X POST https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W/rotate_secret \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter             | In   | Required | Type   | Notes |
| --------------------- | ---- | -------- | ------ | ----- |
| `webhook_endpoint_id` | path | yes      | string |       |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body                      |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `200`     | The endpoint with its new secret.                                                                                                                                                                                                                                                                       | WebhookEndpointWithSecret |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse             |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse             |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse             |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse             |

## `GET /webhook_endpoints/{webhook_endpoint_id}/deliveries` [#get-webhook-endpoints-webhook-endpoint-id-deliveries]

**Inspect delivery attempts**

Delivery attempts for this endpoint, newest first, with each attempt's response status and whether it will be retried.

```bash
curl -sS https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W/deliveries \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter             | In    | Required | Type                               | Notes                                                   |
| --------------------- | ----- | -------- | ---------------------------------- | ------------------------------------------------------- |
| `webhook_endpoint_id` | path  | yes      | string                             |                                                         |
| `limit`               | query | —        | integer                            |                                                         |
| `cursor`              | query | —        | string                             | Opaque cursor from a previous response's `next_cursor`. |
| `status`              | query | —        | `pending` · `succeeded` · `failed` |                                                         |
| `event_id`            | query | —        | string                             |                                                         |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body                     |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `200`     | A list of deliveries.                                                                                                                                                                                                                                                                                   | lista de WebhookDelivery |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse            |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse            |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse            |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse            |

## `POST /webhook_endpoints/{webhook_endpoint_id}/deliveries/{delivery_id}/retry` [#post-webhook-endpoints-webhook-endpoint-id-deliveries-delivery-id-retry]

**Redeliver an event**

Rate-limited at 20/min **per account**, not per credential (ticket #152) — a leaked key
would otherwise turn this into a free outbound-request amplifier from Facturia's own IPs.
There is also a hard ceiling of 20 total delivery attempts per `(endpoint, event)` pair,
answered as `409 too_many_replays`.

```bash
curl -sS -X POST https://api.facturia.cl/v1/webhook_endpoints/whe_01K2RF5N8Q2W/deliveries/whd_01K2RG1P4M7X01/retry \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter             | In   | Required | Type   | Notes |
| --------------------- | ---- | -------- | ------ | ----- |
| `webhook_endpoint_id` | path | yes      | string |       |
| `delivery_id`         | path | yes      | string |       |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body            |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `202`     | The new delivery attempt.                                                                                                                                                                                                                                                                               | WebhookDelivery |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse   |
| `404`     | No such object under this key.                                                                                                                                                                                                                                                                          | ErrorResponse   |
| `409`     | State conflict — idempotency reuse, duplicate external\_id, artifact not ready.                                                                                                                                                                                                                         | ErrorResponse   |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse   |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse   |

## `GET /events` [#get-events]

**Query the event ledger (retained 12 months)**

Retention is 12 months — deliberately longer than a typical webhook ledger, so a whole F29
tax cycle stays replayable. Webhook delivery is at-least-once and unordered; deduplicate
on `id`.

**Polling instead of webhooks.** If you cannot expose an HTTPS endpoint — a personal agent,
a laptop, anything behind NAT — this endpoint is the supported substitute, and `?after=`
is the parameter that makes it work:

GET /v1/events?after=evt\_01K2RG1P4M7X\&limit=100

`after` pages FORWARDS: oldest first, strictly newer than the id you name, `next_cursor`
advancing away from it. The loop is `after = next_cursor ?? after`, processing `data` in
order; it never re-reads a row and needs no set of seen ids. `after` accepts both an
`evt_…` id (the last event you processed) and a `cur_…` value from `next_cursor`, so your
stored checkpoint and the token you are handed are interchangeable. When `has_more` is
`false`, `next_cursor` is `null` — keep the `id` of the last event you processed and send
that next time.

`cursor` is the opposite direction and the default: newest first, "older than this", which
is what a dashboard reading a ledger wants. Sending both is `400` rather than a silent
precedence — they name positions in opposite orders, and honouring either would skip rows
for half the callers with nothing to tell them so.

```bash
curl -sS https://api.facturia.cl/v1/events \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter         | In    | Required | Type    | Notes                                                                                                                                                                          |
| ----------------- | ----- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `limit`           | query | —        | integer |                                                                                                                                                                                |
| `cursor`          | query | —        | string  | Opaque cursor from a previous response's `next_cursor`.                                                                                                                        |
| `after`           | query | —        | string  | Forward checkpoint: return events strictly NEWER than this one, oldest first. An `evt_…` id or a `cur_…` value from a previous forward page. Mutually exclusive with `cursor`. |
| `type`            | query | —        | string  |                                                                                                                                                                                |
| `empresa_rut`     | query | —        | Rut     |                                                                                                                                                                                |
| `created_at[gte]` | query | —        | string  |                                                                                                                                                                                |
| `created_at[lte]` | query | —        | string  | Upper bound, inclusive. Honoured since the endpoint shipped and undeclared until now, which made "give me one closed month of the ledger" look impossible from the contract.   |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body           |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| `200`     | A list of events.                                                                                                                                                                                                                                                                                       | lista de Event |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse  |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse  |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse  |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse  |

## `GET /events/{event_id}` [#get-events-event-id]

**Retrieve an event**

One event from the account's own ledger — the same object a webhook delivery carries — for a caller reconciling by id rather than by subscription.

```bash
curl -sS https://api.facturia.cl/v1/events/evt_01K2RG1P4M7X \
  -H "Authorization: Bearer $FACTURIA_KEY"
```

**Parameters**

| Parameter  | In   | Required | Type   | Notes |
| ---------- | ---- | -------- | ------ | ----- |
| `event_id` | path | yes      | string |       |

**Responses**

| Code      | Meaning                                                                                                                                                                                                                                                                                                 | Body          |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `200`     | The event.                                                                                                                                                                                                                                                                                              | Event         |
| `401`     | Missing, malformed or revoked key.                                                                                                                                                                                                                                                                      | ErrorResponse |
| `403`     | Valid key, not permitted — scope, empresa allowlist, or mode.                                                                                                                                                                                                                                           | ErrorResponse |
| `429`     | Too many requests.                                                                                                                                                                                                                                                                                      | ErrorResponse |
| `default` | An error not otherwise enumerated for this operation, in the same §5.5 envelope as every other refusal. Branch on `error.code`, never on the HTTP status alone or on `error.message` — a code this operation has not been seen to throw yet is still safer to route on than a string meant for a human. | ErrorResponse |
