Client reporting
The Badala AI agent talks to your customers on your WhatsApp or Instagram. This page is about the other half: getting what those customers wanted into a system you already use — a board, a CRM, a helpdesk — the moment it is understood, so nobody has to watch an inbox they did not ask for.
How it works
Two things happen from a single decision, and they are independent of each other. Inside Badala, the agent answers the customer, drafts a reply for a teammate, or escalates: that is about who replies. Outward, an issue.reported webhook fires with what the customer actually wanted.
That separation is the point. A reservation the agent handled from start to finish still belongs on your board, and a complaint reaches you whether or not a human was pulled in.
Do not build your board on conversation.escalated. That event only fires when the agent gives up, so you would see every problem and none of the work.
Subscribe
Open Settings → Webhooks, add your URL, and tick issue.reported. Copy the signing secret: it is shown once, at creation. Everything on the outbound webhooks page applies here too, including the delivery log, manual retry, and the test-fire button.
The payload
POST https://your-platform.example.com/badala/reports
Content-Type: application/json
X-Badala-Event: issue.reported
X-Badala-Signature: sha256=<hex>
X-Badala-Timestamp: 1786641657
{
"event": "issue.reported",
"data": {
"report_id": "cmsr9unnl00dhpblvsm2tp875",
"topic": "complaint",
"priority": "high",
"summary": "Food arrived cold and the service was slow at the Salmiya branch last night",
"customer_message": "اكلنا عندكم امس والخدمة كانت سيئة جدا والاكل بارد",
"language": "ar",
"needs_human": true,
"agent_reply": null,
"confidence": 0.95,
"org": { "id": "cmspv1...", "name": "Dar Hamad" },
"channel": { "id": "cmspv1...", "type": "INSTAGRAM", "name": "Dar Hamad Instagram DMs" },
"contact": { "id": "cmsr9...", "name": "Sadia Faisal", "phone": null, "ig_username": "sadia.faisal" },
"conversation_id": "cmsr9unnl00dhpblvsm2tp875",
"occurred_at": "2026-08-13T17:29:52.930Z",
"deep_link": "https://app.badala.app/inbox?c=cmsr9unnl00dhpblvsm2tp875"
}
}| Field | What it is |
|---|---|
report_id | Idempotency key, stable across retries and replays. Key your cards on it and upsert. |
topic | What the customer wants. See the list below. |
priority | low, normal, or high. High means anger, a complaint, money, safety, or something time-critical. |
summary | One sentence, always in English, written for someone who does not read the customer's language. Safe as a card title. |
customer_message | The customer's own words, verbatim, in their language. |
needs_human | true when the agent escalated. Worth surfacing loudest. |
agent_reply | What the agent sent, when it answered automatically. Null when it escalated or the reply is still a draft. |
contact | phone or ig_username is populated depending on the channel; the other is null. |
deep_link | Opens that exact thread in Badala for anyone who wants the full history. |
Topics
complaint · reservation · order · delivery · feature_request · pricing · partnership · careers · question · praise · other
Anything not worth acting on — a greeting, a thank-you, small talk — is classified none and no event is sent, so your board stays quiet. Treat the list as open: accept an unknown topic into a default column rather than rejecting the delivery, and a new one will never break your integration.
Verifying the signature
HMAC-SHA256 over timestamp + "." + rawBody, keyed with your signing secret, compared against the hex in X-Badala-Signature. Use the raw body, before any JSON parsing, or the bytes will not match. Reject anything older than a few minutes to stop replays.
const crypto = require('crypto');
function verify(rawBody, headers, secret) {
const ts = headers['x-badala-timestamp'];
const sent = String(headers['x-badala-signature'] || '').replace('sha256=', '');
const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
const a = Buffer.from(sent);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Delivery
- Fired the moment the decision lands. Badala never waits for your response before replying to the customer.
- Any 2xx is success. Anything else retries at 30s, 2m, 10m, 1h, 6h, then the delivery is marked dead and waits for a manual retry in the webhook screen.
- Deliveries are not ordered. Two customers writing at once arrive in any order, so read
occurred_atrather than trusting arrival sequence. - Answer quickly, under a few seconds, and do your own work asynchronously.
A board that works
| Topic | Lane | Why |
|---|---|---|
complaint | Needs attention | Always, and it usually arrives as high priority. |
reservation, order, delivery | Operations | Time-critical, someone acts today. |
feature_request, praise | Feedback | Read weekly, not hourly. |
pricing, partnership, careers | Business | Routed to a person, not a team. |
question, other | Triage | The agent probably answered it. Skim. |
Testing before your side exists
Point the webhook at any request bin and send a real message to the channel. To see the classification without involving a customer, use the playground under Settings → AI Agent: what it returns is exactly what the event carries.