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

# Create a webhook subscription

> Register an HTTPS endpoint to receive events. The response includes the signing secret **once** — store it immediately; it cannot be retrieved again. Use the secret to verify incoming webhook signatures (see [Signature verification](/guides/webhook-signatures)).

If `event_types` is omitted, the subscription receives all event types. You can register multiple subscriptions per account (e.g., one for production, one for staging).

Delivery is at-least-once with exponential backoff. Customer endpoints should be idempotent based on the event `id`.

### Create a webhook subscription

**Method:** `POST` | **Endpoint:** `/v1/webhooks`

**Description:** Register an HTTPS URL to receive [webhook events](https://docs.ibubbl.com/webhooks/events.md). The response is the only time the signing secret is shown.

**Authentication:** API key in the `Authorization` header: `Authorization: Bearer ib_pk_<64 hex>`. Create keys in the dashboard under **Settings → API access**.

#### Request Body

| Field | Type | Required | Description |
| - | - | - | - |
| `url` | string | Yes | Your HTTPS endpoint. Must start with `https://`. |
| `event_types` | array of strings | No | Only send these types. Leave it out, or send `null` or `[]`, to receive every type, including ones added later. Each entry must be a type from the [event catalog](https://docs.ibubbl.com/webhooks/events.md). |

```bash cURL theme={null}
curl -X POST https://api.ibubbl.com/v1/webhooks \
  -H "Authorization: Bearer $IBUBBL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/ibubbl-webhooks",
    "event_types": ["message.sent", "message.failed", "message.inbound"]
  }'
```

```typescript TypeScript theme={null}
const res = await fetch("https://api.ibubbl.com/v1/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.IBUBBL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://api.example.com/ibubbl-webhooks",
    event_types: ["message.sent", "message.failed", "message.inbound"],
  }),
  signal: AbortSignal.timeout(10_000),
});

const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.detail?.error}`);
// Store data.secret now: it is never shown again.
console.log(data.id, data.secret_prefix);
```

```python Python theme={null}
import os

import requests

API_KEY = os.environ["IBUBBL_API_KEY"]

res = requests.post(
    "https://api.ibubbl.com/v1/webhooks",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "url": "https://api.example.com/ibubbl-webhooks",
        "event_types": ["message.sent", "message.failed", "message.inbound"],
    },
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['detail']['error']}")
# Store data["secret"] now: it is never shown again.
print(data["id"], data["secret_prefix"])
```

```json 201 Created theme={null}
{
  "id": "4a5b6c7d-8e9f-0a1b-2c3d-4e5f6a7b8c9d",
  "url": "https://api.example.com/ibubbl-webhooks",
  "secret_prefix": "whsec_a3f2c8d9",
  "event_types": [
    "message.sent",
    "message.failed",
    "message.inbound"
  ],
  "created_at": "2026-11-05T14:23:45.123Z",
  "last_delivered_at": null,
  "revoked_at": null,
  "secret": "whsec_a3f2c8d9e0f1b4d52c8a1f4e23b9d4f7ca8e51d2c3b4a5e6f7a8b9c0d1e2f3a4",
  "warning": "Save this secret now — it cannot be retrieved again. Use it to verify the X-iBubbl-Signature header on incoming webhook POSTs (HMAC-SHA256 over `{t}.{raw body}`)."
}
```

```json 422 Unprocessable Entity theme={null}
{
  "detail": {
    "error": "invalid_url",
    "message": "Webhook URL must start with https://"
  }
}
```

#### Responses

**201 Created**

```json theme={null}
{
  "id": "4a5b6c7d-8e9f-0a1b-2c3d-4e5f6a7b8c9d",
  "url": "https://api.example.com/ibubbl-webhooks",
  "secret_prefix": "whsec_a3f2c8d9",
  "event_types": [
    "message.sent",
    "message.failed",
    "message.inbound"
  ],
  "created_at": "2026-11-05T14:23:45.123Z",
  "last_delivered_at": null,
  "revoked_at": null,
  "secret": "whsec_a3f2c8d9e0f1b4d52c8a1f4e23b9d4f7ca8e51d2c3b4a5e6f7a8b9c0d1e2f3a4",
  "warning": "Save this secret now — it cannot be retrieved again. Use it to verify the X-iBubbl-Signature header on incoming webhook POSTs (HMAC-SHA256 over `{t}.{raw body}`)."
}
```

Store `secret` now; later responses only show `secret_prefix`. See [Signatures](https://docs.ibubbl.com/guides/webhook-signatures.md).

**401 Unauthorized**

```json theme={null}
{
  "detail": {
    "error": "unauthorized",
    "message": "Invalid or revoked API key"
  }
}
```

Other `message` values: `Authorization Bearer token required`, `API key required (use Authorization: Bearer ib_pk_…)`, `Invalid API key format`, `Customer account not found`.

**403 Forbidden**

```json theme={null}
{
  "detail": {
    "error": "forbidden",
    "message": "Account is suspended"
  }
}
```

Your iBubbl account is suspended or cancelled (`message` is then `Account is cancelled`). Every API-key endpoint returns this.

**422 Unprocessable Entity**

```json theme={null}
{
  "detail": {
    "error": "invalid_url",
    "message": "Webhook URL must start with https://"
  }
}
```

Other `error` value: `validation_error`, for example an unknown event type:

```json theme={null}
{
  "detail": {
    "error": "validation_error",
    "message": "event_types: unknown event type(s): message.bogus. See https://docs.ibubbl.com/webhooks/events"
  }
}
```

**500 Internal Server Error**

```json theme={null}
{
  "detail": {
    "error": "internal_error",
    "message": "Failed to create subscription"
  }
}
```

The subscription was not saved. Retry the request.


## OpenAPI

````yaml POST /v1/webhooks
openapi: 3.1.0
info:
  title: iBubbl API
  version: v1
  description: >-
    The iBubbl REST API. Send iMessages, manage contacts, and subscribe to
    webhook events from your backend.


    Authenticate every request with a Bearer API key (`ib_pk_<hex>`) — generate
    one from your dashboard's API Access settings. See the
    [Authentication](/authentication) guide.
servers:
  - url: https://api.ibubbl.com
security:
  - BearerAuth: []
tags:
  - name: Messages
  - name: Contacts
  - name: Conversations
  - name: Webhooks
  - name: Inbound Events
paths:
  /v1/webhooks:
    post:
      tags:
        - Webhooks
      summary: Create a webhook subscription
      description: >-
        Register an HTTPS endpoint to receive events. The response includes the
        signing secret **once** — store it immediately; it cannot be retrieved
        again. Use the secret to verify incoming webhook signatures (see
        [Signature verification](/guides/webhook-signatures)).


        If `event_types` is omitted, the subscription receives all event types.
        You can register multiple subscriptions per account (e.g., one for
        production, one for staging).


        Delivery is at-least-once with exponential backoff. Customer endpoints
        should be idempotent based on the event `id`.
      operationId: v1_create_webhook_v1_webhooks_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookSubscriptionCreate'
      responses:
        '201':
          description: Subscription created. Secret returned ONCE.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionCreated'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '403':
          description: '`forbidden`: your iBubbl account is suspended or cancelled.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '422':
          description: >-
            `invalid_url` (URL is not HTTPS) or `validation_error` (for example
            an unknown event type).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '500':
          description: '`internal_error`: the subscription could not be saved. Retry.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
components:
  schemas:
    WebhookSubscriptionCreate:
      properties:
        url:
          type: string
          title: Url
          description: >-
            HTTPS URL that will receive webhook POST requests. HTTP is rejected
            (`422 invalid_url`).
          examples:
            - https://api.example.com/ibubbl-webhooks
        event_types:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
          title: Event Types
          description: >-
            Filter to a specific subset of events. Omit, or pass `null` or `[]`,
            to receive ALL event types, including ones added later. Every entry
            must be a type from the [Webhook Events catalog](/webhooks/events);
            an unknown type returns `422 validation_error`.
          examples:
            - - message.sent
              - message.failed
              - message.inbound
      type: object
      required:
        - url
      title: WebhookSubscriptionCreate
      example:
        event_types:
          - message.sent
          - message.failed
          - message.inbound
        url: https://api.example.com/ibubbl-webhooks
    WebhookSubscriptionCreated:
      properties:
        id:
          type: string
          title: Id
          description: Unique subscription identifier (UUID).
        url:
          type: string
          title: Url
          description: The HTTPS URL receiving events.
        secret_prefix:
          type: string
          title: Secret Prefix
          description: >-
            First 14 characters of the signing secret (`whsec_` + 8 hex). Useful
            for identifying which subscription's secret is in use without
            revealing the full value. The full secret is returned only once, at
            creation time.
          examples:
            - whsec_a3f2c8d9
        event_types:
          anyOf:
            - items:
                type: string
              type: array
            - type: 'null'
          title: Event Types
          description: Event filter, or null if subscribed to all events.
        created_at:
          type: string
          title: Created At
          description: ISO 8601 UTC timestamp.
        last_delivered_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Delivered At
          description: >-
            ISO 8601 UTC timestamp of the most recent successful delivery to
            this subscription, or null if no event has been delivered yet.
        revoked_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Revoked At
          description: >-
            ISO 8601 UTC timestamp the subscription was revoked, or null if
            active. Revoked subscriptions stop receiving events.
        secret:
          type: string
          title: Secret
          description: >-
            Full signing secret. **Save it now** — this is the only time it is
            returned. Use it to verify the `X-iBubbl-Signature` header on
            incoming webhook POSTs (HMAC-SHA256 over `{t}.{raw request body}`,
            keyed with this whole string). See [Signature
            verification](/guides/webhook-signatures).
          examples:
            - >-
              whsec_a3f2c8d9e0f1b4d52c8a1f4e23b9d4f7ca8e51d2c3b4a5e6f7a8b9c0d1e2f3a4
        warning:
          type: string
          title: Warning
          description: >-
            Reminder that the secret cannot be retrieved again. Present only on
            the create response.
      type: object
      required:
        - id
        - url
        - secret_prefix
        - created_at
        - secret
        - warning
      title: WebhookSubscriptionCreated
      example:
        created_at: '2026-05-17T14:23:45.123Z'
        event_types:
          - message.sent
          - message.failed
        id: 4a5b6c7d-8e9f-0a1b-2c3d-4e5f6a7b8c9d
        secret: whsec_a3f2c8d9e0f1b4d52c8a1f4e23b9d4f7ca8e51d2c3b4a5e6f7a8b9c0d1e2f3a4
        secret_prefix: whsec_a3f2c8d9
        url: https://api.example.com/ibubbl-webhooks
        warning: Save this secret now — it cannot be retrieved again.
    ApiError:
      properties:
        detail:
          allOf:
            - $ref: '#/components/schemas/ApiErrorDetail'
          description: >-
            Error envelope. Every `/v1` error response wraps a stable `error`
            code and a human-readable `message` in `detail`.
      type: object
      required:
        - detail
      title: ApiError
      example:
        detail:
          error: validation_error
          message: >-
            Exactly one of contact_id, to, chat_guid, or participant_phones is
            required
    ApiErrorDetail:
      properties:
        error:
          type: string
          title: Error
          description: >-
            Stable machine-readable error code. Use this for branching logic in
            your integration; the human-readable `message` is subject to change.
            See the Error Reference for the catalog.
          examples:
            - validation_error
        message:
          type: string
          title: Message
          description: Human-readable description of what went wrong.
          examples:
            - >-
              Exactly one of contact_id, to, chat_guid, or participant_phones is
              required
      type: object
      required:
        - error
        - message
      title: ApiErrorDetail
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ib_pk_<64 hex>
      description: >-
        Bearer API key. Format: `Authorization: Bearer ib_pk_<64 hex>` (70
        characters). Generate keys from Dashboard → Settings → API Access.

````

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