App integrations
An app integration connects your own product to one or more of your organization’s WhatsApp numbers. Your app:- is asked first about each customer message and can answer it itself (for example a button tap that marks attendance), leaving everything else to the AI agent and your inbox;
- receives signed webhooks for messages, delivery receipts and every message sent;
- sends text, reply buttons, lists, link buttons and approved templates through the app API;
- links customers to its own user ids, and opts them out of its messages;
- reads usage per day, number and price to reconcile its WhatsApp bill.
App integrations are part of the enterprise/commercial edition and are available on the Pro
and Enterprise plans. Messages your app sends don’t count toward your AI message quota.
Connect an app
- Go to Settings → App integrations and click Connect an app.
- Give it a name, your webhook URL (public
https), the events you want, and the WhatsApp numbers it should drive. A number can be driven by one app at a time. - Copy the API key (
cmai_…) and the webhook signing secret (whsec_…). They are shown once. - Click Test to send a signed
pingto your webhook.
Receive webhooks
Every request to your webhook is aPOST with a JSON body and these headers:
Verify the signature
Eachv1 is HMAC-SHA256(secret, "<t>.<raw body>") in hex. Accept the request when any v1 matches,
and reject a t more than 5 minutes away from your clock. Always verify the raw body, before
parsing it.
Answer first
When a customer writes on one of your app’s numbers, and nobody on your team is handling that chat, ChatterMate first sends your webhook themessage.received event with "handled_by": null. Answer
within 3 seconds with a 2xx and
{"handled": false}, another status, a timeout) lets the AI or your team answer as usual.
Reply to the customer yourself through the app API, not in the webhook response.
If your webhook keeps failing, ChatterMate stops asking it first for a minute at a time so customers
aren’t kept waiting; its events still queue.
Events
Every event hasevent_id, type, occurred_at, channel_account_id (the WhatsApp number),
session_id and customer — {id, wa_phone, name, external_user_id, opted_out}.
message.received—handled_byisapp,aiorhuman(who answered), andnullon the answer-first call.interactive.typeisbutton_reply,list_replyortemplate_button;interactive.idis the id you gave the button or row. A message your app was asked about first isn’t sent again.message.status—sent,delivered,readorfailed, for any message sent on the number.message.outbound— every message sent to a customer;sourceisapp,ai,humanorsystem.
Delivery and retries
Answer a webhook with any 2xx. Otherwise the event is retried with exponential backoff (honouringRetry-After) for about a day, then marked failed; you can resend it from the delivery log. Events
can arrive more than once and out of order: dedupe on X-ChatterMate-Event-Id and order by
occurred_at. While your app is paused, its queued events wait and no new ones are created.
Inbound media (images, documents, voice notes) isn’t forwarded yet.
Send messages
channel_account_id), the customer — one of to (international
format) or external_user_id (once bound) — and one of text, interactive or
template.
{"message_id": "wamid.…", "session_id": "…", "customer_id": "…", "held_by_human": false}.
held_by_human: true means someone on your team is handling the chat: the message was sent, but the
customer’s reply goes to them, not your app.
- 24-hour window. Text and interactive messages need the customer to have written in the last 24
hours; otherwise you get
409 template_required— send an approved template instead. A template can start a conversation. - Limits. Up to 3 reply buttons (titles ≤ 20 characters), lists up to 10 rows (titles ≤ 24), a
1024-character body, ids up to 256 characters. A
kind: "cta_url"message sends one link button (url,display_text). - Idempotency. Send an
Idempotency-Key(up to 255 characters) and retry with the same key: a message that was sent is never sent twice — the first answer is replayed for 24 hours. A refused request sent nothing, so after fixing the cause you can retry it with the same key. The same key with a different body is422 idempotency_key_reused; one still in flight is409 request_in_progress. - Rate limit. 120 requests a minute per app (
429 rate_limited).
Errors
Errors answer{"detail": {"code": "…", "message": "…"}}. The codes are stable:
Link customers
Bind a customer to your own user id and you can send to it, and see it on every event ascustomer.external_user_id. A customer can be bound once they have messaged one of your app’s numbers.
Ids are 1–255 characters with no spaces at either end; numbers are taken as text. An opt-out belongs to
the customer’s WhatsApp number: unbinding or moving the id keeps it. It applies to your app’s messages —
the AI and your team can still answer the customer when they write.
Read usage
from and to are UTC dates, both included, at most 92 days. Add channel_account_id for one number.
Each row is one UTC day, number, sender (source) and price (pricing_category, pricing_type):
- Each message counts once, in its furthest state: read, delivered, failed, sent.
chargedis delivered messages WhatsApp prices as paid (pricing_type: "regular").- The day is WhatsApp’s own timestamp, so totals line up with Meta’s billing.
numbers[].recorded_sincesays since when each number has been connected: usage is recorded while it stays connected (also while your app is paused).- A
sourceofnullis a message ChatterMate didn’t send itself — sent from elsewhere on the number, or before your app was connected.