Guides

Send messages

Text, media, location and templates: formats, the 24-hour window, statuses, history and errors.

Updated November 12, 2026View as Markdown

POST /messages accepts a message and responds immediately (202, status queued). The actual delivery to Meta, any automatic retries and status tracking happen in the background.

Common fields#

FieldDescription
phoneNumberIdThe sending number: the id returned by the account connection.
toThe recipient, in international format with a "+": +237600000000. Spaces, hyphens and parentheses are ignored.
typetext, template, image, video, audio, document or location.

A template (at any time)#

A template approved by Meta can be sent at any time, even without a prior exchange. It is the only way to start a conversation. See Templates.

bash
curl -X POST "$INBOX_API/messages" \
  -H "Authorization: Bearer $INBOX_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rdv-2026-11-14-client-42" \
  -d '{
    "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
    "to": "+237600000000",
    "type": "template",
    "template": {
      "name": "appointment_reminder",
      "language": "en",
      "components": [
        {
          "type": "body",
          "parameters": [
            {
              "type": "text",
              "text": "Awa"
            },
            {
              "type": "text",
              "text": "November 14 at 10 am"
            }
          ]
        }
      ]
    }
  }'
Response · 202 Accepted
{
  "id": "d5ad1e3e-6ab9-4f7f-8e4e-b17104a7bd00",
  "direction": "outbound",
  "status": "queued",
  "type": "template",
  "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
  "to": "237600000000",
  "waMessageId": null,
  "content": {
    "type": "template",
    "template": {
      "name": "appointment_reminder",
      "language": "en",
      "components": [
        {
          "type": "body",
          "parameters": [
            {
              "type": "text",
              "text": "Awa"
            },
            {
              "type": "text",
              "text": "November 14 at 10 am"
            }
          ]
        }
      ]
    }
  },
  "error": null,
  "createdAt": "2026-11-12T08:33:45.855Z",
  "sentAt": null,
  "deliveredAt": null,
  "readAt": null
}

A free-form message: the 24-hour window#

A free-form message is only accepted if the recipient has written to you within the last 24 hours on this number. Otherwise:

Real error · HTTP 422
{
  "error": {
    "code": "outside_service_window",
    "message": "This contact has not written to you in the last 24 hours: only an approved template can be sent to them",
    "requestId": "req_4fdba006-98d4-4164-893d-98389fb54c62"
  }
}

Within the window, all message types are allowed.

Text#

bash
curl -X POST "$INBOX_API/messages" \
  -H "Authorization: Bearer $INBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
    "to": "+237611111111",
    "type": "text",
    "text": {
      "body": "Hello Awa, we have received your request."
    }
  }'
Response · 202 Accepted
{
  "id": "2dbd95d9-4091-4cbf-8f75-c2dea9f39372",
  "direction": "outbound",
  "status": "queued",
  "type": "text",
  "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
  "to": "237611111111",
  "waMessageId": null,
  "content": {
    "type": "text",
    "text": {
      "body": "Hello Awa, we have received your request.",
      "previewUrl": false
    }
  },
  "error": null,
  "createdAt": "2026-11-12T08:33:46.048Z",
  "sentAt": null,
  "deliveredAt": null,
  "readAt": null
}

The text body is 1 to 4096 characters long.

Image#

bash
curl -X POST "$INBOX_API/messages" \
  -H "Authorization: Bearer $INBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
    "to": "+237611111111",
    "type": "image",
    "image": {
      "link": "https://example.com/plan-d-acces.jpg",
      "caption": "Directions to the clinic"
    }
  }'
Response · 202 Accepted
{
  "id": "097f6e1e-64f0-4bdf-8a00-672f6d7b168c",
  "direction": "outbound",
  "status": "queued",
  "type": "image",
  "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
  "to": "237611111111",
  "waMessageId": null,
  "content": {
    "type": "image",
    "media": {
      "link": "https://example.com/plan-d-acces.jpg",
      "caption": "Directions to the clinic"
    }
  },
  "error": null,
  "createdAt": "2026-11-12T08:33:46.070Z",
  "sentAt": null,
  "deliveredAt": null,
  "readAt": null
}

For media (image, video, audio, document), provide either link (a public https:// URL) or id (media already hosted by Meta), never both. caption doesn't exist for audio. Uploading and downloading media through the API is not yet available.

Document#

bash
curl -X POST "$INBOX_API/messages" \
  -H "Authorization: Bearer $INBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
    "to": "+237611111111",
    "type": "document",
    "document": {
      "link": "https://example.com/ordonnance.pdf",
      "filename": "ordonnance.pdf",
      "caption": "Your prescription"
    }
  }'
Response · 202 Accepted
{
  "id": "9e09a76b-537b-4d5f-8454-4305ba8748a6",
  "direction": "outbound",
  "status": "queued",
  "type": "document",
  "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
  "to": "237611111111",
  "waMessageId": null,
  "content": {
    "type": "document",
    "media": {
      "link": "https://example.com/ordonnance.pdf",
      "caption": "Your prescription",
      "filename": "ordonnance.pdf"
    }
  },
  "error": null,
  "createdAt": "2026-11-12T08:33:46.098Z",
  "sentAt": null,
  "deliveredAt": null,
  "readAt": null
}

