Skip to content

Messages

View as Markdown

Send messages and read their status.

Everything you send goes through one endpoint, POST /messages. You choose a type, put the content under a key with the same name, and follow what happens through message.* events.

How it works: Sending messagesThe 24-hour window · Text · Media · Buttons, lists and links · Location, contact cards and reactions · Replies, read receipts and typing · What happens after you send · Instagram and Messenger · Which number sends

The 24-hour window#

WhatsApp decides what you may send by one rule:

  • Within 24 hours of the customer's last message, the conversation is open: send any type, free-form.
  • Otherwise, only an approved template is delivered. Anything else is refused with window_closed before it reaches WhatsApp, so you are never charged for a message that could not arrive.

A template that the customer answers reopens the window. GET /conversations/{id} tells you when it closes (reply_window_expires_at).

Text#

JSON
{ "to": "+966501234567", "type": "text", "text": { "body": "أهلًا سارة! طلبك جاهز 🎉", "preview_url": true } }

Up to 4,096 characters. preview_url shows a preview of the first link.

Media#

Images, video, audio, documents and stickers come from a public https:// link WhatsApp downloads, or from a file you uploaded once with POST /media:

JSON
{ "to": "+966501234567", "type": "document", "document": { "media_id": "med_381", "filename": "invoice-10482.pdf", "caption": "فاتورتك" } }
TypeFormatsLargestCaption
imageJPEG, PNG5 MByes
videoMP4, 3GPP16 MByes
audioAAC, MP4, MPEG, AMR, OGG/Opus16 MBno
documentany file100 MByes, plus filename
stickerWebP 512×512100 KB (500 KB animated)no

An upload is checked against these limits when you upload it, so a file that WhatsApp would reject never gets as far as a send.

Buttons, lists and links#

interactive messages are answered with a tap. The customer's choice comes back as a message.received event of type interactive, carrying the id you set: route on it.

JSON
{
  "to": "+966501234567",
  "type": "interactive",
  "interactive": {
    "type": "button",
    "body": "هل تؤكد موعدك غدًا الساعة 4 عصرًا؟",
    "buttons": [
      { "id": "confirm_10482", "title": "تأكيد" },
      { "id": "reschedule_10482", "title": "تغيير الموعد" }
    ]
  }
}
interactive.typeWhat the customer seesLimits
buttonUp to 3 reply buttonstitle 20 characters, id 256
listA button that opens up to 10 options in sections10 rows in total; row title 24, description 72
cta_urlOne button that opens a linklabel 20, https:// only
location_requestA button to share their locationbody only

Every type takes a body, and all but location_request an optional footer and header (text, or for cta_url an image, video or document link).

Location, contact cards and reactions#

  • location: latitude, longitude, and optionally name and address.
  • contacts: a list of cards; each needs name.formatted_name, and a phone with wa_id shows a "Message" button.
  • reaction: {"message_id": "imsg_…", "emoji": "👍"} reacts to a message; an empty emoji removes it. Reactions are free and do not count as messages.

Replies, read receipts and typing#

  • Send reply_to (a msg_… / imsg_… id or a WhatsApp id) to quote the customer's message in your reply.
  • POST /messages/{id}/read gives the customer blue ticks. Add "typing_indicator": true to show "typing…" while you prepare the answer (up to 25 seconds, or until you reply).

What happens after you send#

The response comes back as soon as WhatsApp accepts the message, with status: accepted. Then events follow:

EventMeaning
message.sentWhatsApp sent it
message.deliveredIt reached the customer's phone
message.readThey opened it (if they share read receipts)
message.failedIt could not be delivered: error.code is WhatsApp's reason

Put your own id in reference (an order number, a ticket id): it comes back in the response and in every event, so you never need a lookup table.

Instagram and Messenger#

The same endpoint replies on Instagram and Messenger: set channel and send to the customer id from their message's from. These channels take text, image, video, audio and document, only within 24 hours of the customer's last message, and they have no templates: once the window closes, you can write again only after the customer does.

JSON
{ "channel": "instagram", "to": "17841400000000000", "type": "text", "text": { "body": "شكرًا لتواصلك! المقاس متوفر ✨" } }

Which number sends#

Leave from out and the message goes from the number the customer last wrote to, or else from your default number. To choose, send from as a number id from GET /numbers, which also tells you whether each number can send right now.

Endpoints

List messages sent through the API

gethttps://api.saei.automize.sa/v1/messages
  • Scope: messages:read
  • Works in test mode

