> ## 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.

# Webhooks Overview

> Receive events on your server in real time instead of polling.

Webhooks let iBubbl push events to your backend shortly after they happen — inbound replies from your contacts, delivery state changes, reactions, opt-outs. You avoid the latency and rate-limit cost of polling.

The first delivery attempt usually arrives within a minute of the event. There is no latency guarantee, so don't build logic that depends on precise timing.

## Subscription model

You register an HTTPS URL via [`POST /v1/webhooks`](https://docs.ibubbl.com/api-reference/webhooks/create). iBubbl returns a subscription with a signing secret. You can register **multiple subscriptions** per account — one per environment, one per service, or one filtered to a subset of event types.

```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.inbound", "contact.opted_out"]
  }'
```

```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.inbound", "contact.opted_out"],
  }),
  signal: AbortSignal.timeout(10_000),
});

const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.detail?.error}`);
console.log(data.id, data.secret); // store the secret now
```

```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.inbound", "contact.opted_out"],
    },
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['detail']['error']}")
print(data["id"], data["secret"])  # store the secret now
```

If you omit `event_types` (or pass `null` or `[]`), the subscription receives **all** events, including event types added later. See the [Webhook Events catalog](https://docs.ibubbl.com/webhooks/events.md) for the full list.

## Changing a subscription

Subscriptions can't be edited. To change the URL or the event filter, [create](https://docs.ibubbl.com/api-reference/webhooks/create.md) a new subscription, switch your endpoint to its new signing secret, then [revoke](https://docs.ibubbl.com/api-reference/webhooks/revoke.md) the old one. Both receive events while they overlap, with the same event `id`s, so your de-duplication covers the switch.

## The signing secret

The create response includes a `secret` field (format: `whsec_<64 hex chars>`). **This is the only time the full secret is returned.** Store it immediately in your secrets manager.

You use the secret to verify the `X-iBubbl-Signature` header on every incoming webhook POST, ensuring the request actually came from iBubbl and wasn't forged. See [Signature verification](https://docs.ibubbl.com/guides/webhook-signatures.md) for the algorithm and reference verifiers in Python + Node.

If you lose the secret, revoke the subscription and create a new one — iBubbl cannot recover or re-display it.

## Delivery and retries

Delivery is **at least once**, in no guaranteed order. Your endpoint must return a `2xx` within 10 seconds; anything else, including a `3xx` or a timeout, is retried up to 5 attempts over about 37 minutes, then marked dead. De-duplicate on the event `id`. [Retries and deduplication](https://docs.ibubbl.com/guides/webhook-retries.md) has the exact schedule, what iBubbl does and doesn't do after a failure, and de-duplication code.

## Best practices

> [accordion] Return 2xx fast
>
> Process asynchronously. iBubbl's 10s timeout is firm; slow endpoints look like failures and trigger unnecessary retries.

> [accordion] De-duplicate on the event ID
>
> Use the event `id`, plus `data.message_id` and `type` for the rare change recorded twice. Never de-duplicate on content. See [Retries and deduplication](https://docs.ibubbl.com/guides/webhook-retries.md).

> [accordion] Verify signatures on every request
>
> Don't make exceptions for "internal" testing or "trusted" networks. Signature verification is cheap and catches forged requests + misrouted traffic. See [Signature verification](https://docs.ibubbl.com/guides/webhook-signatures.md).

> [accordion] Watch the dashboard webhook log
>
> **Settings → Webhooks → Recent deliveries** shows every attempt, its HTTP status and retry state. Use it to spot endpoints that are silently failing. To try it out, see [Test webhooks locally](https://docs.ibubbl.com/guides/test-webhooks-locally.md).

## What's next

> [card] [Webhook Events](https://docs.ibubbl.com/webhooks/events.md)
>
> The catalog of event types and their payload shapes.

> [card] [Signature verification](https://docs.ibubbl.com/guides/webhook-signatures.md)
>
> HMAC-SHA256 verification with reference code in Python + Node, and a test vector.

> [card] [Retries and deduplication](https://docs.ibubbl.com/guides/webhook-retries.md)
>
> The retry schedule, and handling duplicates and out-of-order events.

> [card] [Test webhooks locally](https://docs.ibubbl.com/guides/test-webhooks-locally.md)
>
> Receive real events on your laptop through a tunnel.


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