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"]
}'
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);
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"])
{
"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}`)."
}
{
"detail": {
"error": "invalid_url",
"message": "Webhook URL must start with https://"
}
}
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).
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.
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"]
}'
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);
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"])
{
"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}`)."
}
{
"detail": {
"error": "invalid_url",
"message": "Webhook URL must start with https://"
}
}
Create a webhook subscription
Method:POST | Endpoint: /v1/webhooks
Description: Register an HTTPS URL to receive webhook events. 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. |
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"]
}'
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);
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"])
{
"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}`)."
}
{
"detail": {
"error": "invalid_url",
"message": "Webhook URL must start with https://"
}
}
Responses
201 Created{
"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}`)."
}
secret now; later responses only show secret_prefix. See Signatures.
401 Unauthorized
{
"detail": {
"error": "unauthorized",
"message": "Invalid or revoked API key"
}
}
message values: Authorization Bearer token required, API key required (use Authorization: Bearer ib_pk_…), Invalid API key format, Customer account not found.
403 Forbidden
{
"detail": {
"error": "forbidden",
"message": "Account is suspended"
}
}
message is then Account is cancelled). Every API-key endpoint returns this.
422 Unprocessable Entity
{
"detail": {
"error": "invalid_url",
"message": "Webhook URL must start with https://"
}
}
error value: validation_error, for example an unknown event type:
{
"detail": {
"error": "validation_error",
"message": "event_types: unknown event type(s): message.bogus. See https://docs.ibubbl.com/webhooks/events"
}
}
{
"detail": {
"error": "internal_error",
"message": "Failed to create subscription"
}
}
Authorizations
Bearer API key. Format: Authorization: Bearer ib_pk_<64 hex> (70 characters). Generate keys from Dashboard → Settings → API Access.
Body
HTTPS URL that will receive webhook POST requests. HTTP is rejected (422 invalid_url).
"https://api.example.com/ibubbl-webhooks"
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; an unknown type returns 422 validation_error.
[
"message.sent",
"message.failed",
"message.inbound"
]
Response
Subscription created. Secret returned ONCE.
Unique subscription identifier (UUID).
The HTTPS URL receiving events.
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.
"whsec_a3f2c8d9"
ISO 8601 UTC timestamp.
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.
"whsec_a3f2c8d9e0f1b4d52c8a1f4e23b9d4f7ca8e51d2c3b4a5e6f7a8b9c0d1e2f3a4"
Reminder that the secret cannot be retrieved again. Present only on the create response.
Event filter, or null if subscribed to all events.
ISO 8601 UTC timestamp of the most recent successful delivery to this subscription, or null if no event has been delivered yet.
ISO 8601 UTC timestamp the subscription was revoked, or null if active. Revoked subscriptions stop receiving events.

