Webhooks
The relay POSTs chat events to your backend under
/api/v1/chat-callbacks/*, each signed with HMAC#1 — the same recipe
as your outbound calls, in the other direction. The
Python SDK adapter mounts these endpoints and
verifies the signature for you.
Where callbacks land
Section titled “Where callbacks land”The relay builds the callback URL from the tenant’s callback_url_base
(configured at provisioning or via
update/tenant): it appends
/chat-callbacks/<event> and signs each request.
POST {callback_url_base}/api/v1/chat-callbacks/<event>The 9 events
Section titled “The 9 events”The adapter mounts nine endpoints. Six have existed since the first phase;
the three agent_* events arrive with agent takeover.
| Path | Event |
|---|---|
POST /api/v1/chat-callbacks/conversation-started | New conversation opened |
POST /api/v1/chat-callbacks/message-received | The visitor sent a message |
POST /api/v1/chat-callbacks/ticket-created | Ticket created automatically by escalation |
POST /api/v1/chat-callbacks/ticket-assigned | Ticket taken by a human agent |
POST /api/v1/chat-callbacks/ticket-resolved | Ticket marked resolved |
POST /api/v1/chat-callbacks/escalation-triggered | The bot escalated to a human |
POST /api/v1/chat-callbacks/agent-assigned | An operator took the conversation |
POST /api/v1/chat-callbacks/agent-released | The operator returned the conversation to the queue |
POST /api/v1/chat-callbacks/agent-resolved | The operator marked the conversation resolved |
Mount the adapter (FastAPI)
Section titled “Mount the adapter (FastAPI)”BloonioChatAdapter.from_env(app) mounts the nine endpoints and wires the
client from the BLOONIO_CHAT_* variables.
from fastapi import FastAPIfrom bloonio_chat_relay_client.adapters.fastapi import BloonioChatAdapter
app = FastAPI()BloonioChatAdapter.from_env(app) # mounts /api/v1/chat-callbacks/* + wires the clientRegister handlers to react to events on your side. Handlers are
optional: an event with no registered handler is still accepted (HMAC#1
signature verified), logged at debug level, and returns
{"received": true}.
async def on_ticket_created(event): ... # update your CRM, notify, etc.
BloonioChatAdapter.from_env(app, handlers={ "ticket-created": on_ticket_created,})Shape of the agent_* events
Section titled “Shape of the agent_* events”The three agent-takeover events share the same body shape:
{ "conversation_id": "<chat_api ObjectId>", "tenant_id": "<UUID v7>", "status": "ASSIGNED" | "WAITING" | "RESOLVED", "operator_id": "<UUID v7>", "operator_display_name": "Sarah Chen", // only on agent-assigned "visitor_session_id": "vs_<base64url>", "escalation_reason": "user_requested" | "restricted_topic" | "low_confidence" | "direct" | null, "claim_count": 1, "claimed_at": "2026-05-22T12:34:56Z", "released_at": null, "resolved_at": null}Use it to update your CRM, send push notifications or trigger analytics.
HMAC verification
Section titled “HMAC verification”- Read the three
X-Bloonio-*headers from the incoming request. - Recompute
hex(hmac_sha256(tenant_secret, f"{ts_ms}.{sha256_hex(body)}")). - Compare in constant time; check timestamp freshness and the absence of replay.
- If everything passes, process the event; otherwise, respond
401.
The SDK adapter does exactly this before calling your handlers — prefer it to a hand-rolled verification.
Next steps
Section titled “Next steps”- Install and configure the SDK. See Python SDK.
- The shared signing recipe. See Authentication.
- The generic callback contract. See Webhooks & callbacks.