Webhooks
Webhooks allow external systems to receive notifications when events occur in oHallo. This page covers the current state of webhook support and recommended patterns for real-time data access.
Current status
Section titled “Current status”oHallo’s webhook system is under active development. The platform uses internal event streaming (Kafka) to propagate events between services, but external webhook delivery is not yet generally available.
Internal events
Section titled “Internal events”The following events flow through the platform internally:
| Event | Description |
|---|---|
channel.message_received | A new message arrived on a channel |
conversation.updated | A conversation’s status, summary, or message count changed |
These events power the real-time dashboard updates via WebSocket/SSE but are not yet exposed as external webhooks.
Account provisioning webhook
Section titled “Account provisioning webhook”One webhook is currently active for platform operations:
organization.created: When a new organisation is created in the authentication provider, oHallo automatically provisions an account, creates a default brand and workspace, and sets up system MCP connections. This is an internal platform webhook and is not configurable.
Planned webhook events
Section titled “Planned webhook events”The following webhook events are planned for a future release:
| Event | Trigger | Payload |
|---|---|---|
conversation.created | A new conversation is started (inbound or outbound) | Conversation ID, channel, contact, workspace |
conversation.resolved | A conversation is marked as resolved | Conversation ID, outcome, resolution method (assistant or human) |
conversation.status_changed | A conversation transitions between statuses | Conversation ID, old status, new status |
message.received | A new inbound message is received | Message ID, conversation ID, sender, body preview |
message.sent | An outbound message is delivered | Message ID, conversation ID, delivery status |
attention_item.created | An attention item is raised | Attention item ID, type, conversation ID |
kb_entry.proposed | The learning loop proposes a new KB entry | Entry ID, question, answer, confidence |
policy_entry.proposed | The learning loop proposes a new policy entry | Entry ID, topic, rule text, confidence |
When available, webhooks will use the following delivery model:
- HTTPS POST to your registered endpoint
- JSON payload with event type and data
- HMAC-SHA256 signature in the
X-OHallo-Signatureheader for verification - Retry with exponential backoff on 4xx/5xx responses (up to 3 attempts)
- Idempotency key in the payload to handle duplicate deliveries
Recommended approach: polling
Section titled “Recommended approach: polling”Until external webhooks are available, use the REST API to poll for changes. Here is a practical pattern:
Poll for new or updated conversations
Section titled “Poll for new or updated conversations”GET /api/conversations is cursor-paginated: the response is { "data": [...], "nextCursor": "..." }, and nextCursor is omitted once you reach the end. Track the newest conversation you have already seen so you only act on genuinely new activity. To page through a large result set, pass the cursor value back on the next call.
const API_BASE = "https://api.ohallo.eu";const API_KEY = "sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx";const WORKSPACE_ID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const seen = new Set<string>();
async function pollConversations(): Promise<void> { const response = await fetch( `${API_BASE}/api/conversations?workspaceId=${WORKSPACE_ID}&limit=50`, { headers: { "Authorization": `Bearer ${API_KEY}`, }, } );
if (!response.ok) { console.error(`Poll failed: ${response.status}`); return; }
const data = await response.json();
for (const conversation of data.data) { if (seen.has(conversation.id)) continue; seen.add(conversation.id); // Process each new or updated conversation console.log(`Conversation ${conversation.id}: ${conversation.status}`); }
// `data.nextCursor` is present only when more pages remain. Pass it back as // `&cursor=<value>` to continue paging; omit it to start from the top again.}
// Poll every 30 secondssetInterval(pollConversations, 30_000);Poll for pending KB entries
Section titled “Poll for pending KB entries”async function pollPendingKBEntries(): Promise<void> { const response = await fetch( `${API_BASE}/api/workspaces/${WORKSPACE_ID}/kb-entries/pending`, { headers: { "Authorization": `Bearer ${API_KEY}`, }, } );
if (!response.ok) return;
// The pending endpoint returns a bare JSON array of entries. const entries = await response.json();
for (const entry of entries) { // Review and auto-approve entries with high confidence if (entry.confidence && entry.confidence >= 0.9) { await fetch(`${API_BASE}/api/kb-entries/${entry.id}/approve`, { method: "POST", headers: { "Authorization": `Bearer ${API_KEY}`, }, }); console.log(`Auto-approved KB entry: ${entry.question}`); } }}
// Poll every 5 minutessetInterval(pollPendingKBEntries, 300_000);Polling guidelines
Section titled “Polling guidelines”- Conversations: Poll every 15 to 30 seconds for near-real-time updates. The list is ordered by recent activity, so the first page surfaces the latest changes; track ids you have already processed to avoid acting on the same conversation twice.
- KB and policy entries: Poll every 5 to 15 minutes. Pending entries do not require immediate action.
- Attention items: Poll every 1 to 5 minutes depending on urgency requirements.
- Rate limits: The API allows 600 requests per minute per client IP in production. A typical integration polling conversations (every 30s), KB entries (every 5min), and attention items (every 2min) uses only a few requests per minute, well within budget.
Stay informed
Section titled “Stay informed”Webhook support will be announced in the changelog. If you have specific requirements for event delivery, contact the oHallo team to discuss your use case.