Newest first. Live keys list messages sent with the API; test keys list test-mode messages (sent and simulated inbound).

Query parameters

  • limitintegeroptional

    How many objects to return, 1 to 100.

  • cursorstringoptional

    The next_cursor from the previous page.

  • referencestringoptional

    Only the messages you sent with this reference.

  • tostringoptional

    Only messages to this number (E.164).

Response fields200
  • object"list"required
  • dataarray<Message>required
    Child fields (28)
    • idstringrequired

      Unique id. Opaque: store it as a string of up to 64 characters.

    • object"message"required
    • livemodebooleanrequired
    • directionstringrequired

      outbound for messages you send, inbound for customer messages.

      One ofoutboundinbound

    • channelstringrequired

      One ofwhatsappinstagrammessenger

    • fromstring | nulloptional

      Sender, in E.164 for WhatsApp.

    • tostring | nulloptional

      Recipient, in E.164 for WhatsApp.

    • typestringrequired

      What kind of content the message carries.

    • textobjectoptional
      Child fields (1)
      • bodystringoptional
    • templateobjectoptional
      Child fields (4)
      • namestringoptional
      • languagestringoptional
      • variablesobjectoptional
      • bodystringoptional
    • imageobjectoptional

      Media messages carry link (or media_id), caption, filename and mime_type when known.

    • videoobjectoptional
    • audioobjectoptional
    • documentobjectoptional
    • stickerobjectoptional
    • locationobjectoptional
    • contactsarrayoptional
    • interactiveobjectoptional

      What you sent (type, body, buttons…), or for a customer's tap, reply: {id, title}.

    • reactionobjectoptional
    • statusstringrequired

      accepted → sent → delivered → read, or failed. Customer messages are received. Status changes arrive as webhook events.

      One ofacceptedsentdeliveredreadfailedreceived

    • errorobject | nulloptional

      Why delivery failed, when status is failed.

      Child fields (3)
      • codestring | nulloptional
      • titlestring | nulloptional
      • messagestring | nulloptional
    • referencestring | nulloptional

      Your own reference, echoed back from the send request and in every event.

    • conversation_idstring | nulloptional
    • provider_message_idstring | nulloptional

      WhatsApp's own message id (wamid).

    • contactobject | nulloptional
      Child fields (1)
      • namestring | nulloptional
    • contextobject | nulloptional

      For a reply, the message being replied to.

      Child fields (1)
      • provider_message_idstringoptional
    • created_atstring | nullrequired
    • updated_atstring | nulloptional
  • has_morebooleanrequired
  • next_cursorstring | nullrequired
Errors this can return2
Try it
curl -X GET https://api.saei.automize.sa/v1/messages \
  -H "Authorization: Bearer $API_KEY"
Response

A page of messages.