Location#

bash
curl -X POST "$INBOX_API/messages" \
  -H "Authorization: Bearer $INBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
    "to": "+237611111111",
    "type": "location",
    "location": {
      "latitude": 4.0511,
      "longitude": 9.7679,
      "name": "Cabinet Dentaire Zokem",
      "address": "Akwa, Douala"
    }
  }'
Response · 202 Accepted
{
  "id": "a9691d7a-ee51-4da9-87b2-24cce79a9313",
  "direction": "outbound",
  "status": "queued",
  "type": "location",
  "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
  "to": "237611111111",
  "waMessageId": null,
  "content": {
    "type": "location",
    "location": {
      "latitude": 4.0511,
      "longitude": 9.7679,
      "name": "Cabinet Dentaire Zokem",
      "address": "Akwa, Douala"
    }
  },
  "error": null,
  "createdAt": "2026-11-12T08:33:46.131Z",
  "sentAt": null,
  "deliveredAt": null,
  "readAt": null
}

Track a message#

bash
curl "$INBOX_API/messages/ID" \
  -H "Authorization: Bearer $INBOX_KEY"
Response · 200 OK
{
  "id": "d5ad1e3e-6ab9-4f7f-8e4e-b17104a7bd00",
  "direction": "outbound",
  "status": "sent",
  "type": "template",
  "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
  "to": "237600000000",
  "waMessageId": "wamid.HBgMXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "content": {
    "type": "template",
    "template": {
      "name": "appointment_reminder",
      "language": "en",
      "components": [
        {
          "type": "body",
          "parameters": [
            {
              "text": "Awa",
              "type": "text"
            },
            {
              "text": "November 14 at 10 am",
              "type": "text"
            }
          ]
        }
      ]
    }
  },
  "error": null,
  "createdAt": "2026-11-12T08:33:45.855Z",
  "sentAt": "2026-11-12T08:33:45.891Z",
  "deliveredAt": null,
  "readAt": null
}

error is null as long as everything is fine; on failure, it contains Meta's code and message. To be notified without polling the API, use webhooks (message.sent, message.delivered, message.read, message.failed).

History#

bash
curl "$INBOX_API/messages" \
  -H "Authorization: Bearer $INBOX_KEY"
Response · 200 OK
{
  "data": [
    {
      "id": "a9691d7a-ee51-4da9-87b2-24cce79a9313",
      "direction": "outbound",
      "status": "sent",
      "type": "location",
      "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
      "to": "237611111111",
      "waMessageId": "wamid.HBgMXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "content": {
        "type": "location",
        "location": {
          "name": "Cabinet Dentaire Zokem",
          "address": "Akwa, Douala",
          "latitude": 4.0511,
          "longitude": 9.7679
        }
      },
      "error": null,
      "createdAt": "2026-11-12T08:33:46.131Z",
      "sentAt": "2026-11-12T08:33:46.251Z",
      "deliveredAt": null,
      "readAt": null
    },
    {
      "id": "9e09a76b-537b-4d5f-8454-4305ba8748a6",
      "direction": "outbound",
      "status": "sent",
      "type": "document",
      "phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
      "to": "237611111111",
      "waMessageId": "wamid.HBgMXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "content": {
        "type": "document",
        "media": {
          "link": "https://example.com/ordonnance.pdf",
          "caption": "Your prescription",
          "filename": "ordonnance.pdf"
        }
      },
      "error": null,
      "createdAt": "2026-11-12T08:33:46.098Z",
      "sentAt": "2026-11-12T08:33:46.221Z",
      "deliveredAt": null,
      "readAt": null
    }
  ],
  "nextCursor": "MjAyNi0xMS0xMlQwODozMzo0Ni4wOThafGMwYmU4NDdlLWQyNDQtNGUyOS04N2M1LTAyNTE3ODk1Yzg5OQ"
}

History is sorted from newest to oldest and cursor-paginated: as long as nextCursor is not null, pass it in ?cursor= to get the next page. Filters: phoneNumberId, direction (inbound, outbound), status, limit (1 to 100).

Sending errors#

CodeHTTPCause
invalid_message422Malformed number, empty text, ambiguous media (id and link together), non-https link
outside_service_window422Free-form message outside the 24-hour window
template_not_approved422The template exists but is not (or no longer) approved
idempotency_conflict422Same Idempotency-Key for a different request
quota_exceeded402Your plan's monthly quota has been reached (production)
not_found404Unknown number, or one from another environment
Real error · HTTP 422
{
  "error": {
    "code": "invalid_message",
    "message": "Invalid recipient number: use the international format with a \"+\", e.g. +237600000000",
    "requestId": "req_72871759-6841-4dcf-8065-3425c4a44633"
  }
}

Meta's rejections during delivery (template not found, recipient unreachable…) don't show up in the 202 response: the message moves to failed, with Meta's reason, and you receive message.failed.