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

# Webhooks

> Get a signed POST when an invoice is created, finalized, emailed, viewed, paid or voided.

## Register an endpoint

Needs the `webhooks:manage` scope. You can also register endpoints in **Settings → Webhooks**.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://invoice.horizonpay.co/api/v1/webhook-endpoints \
    -H "Authorization: Bearer $INVOICE_AI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com/webhooks/invoice-ai",
      "events": ["invoice.finalized", "invoice.paid"]
    }'
  ```

  ```ts Node.js theme={null}
  const endpoint = await invoiceai.webhookEndpoints.create({
    url: 'https://example.com/webhooks/invoice-ai',
    events: ['invoice.finalized', 'invoice.paid'],
  })
  console.log(endpoint.secret) // whsec_… Store it now.
  ```

  ```python Python theme={null}
  endpoint = client.webhook_endpoints.create(
      url="https://example.com/webhooks/invoice-ai",
      events=["invoice.finalized", "invoice.paid"],
  )
  print(endpoint.secret)  # whsec_… Store it now.
  ```

  ```bash CLI theme={null}
  invoice-ai webhooks create --url https://example.com/webhooks/invoice-ai --events invoice.finalized,invoice.paid
  ```
</CodeGroup>

```json Response theme={null}
{
  "data": {
    "id": "8f14e45f-ceea-467a-9575-2f9c1d3b7a60",
    "url": "https://example.com/webhooks/invoice-ai",
    "events": ["invoice.finalized", "invoice.paid"],
    "active": true,
    "disabled_at": null,
    "failure_count": 0,
    "created_at": "2026-10-04T08:30:00.000Z",
    "secret": "whsec_MfKQ9r6nRhD8yU2vXc4bWq7tLz0aPe3k"
  }
}
```

<Warning>
  The signing `secret` is returned only once, in this response. If you lose it, delete the endpoint and create a new one.
</Warning>

<ParamField body="url" type="string" required>
  Where to send events. It must:

  * use `https://`
  * resolve to a public address. Private, loopback, link-local, carrier-grade NAT, multicast and IPv6 ranges that embed an IPv4 address (NAT64, 6to4, Teredo) are refused.
  * not contain a username or password

  A URL that breaks these rules returns `422`. The address is checked again at every delivery.
</ParamField>

<ParamField body="events" type="string[]">
  The [events](#events) to receive. Leave it out to receive every event, including ones added later.
</ParamField>

To list endpoints, call `GET /webhook-endpoints`. To remove one, call `DELETE /webhook-endpoints/{id}`. You can't edit an endpoint, so delete it and create a new one.

## Events

| Event | Sent when |
| - | - |
| `invoice.created` | A draft is created. |
| `invoice.updated` | A draft is edited, including lines added or removed. |
| `invoice.finalized` | An invoice gets its number and becomes `open`. |
| `invoice.emailed` | An invoice is emailed. |
| `invoice.email_failed` | The email provider rejected an invoice email. |
| `invoice.viewed` | The customer opens the invoice link. At most once per invoice every 10 minutes. |
| `invoice.downloaded` | The customer downloads the PDF. At most once per invoice every 10 minutes. |
| `invoice.paid` | An invoice is marked paid. |
| `invoice.voided` | An invoice is voided. |

## Payload

```json theme={null}
{
  "id": "0d6c3e9a-51f2-4b8e-9a7d-2c4f6e8b1a03",
  "type": "invoice.paid",
  "created_at": "2026-10-04T09:12:44.123456+00:00",
  "data": {
    "object": {
      "id": "in_Pb2Xk7Mv4Qs9Lr1Wd6Tn3Fh8",
      "object": "invoice",
      "number": "INV-0042",
      "status": "paid",
      "customer": "cus_Nf3kQ8pR2mX7vB1cT9wL4sZ6",
      "currency": "USD",
      "total": 150000,
      "amount_due": 0
    }
  }
}
```

* `id` is the event id. It's the same for every endpoint that receives the event.
* `data.object` is the invoice as it was when the event happened, in the same shape as `GET /invoices`. It has no `lines`. Call `GET /invoices/{id}` for those.

Each request carries `webhook-id`, `webhook-timestamp` and `webhook-signature` headers. [Verify them](/verify-webhooks) before you trust the payload.

<Warning>
  Webhooks are currently delivered **once a day, at 03:00 UTC**. An event can take up to 24 hours to arrive. For anything time-sensitive, poll `GET /invoices` or `GET /invoices/{id}/events`.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.