Sends a plain text message to a contact using their default messaging channel (WhatsApp or RCS).
Use Case: Use this endpoint to integrate your own AI, automation, or help desk system with Wax. When you receive an inbound message webhook, process it with your system, then use this endpoint to send a reply back to the customer.
Technical Details:
- The message is sent via the contact's
default_channel(WhatsApp or RCS), which is automatically set based on the channel they last messaged from - If the contact has no default channel, WhatsApp is used as fallback
- Only plain text messages are supported (no images, cards, or interactive elements)
- The conversation remains in the inbox as unresolved for human agents to monitor
- The
contact_idis included in the inbound message webhook payload for easy reference - The response returns
message_id, the identifier of the message WAX created for your reply. It uses the samechannel:idformat as the webhook payloads, so you can match it against messages you receive back (see Conversation mirror webhook below)
Inbound message webhook
When a contact sends a message that is not handled by one of your automations, WAX POSTs it to the webhook URL configured in Settings → Organization → External AI. Each request carries an X-Signature header (HMAC-SHA256 of the raw JSON body, signed with your signing key) plus any custom headers you configured.
{
"message_id": "wamid.HBgLM...",
"organization_id": 42,
"contact_id": 12345,
"message": {
"id": "whatsapp:48213",
"external_message_id": "wamid.HBgLM...",
"channel": "whatsapp",
"content": "Where is my order?",
"timestamp": "2026-07-08T14:03:21.532Z",
"media_url": "https://cdn.example.com/img.jpg",
"media_type": "image/jpeg"
},
"contact": {
"name": "John Doe",
"phone": "+33612345678",
"email": "[email protected]",
"tags": ["VIP"],
"custom_attributes": { "loyalty_tier": "gold" }
},
"history": [
{
"id": "whatsapp:48201",
"external_message_id": "wamid.HBgKN...",
"role": "user",
"content": "Hi!",
"timestamp": "2026-07-08T13:58:02.101Z"
},
{
"id": "whatsapp:48204",
"external_message_id": "wamid.HBgQP...",
"role": "assistant",
"content": "Hello John, how can we help?",
"timestamp": "2026-07-08T13:58:40.870Z"
}
],
"shopify": {
"recent_orders": [
{ "order_number": "#1001", "status": "shipped", "items": ["Blue T-Shirt"], "date": "2026-07-06T09:12:00Z" }
],
"active_checkouts": [
{ "items": ["Red Sneakers"], "created_at": "2026-07-07T18:44:00Z" }
]
}
}Message identity & deduplication:
message.idandhistory[].idare stable, globally unique message identifiers prefixed with the channel (whatsapp:123,rcs:45,sms:67). The same message keeps the sameidacross deliveries, so you can upsert messages and store them deduplicated.external_message_idis the provider-side identifier (e.g. the WhatsAppwamid); it can benull.timestampis ISO8601 with millisecond precision;historyis sorted chronologically ascending.- Deliveries are at-least-once (failed requests are retried), and
historyoverlaps across deliveries by design — always deduplicate onid.
Payload notes:
message.media_url/message.media_typeare only present for image messages.historycontains up to 20 previous text messages (roleisuserfor the contact,assistantfor messages sent by WAX or your integration).shopifycontains the contact's 5 most recent orders (status:ordered,shipped,deliveredorcancelled) and up to 3 active checkouts. It isnullunless the organization has an active Shopify connection and the "Include Shopify data" option is enabled on the webhook.
Conversation mirror webhook
Separately from the inbound webhook above, a URL can be configured in Settings → Organization → External helpdesk. Once a conversation needs attention, WAX POSTs every message on it to that URL, one request per message, in both directions: free-text, quick replies, media, the automated messages your flows send, and replies sent through this endpoint. It is a one-way feed, WAX expects no reply to these requests. Signing is identical (X-Signature HMAC-SHA256 of the raw body).
Every call carries a history array (up to the last 20 messages, oldest first, same entry shape as message) so an agent picking the conversation up has the context that was never forwarded while your automations were handling it, and so a dropped delivery is repaired by the next call. event is conversation.opened on the first call (create the ticket) and message.created afterwards (append). Deduplicate on message.id.
{
"event": "message.created",
"organization_id": 42,
"contact_id": 12345,
"timeline": { "id": 987, "status": "unresolved" },
"message": {
"id": "whatsapp:48213",
"external_message_id": "wamid.HBgLM...",
"channel": "whatsapp",
"direction": "inbound",
"kind": "quick_reply",
"origin": "customer",
"content": "Track my order",
"timestamp": "2026-07-08T14:03:21.532Z"
},
"contact": {
"name": "John Doe",
"phone": "+33612345678",
"email": "[email protected]",
"tags": ["VIP"]
}
}directionisinbound(from the contact) oroutbound(sent by WAX).originsays who produced the message:customer,human(a teammate in the WAX inbox),ai_agent(the WAX AI agent),flow(an automation) orapi(sent through this endpoint).media_urlis present forimage,video,audioanddocumentmessages. It is a permanent link WAX re-hosted, so you can fetch it directly with no authentication. Usekindto know what kind of asset to expect. When a media message has a caption,contentholds it; otherwisecontentis a short label such as📷 Photo.- Deduplicating your own replies: a message you send via
POST /v1/conversation_repliesis mirrored back to you withdirection: "outbound"andorigin: "api". Itsmessage.idequals themessage_idreturned by that call, so store the returned id and drop the echo rather than duplicating it in your thread. - Reactions, stickers, locations and shared contacts are not mirrored.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||