{
  "object": "list",
  "data": [
    {
      "id": "msg_48213",
      "object": "message",
      "livemode": false,
      "direction": "outbound",
      "channel": "whatsapp",
      "from": "+966112345678",
      "to": "+966501234567",
      "type": "text",
      "text": {
        "body": "string"
      },
      "template": {
        "name": "string",
        "language": "string",
        "variables": {},
        "body": "string"
      },
      "image": {},
      "video": {},
      "audio": {},
      "document": {},
      "sticker": {},
      "location": {},
      "contacts": [],
      "interactive": {},
      "reaction": {},
      "status": "accepted",
      "error": {
        "code": "string",
        "title": "string",
        "message": "string"
      },
      "reference": "string",
      "conversation_id": "string",
      "provider_message_id": "string",
      "contact": {
        "name": "string"
      },
      "context": {
        "provider_message_id": "string"
      },
      "created_at": "2026-10-01T09:30:00Z",
      "updated_at": "2026-10-01T09:30:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": "string"
}

Send a message

posthttps://api.saei.automize.sa/v1/messages
  • Scope: messages:send
  • Simulated in test mode
  • Accepts Idempotency-Key

One endpoint for every kind of message: set type and put the content under a key of the same name: text, template, image, video, audio, document, sticker, location, contacts, interactive (reply buttons, lists, link buttons, location requests) or reaction.

  • Inside 24 hours of the customer's last message you can send any type.
  • Outside that window (or to start a conversation) only an approved template is delivered. Anything else returns window_closed.
  • Instagram and Messenger (channel) take replies only: text and media, inside the 24 hours.

The response comes back as soon as the channel accepts the message (status: accepted). Delivery and read updates arrive as message.* webhook events. With a test key, the message is simulated and never reaches WhatsApp.

Headers

  • Idempotency-Keystringoptional

    Any unique string (a UUID is ideal). Retrying with the same key and body returns the first response instead of acting twice. Remembered for 24 hours.

Request body

Message type

On the customer's phoneApproximate look
Your businessBusiness account

Then these webhook events arrive

  1. Sentmessage.sent
  2. Deliveredmessage.delivered
  3. Readmessage.read

A plain text message. Only inside the 24-hour window after the customer's last message.

  • channelstringoptional

    Where to send. Instagram and Messenger take replies only (text, image, video, audio, document) within 24 hours of the customer's last message; to is then the customer id from their message's from.

    One ofwhatsappinstagrammessenger

  • tostringrequired

    The customer's phone number in E.164, e.g. +966501234567. On Instagram / Messenger, the customer id from their message.

  • fromstringoptional

    Which of your numbers sends it: a number id (num_…) or the number in E.164. Default: the number the customer last wrote to, else your default number.

  • reply_tostringoptional

    Send as a reply to this message (a msg_… / imsg_… id or a WhatsApp wamid).

  • referencestringoptional

    Your own id for this message (an order number, a ticket). Returned in the response and in every webhook event about it.

  • type"text"required
  • textobjectrequired
    Child fields (2)
    • bodystringrequired

      The message text. Up to 4096 characters; Arabic and emoji are fine.

    • preview_urlbooleanoptional

      Show a preview of the first link in the text.

Response fields202
  • idstringrequired

    Unique id. Opaque: store it as a string of up to 64 characters.

  • object"message"required
  • livemodebooleanrequired
  • directionstringrequired

    outbound for messages you send, inbound for customer messages.

    One ofoutboundinbound

  • channelstringrequired

    One ofwhatsappinstagrammessenger

  • fromstring | nulloptional

    Sender, in E.164 for WhatsApp.

  • tostring | nulloptional

    Recipient, in E.164 for WhatsApp.

  • typestringrequired

    What kind of content the message carries.

  • textobjectoptional
    Child fields (1)
    • bodystringoptional
  • templateobjectoptional
    Child fields (4)
    • namestringoptional
    • languagestringoptional
    • variablesobjectoptional
    • bodystringoptional
  • imageobjectoptional

    Media messages carry link (or media_id), caption, filename and mime_type when known.

  • videoobjectoptional
  • audioobjectoptional
  • documentobjectoptional
  • stickerobjectoptional
  • locationobjectoptional
  • contactsarrayoptional
  • interactiveobjectoptional

    What you sent (type, body, buttons…), or for a customer's tap, reply: {id, title}.

  • reactionobjectoptional
  • statusstringrequired

    accepted → sent → delivered → read, or failed. Customer messages are received. Status changes arrive as webhook events.

    One ofacceptedsentdeliveredreadfailedreceived

  • errorobject | nulloptional

    Why delivery failed, when status is failed.

    Child fields (3)
    • codestring | nulloptional
    • titlestring | nulloptional
    • messagestring | nulloptional
  • referencestring | nulloptional

    Your own reference, echoed back from the send request and in every event.

  • conversation_idstring | nulloptional
  • provider_message_idstring | nulloptional

    WhatsApp's own message id (wamid).

  • contactobject | nulloptional
    Child fields (1)
    • namestring | nulloptional
  • contextobject | nulloptional

    For a reply, the message being replied to.

    Child fields (1)
    • provider_message_idstringoptional
  • created_atstring | nullrequired
  • updated_atstring | nulloptional
Errors this can return21
Try it
Example
curl -X POST https://api.saei.automize.sa/v1/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "+966501234567",
  "type": "text",
  "text": {
    "body": "أهلًا سارة! طلبك جاهز للاستلام 🎉"
  },
  "reference": "order-10482"
}'
Response

Accepted by WhatsApp (or by the simulator, in test mode).

{
  "id": "msg_48213",
  "object": "message",
  "livemode": true,
  "direction": "outbound",
  "channel": "whatsapp",
  "from": "+966112345678",
  "to": "+966501234567",
  "type": "text",
  "text": {
    "body": "أهلًا سارة! طلبك جاهز للاستلام 🎉"
  },
  "status": "accepted",
  "error": null,
  "reference": "order-10482",
  "conversation_id": "conv_311",
  "provider_message_id": "wamid.HBgMOTY2NTAxMjM0NTY3FQIAERgSMEM4",
  "created_at": "2026-10-01T09:30:00Z",
  "updated_at": "2026-10-01T09:30:00Z"
}

Get a message

gethttps://api.saei.automize.sa/v1/messages/{id}
  • Scope: messages:read
  • Works in test mode

A message and its current status. Works for messages you sent (msg_…) and customer messages from webhook events (imsg_…).

Path parameters

  • idstringrequired

    The object's id.

Response fields200
  • idstringrequired

    Unique id. Opaque: store it as a string of up to 64 characters.

  • object"message"required
  • livemodebooleanrequired
  • directionstringrequired

    outbound for messages you send, inbound for customer messages.

    One ofoutboundinbound

  • channelstringrequired

    One ofwhatsappinstagrammessenger

  • fromstring | nulloptional

    Sender, in E.164 for WhatsApp.

  • tostring | nulloptional

    Recipient, in E.164 for WhatsApp.

  • typestringrequired

    What kind of content the message carries.

  • textobjectoptional
    Child fields (1)
    • bodystringoptional
  • templateobjectoptional
    Child fields (4)
    • namestringoptional
    • languagestringoptional
    • variablesobjectoptional
    • bodystringoptional
  • imageobjectoptional

    Media messages carry link (or media_id), caption, filename and mime_type when known.

  • videoobjectoptional
  • audioobjectoptional
  • documentobjectoptional
  • stickerobjectoptional
  • locationobjectoptional
  • contactsarrayoptional
  • interactiveobjectoptional

    What you sent (type, body, buttons…), or for a customer's tap, reply: {id, title}.

  • reactionobjectoptional
  • statusstringrequired

    accepted → sent → delivered → read, or failed. Customer messages are received. Status changes arrive as webhook events.

    One ofacceptedsentdeliveredreadfailedreceived

  • errorobject | nulloptional

    Why delivery failed, when status is failed.

    Child fields (3)
    • codestring | nulloptional
    • titlestring | nulloptional
    • messagestring | nulloptional
  • referencestring | nulloptional

    Your own reference, echoed back from the send request and in every event.

  • conversation_idstring | nulloptional
  • provider_message_idstring | nulloptional

    WhatsApp's own message id (wamid).

  • contactobject | nulloptional
    Child fields (1)
    • namestring | nulloptional
  • contextobject | nulloptional

    For a reply, the message being replied to.

    Child fields (1)
    • provider_message_idstringoptional
  • created_atstring | nullrequired
  • updated_atstring | nulloptional
Errors this can return2
Try it
curl -X GET https://api.saei.automize.sa/v1/messages/msg_48213 \
  -H "Authorization: Bearer $API_KEY"
Response

The message.

{
  "id": "msg_48213",
  "object": "message",
  "livemode": false,
  "direction": "outbound",
  "channel": "whatsapp",
  "from": "+966112345678",
  "to": "+966501234567",
  "type": "text",
  "text": {
    "body": "string"
  },
  "template": {
    "name": "string",
    "language": "string",
    "variables": {},
    "body": "string"
  },
  "image": {},
  "video": {},
  "audio": {},
  "document": {},
  "sticker": {},
  "location": {},
  "contacts": [],
  "interactive": {},
  "reaction": {},
  "status": "accepted",
  "error": {
    "code": "string",
    "title": "string",
    "message": "string"
  },
  "reference": "string",
  "conversation_id": "string",
  "provider_message_id": "string",
  "contact": {
    "name": "string"
  },
  "context": {
    "provider_message_id": "string"
  },
  "created_at": "2026-10-01T09:30:00Z",
  "updated_at": "2026-10-01T09:30:00Z"
}

Mark a customer message as read

posthttps://api.saei.automize.sa/v1/messages/{id}/read
  • Scope: messages:send
  • Simulated in test mode

Shows the customer blue ticks on their message (and on everything they sent before it). Add typing_indicator: true to also show "typing…" while you prepare the answer: it lasts up to 25 seconds, or until you reply.

Works on WhatsApp messages you received (imsg_…, from message.received events).

Path parameters

  • idstringrequired

    The object's id.

Request body

  • typing_indicatorbooleanoptional

    Also show "typing…" to the customer.

Response fields200
  • object"read_receipt"required
  • message_idstringrequired
  • livemodebooleanrequired
  • typing_indicatorbooleanoptional
Errors this can return4
Try it
curl -X POST https://api.saei.automize.sa/v1/messages/msg_48213/read \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "typing_indicator": true
}'
Response

The customer now sees the message as read.

{
  "object": "read_receipt",
  "message_id": "imsg_88213",
  "livemode": true,
  "typing_indicator": true
}