Messaging
Receiving messages
Messages that people send to your channels arrive as message.received webhook events and are stored as inbound messages you can list and reply to.
How inbound messages reach you#
When someone writes to one of your connected channels, OmniMessage stores the message and sends a message.received event to every webhook endpoint subscribed to it. There is nothing to configure per channel: connecting the channel is enough.
- Create a webhook endpoint subscribed to
message.received(or to*). - Verify the signature of each request, then read the message from
data.object. - Answer with a 2xx status within 10 seconds and process the message asynchronously.
Inbound messages are free: billing.source is none.
The event#
{
"id": "evt_6hJ9kL2zX5cV8bN1mQ4w",
"object": "event",
"type": "message.received",
"mode": "live",
"created_at": "2026-10-05T09:30:02.900Z",
"data": {
"object": {
"id": "msg_4fD8sA1gH6jK9lZ3xC5v",
"object": "message",
"mode": "live",
"channel_id": "ch_7Hq2mN5vB8cX1zL0pK3j",
"channel_type": "whatsapp",
"direction": "inbound",
"to": "+971800123456",
"from": "+971501234567",
"type": "text",
"content": {
"text": {
"body": "Where is my order?"
}
},
"status": "received",
"error": null,
"reference": null,
"metadata": {},
"billing": {
"source": "none",
"amount_micros": 0,
"package_grant_id": null,
"refunded": false
},
"sender": {
"name": "Layla Hassan",
"username": null
},
"contact_id": "ct_8Jk3mP6qR9sT2vW5xY1z",
"created_at": "2026-10-05T09:42:10.000Z",
"updated_at": "2026-10-05T09:42:10.000Z",
"sent_at": null,
"delivered_at": null,
"read_at": null,
"failed_at": null
}
}
}| Field | For an inbound message |
|---|---|
direction | Always inbound. |
status | Always received. Inbound messages have no further lifecycle. |
from | Identifier of the person who wrote, in the format of the channel: a phone number, a chat ID or a page-scoped user ID. Use it as to when you answer. |
to | Identifier of your channel. |
channel_id, channel_type | The channel the message arrived on. |
type, content | The content type and the content, keyed by type, as with outbound messages. |
reference, metadata | null and {}. They are set only on messages you send. |
Content of inbound messages#
Text arrives as "type": "text" with content.text.body. Media arrives as "type": "attachments" with one item in content.attachments. Other kinds of content, such as locations, contacts and taps on the buttons or list rows you sent, arrive under their own type in the format of the channel provider.
- Branch on
typeand ignore types your code does not handle. New types can appear as channels add features. - Download media from
attachments[].urlpromptly and store your own copy. Provider media URLs can expire. - A tap on a reply button or list row carries the
idyou assigned when you sent the message.
Reply#
Send a message on the same channel with to set to the from of the inbound message. Add reply_to with the inbound message ID to quote it, on channels that show quotes.
curl https://api.omnimessage.co/v1/messages \
-H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"channel": "ch_7Hq2mN5vB8cX1zL0pK3j",
"to": "+971501234567",
"type": "text",
"text": {
"body": "It left the warehouse this morning."
},
"reply_to": "msg_4fD8sA1gH6jK9lZ3xC5v"
}'List inbound messages#
Inbound messages are stored like outbound ones. Use the direction filter to page through them, for example to backfill after an outage of your webhook endpoint.
curl "https://api.omnimessage.co/v1/messages?direction=inbound&created_after=2026-10-05T00%3A00%3A00Z&limit=50" \
-H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx"Reliability#
- Events are retried for about 45 hours if your endpoint does not answer with 2xx. See retries.
- A retry repeats the same event ID. Deduplicate on
idso that a message is processed once. - Events for different messages can arrive out of order. Order a conversation by
created_at, not by arrival time. - Test mode does not produce inbound messages. To exercise your handler without a real channel, send a
webhook.testevent or replay a stored payload.