Skip to content

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#

message.received
{
  "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
    }
  }
}
FieldFor an inbound message
directionAlways inbound.
statusAlways received. Inbound messages have no further lifecycle.
fromIdentifier 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.
toIdentifier of your channel.
channel_id, channel_typeThe channel the message arrived on.
type, contentThe content type and the content, keyed by type, as with outbound messages.
reference, metadatanull 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 type and ignore types your code does not handle. New types can appear as channels add features.
  • Download media from attachments[].url promptly and store your own copy. Provider media URLs can expire.
  • A tap on a reply button or list row carries the id you 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 id so 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.test event or replay a stored payload.

    Loading