Inkbox

> # Documentation index
> Fetch the complete documentation index at: https://inkbox.ai/sitemap.xml
> Use this file to discover all available pages before exploring further.

---

# Webhook Subscriptions
description: Create, list, update, and delete webhook subscriptions that fan event delivery out to your HTTPS endpoints

---


# Webhook Subscriptions

A webhook subscription names one owner — a [mailbox](/docs/api/mail/mailboxes) **or** a [phone number](/docs/api/phone/numbers) — one HTTPS destination URL, and a non-empty list of event types from the [webhook catalog](/docs/webhooks). When a matching event fires, Inkbox POSTs the payload to the subscription's URL.

You can attach up to **20 active subscriptions per owner** (mailbox or phone number). Each URL receives its own POST per event, independently — one slow receiver doesn't block delivery to the others. A 21st `POST /webhooks/subscriptions` on the same owner returns 409; delete an existing subscription first.

> **`phone.incoming_call` is not subscribable.** That event is a synchronous control-plane callback — the response body decides whether Inkbox answers the call — so it can't fan out. Configure it via the `incoming_call_webhook_url` field on the [phone number resource](/docs/api/phone/numbers) instead.

## Auth

| Caller | Visibility |
| :--- | :--- |
| [Admin-scoped API key](/docs/api-keys) | Every subscription in the organization. |
| Human session via the [Inkbox Console](https://inkbox.ai/console) | Every subscription in the organization. |
| Claimed [agent-scoped API key](/docs/api-keys) | Only subscriptions whose owning mailbox or phone number belongs to that agent's identity. |

Unclaimed agent-scoped API keys are rejected.

---

## Create subscription `POST`


Create a new subscription on a mailbox or a phone number.

### Request body

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `mailbox_id` | UUID | Conditionally | Owning mailbox. Exactly one of `mailbox_id` / `phone_number_id` must be set. |
| `phone_number_id` | UUID | Conditionally | Owning phone number. Exactly one of `mailbox_id` / `phone_number_id` must be set. |
| `url` | string | Yes | HTTPS destination for delivered events. |
| `event_types` | array of strings | Yes | Non-empty, distinct list of event types. Every entry must belong to the owner's channel (`mailbox_id` → `message.*`; `phone_number_id` → `text.*`). |

### Request example

```json
{
    "mailbox_id": "73fdb447-4d3a-4a31-bf05-7373d6dfdf74",
    "url": "https://yourapp.example.com/webhooks/inkbox",
    "event_types": ["message.received", "message.bounced"]
}
```

### Response (201)

Returns the new subscription object. See [Subscription object](#subscription-object).

### Validation errors

| Status | Reason |
| :--- | :--- |
| `422` | Body specified neither or both of `mailbox_id` / `phone_number_id`. |
| `422` | `event_types` was empty or contained duplicates. |
| `422` | An event type doesn't belong to the owner's channel (e.g. `text.received` on a `mailbox_id`). |
| `422` | `phone.incoming_call` was included — managed via the phone-number resource instead. |
| `403` | `mailbox_id` belongs to a different organization than the caller. |
| `404` | `mailbox_id` or `phone_number_id` is not visible to the caller (does not exist, or hidden by access scope). |
| `409` | An active subscription with the same `(owner, url)` pair already exists. |

### Code examples

**cURL**

```bash
curl -X POST "https://inkbox.ai/api/v1/webhooks/subscriptions" \\
    -H "X-API-Key: YOUR_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d '{
      "mailbox_id": "73fdb447-4d3a-4a31-bf05-7373d6dfdf74",
      "url": "https://yourapp.example.com/webhooks/inkbox",
      "event_types": ["message.received", "message.bounced"]
    }'
```

**JavaScript**

```javascript
const response = await fetch(
    "https://inkbox.ai/api/v1/webhooks/subscriptions",
    {
        method: "POST",
        headers: {
            "X-API-Key": "YOUR_API_KEY",
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            mailbox_id: "73fdb447-4d3a-4a31-bf05-7373d6dfdf74",
            url: "https://yourapp.example.com/webhooks/inkbox",
            event_types: ["message.received", "message.bounced"],
        }),
    }
);
const subscription = await response.json();
```

**Python**

```python
import requests

response = requests.post(
    "https://inkbox.ai/api/v1/webhooks/subscriptions",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "mailbox_id": "73fdb447-4d3a-4a31-bf05-7373d6dfdf74",
        "url": "https://yourapp.example.com/webhooks/inkbox",
        "event_types": ["message.received", "message.bounced"],
    },
)
subscription = response.json()
```

---

## List subscriptions `GET`


List active subscriptions visible to the caller, newest first. Returns an object with a `subscriptions` array — not a bare array.

### Query parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `mailbox_id` | UUID | Filter by owning mailbox. Mutually exclusive with `phone_number_id`. |
| `phone_number_id` | UUID | Filter by owning phone number. Mutually exclusive with `mailbox_id`. |
| `url` | string | Filter by exact destination URL. |
| `event_type` | string | Filter by event type. `phone.incoming_call` is rejected — that event isn't stored as a subscription. |

Filters AND-combine. Foreign-organization rows are filtered out, so they never appear in results.

### Response (200)

```json
{
    "subscriptions": [
        {
            "id": "9e1b3f2d-c4a6-4f8e-91bf-71d2c2e4f0a1",
            "organization_id": "org_2abc123def456",
            "mailbox_id": "73fdb447-4d3a-4a31-bf05-7373d6dfdf74",
            "phone_number_id": null,
            "url": "https://yourapp.example.com/webhooks/inkbox",
            "event_types": ["message.received", "message.bounced"],
            "status": "active",
            "created_at": "2026-04-10T18:00:00Z",
            "updated_at": "2026-04-10T18:00:00Z"
        }
    ]
}
```

### Error responses

| Status | Reason |
| :--- | :--- |
| `422` | Both `mailbox_id` and `phone_number_id` supplied. |
| `422` | `event_type=phone.incoming_call`. |

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/webhooks/subscriptions?mailbox_id=73fdb447-4d3a-4a31-bf05-7373d6dfdf74" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const url = new URL("https://inkbox.ai/api/v1/webhooks/subscriptions");
url.searchParams.set("mailbox_id", "73fdb447-4d3a-4a31-bf05-7373d6dfdf74");
const response = await fetch(url, {
    headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { subscriptions } = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://inkbox.ai/api/v1/webhooks/subscriptions",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"mailbox_id": "73fdb447-4d3a-4a31-bf05-7373d6dfdf74"},
)
subscriptions = response.json()["subscriptions"]
```

---

## Get subscription `GET`


### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `sub_id` | UUID | Unique subscription identifier. |

### Response (200)

Returns the subscription object. See [Subscription object](#subscription-object).

### Error responses

| Status | Reason |
| :--- | :--- |
| `404` | Subscription does not exist or is not visible to the caller. Foreign-organization subscriptions return `404`. |

### Code examples

**cURL**

```bash
curl -X GET "https://inkbox.ai/api/v1/webhooks/subscriptions/SUB_ID" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/webhooks/subscriptions/${subId}`,
    { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const subscription = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    f"https://inkbox.ai/api/v1/webhooks/subscriptions/{sub_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
subscription = response.json()
```

---

## Update subscription `PATCH`


Update a subscription's destination URL and/or event list in place. The owner FKs are immutable — to move a subscription to a different mailbox or phone number, delete it and create a new one.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `sub_id` | UUID | Unique subscription identifier. |

### Request body

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| `url` | string | No | New HTTPS destination. Omit to leave unchanged. |
| `event_types` | array of strings | No | New non-empty, distinct list of event types. Same channel-coherence rules as create. Omit to leave unchanged. |

Supplying both fields is fine. Supplying neither no-ops.

### Request example

```json
{
    "event_types": ["message.received", "message.delivered", "message.bounced"]
}
```

### Response (200)

Returns the updated subscription object. See [Subscription object](#subscription-object).

### Validation errors

| Status | Reason |
| :--- | :--- |
| `422` | `event_types` was empty or contained duplicates. |
| `422` | An event type doesn't belong to the owner's channel. |
| `422` | `phone.incoming_call` was included. |
| `404` | Subscription does not exist or is not visible to the caller. |
| `409` | The new `(owner, url)` collides with another active subscription. |

### Code examples

**cURL**

```bash
curl -X PATCH "https://inkbox.ai/api/v1/webhooks/subscriptions/SUB_ID" \\
    -H "X-API-Key: YOUR_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d '{"event_types": ["message.received", "message.delivered", "message.bounced"]}'
```

**JavaScript**

```javascript
const response = await fetch(
    `https://inkbox.ai/api/v1/webhooks/subscriptions/${subId}`,
    {
        method: "PATCH",
        headers: {
            "X-API-Key": "YOUR_API_KEY",
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            event_types: ["message.received", "message.delivered", "message.bounced"],
        }),
    }
);
const subscription = await response.json();
```

**Python**

```python
import requests

response = requests.patch(
    f"https://inkbox.ai/api/v1/webhooks/subscriptions/{sub_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"event_types": ["message.received", "message.delivered", "message.bounced"]},
)
subscription = response.json()
```

---

## Delete subscription `DELETE`


Remove a subscription. Inkbox stops delivering events to the URL immediately. Subsequent reads on the same `sub_id` return `404`.

### Path parameters

| Parameter | Type | Description |
| :--- | :--- | :--- |
| `sub_id` | UUID | Unique subscription identifier. |

### Response (204)

No body.

### Error responses

| Status | Reason |
| :--- | :--- |
| `404` | Subscription does not exist or is not visible to the caller. |

### Code examples

**cURL**

```bash
curl -X DELETE "https://inkbox.ai/api/v1/webhooks/subscriptions/SUB_ID" \\
    -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
await fetch(
    `https://inkbox.ai/api/v1/webhooks/subscriptions/${subId}`,
    {
        method: "DELETE",
        headers: { "X-API-Key": "YOUR_API_KEY" },
    }
);
```

**Python**

```python
import requests

requests.delete(
    f"https://inkbox.ai/api/v1/webhooks/subscriptions/{sub_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
```

---

## Subscription object

| Field | Type | Description |
| :--- | :--- | :--- |
| `id` | UUID | Unique subscription identifier. |
| `organization_id` | string | Owning organization (e.g. `"org_2abc123def456"`). Server-derived from the owner — never read from the request body. |
| `mailbox_id` | UUID \| null | Owning mailbox. Populated when this is a mail subscription; `null` otherwise. |
| `phone_number_id` | UUID \| null | Owning phone number. Populated when this is a text subscription; `null` otherwise. |
| `url` | string | HTTPS destination for delivered events. |
| `event_types` | array of strings | Event types this subscription delivers. Every value belongs to the owner's channel. |
| `status` | string | Always `"active"` on returned rows. Deleted rows are not returned by any endpoint. |
| `created_at` | string | ISO 8601 timestamp the subscription was created. |
| `updated_at` | string | ISO 8601 timestamp the subscription was last updated. |

## Event types

`event_types` is a non-empty list drawn from the same catalog the webhook payload pages document:

| Channel | Allowed values |
| :--- | :--- |
| Mail (`mailbox_id` owner) | `message.received`, `message.sent`, `message.forwarded`, `message.delivered`, `message.bounced`, `message.failed` |
| Phone text (`phone_number_id` owner) | `text.received`, `text.sent`, `text.delivered`, `text.delivery_failed`, `text.delivery_unconfirmed` |

`phone.incoming_call` is not in this list. It is managed via `incoming_call_webhook_url` on the [phone number resource](/docs/api/phone/numbers).

## Related

- [Webhooks guide](/docs/webhooks) — payload verification, typed receiver examples
- [Mail webhooks reference](/docs/api/mail/webhooks) — `message.*` payload shapes
- [Phone webhooks reference](/docs/api/phone/webhooks) — `text.*` and `phone.incoming_call` payload shapes
- [Signing keys](/docs/signing-keys) — rotate the org-level HMAC key used to sign webhook bodies
