> ## Documentation Index
> Fetch the complete documentation index at: https://documents.xobito.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a text message

> Send a free-form text (or interactive) WhatsApp message.

Send a free-form text message, or an interactive message with buttons, to a phone number. If the number is not an existing contact, Xobito auto-creates a minimal contact.

<Note>
  WhatsApp requires an open 24-hour "customer care" window to send free-form text. If the recipient has not messaged your number in the last 24 hours, use [`POST /messages/template`](/api-reference/endpoints/send-template-message) instead.
</Note>

## Endpoint

```
POST /api/v1/{subdomain}/messages/send
```

Required ability: `messages.send`.

## Headers

| Header          | Value                        |
| --------------- | ---------------------------- |
| `Authorization` | `Bearer <your-64-hex-token>` |
| `Content-Type`  | `application/json`           |
| `Accept`        | `application/json`           |

## Path parameters

<ParamField path="subdomain" type="string" required={true}>
  Your workspace subdomain.
</ParamField>

## Body parameters

<ParamField body="phone_number" type="string" required={true}>
  Recipient's phone number in E.164 format.
</ParamField>

<ParamField body="message_body" type="string" required={true}>
  Message body. Max 4096 characters.
</ParamField>

<ParamField body="message_type" type="string" required={false}>
  One of `text`, `interactive`, `cta_url`. Defaults to `text`.
</ParamField>

<ParamField body="header" type="string" required={false}>
  Optional header (interactive / cta\_url messages). Max 60 characters.
</ParamField>

<ParamField body="footer" type="string" required={false}>
  Optional footer. Max 60 characters.
</ParamField>

<ParamField body="buttons" type="array" required={false}>
  Interactive button array. 1–3 items. Each button is `{ id: string (max 256), title: string (max 20) }`.
</ParamField>

<ParamField body="button_text" type="string" required={false}>
  For `cta_url` messages: the button label. Max 20 characters.
</ParamField>

<ParamField body="button_url" type="string" required={false}>
  For `cta_url` messages: the URL the button opens.
</ParamField>

<ParamField body="contact" type="object" required={false}>
  Optional contact payload. Used when auto-creating or updating the contact backing this message. Any of: `firstname`, `lastname`, `email`, `country`, `assigned_id`, `groups`.
</ParamField>

## Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dash.xobito.com/api/v1/acme/messages/send \
    -H "Authorization: Bearer <your_token>" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "phone_number": "+14155551234",
      "message_body": "Hi John, your order is on the way."
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://dash.xobito.com/api/v1/acme/messages/send",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <your_token>",
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({
        phone_number: "+14155551234",
        message_body: "Hi John, your order is on the way.",
      }),
    }
  );
  const body = await res.json();
  ```

  ```python Python theme={null}
  import requests

  r = requests.post(
      "https://dash.xobito.com/api/v1/acme/messages/send",
      headers={
          "Authorization": "Bearer <your_token>",
          "Accept": "application/json",
      },
      json={
          "phone_number": "+14155551234",
          "message_body": "Hi John, your order is on the way.",
      },
  )
  body = r.json()
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://dash.xobito.com/api/v1/acme/messages/send');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer <your_token>',
          'Content-Type: application/json',
          'Accept: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode([
          'phone_number' => '+14155551234',
          'message_body' => 'Hi John, your order is on the way.',
      ]),
  ]);
  $body = json_decode(curl_exec($ch), true);
  curl_close($ch);
  ```
</CodeGroup>

## Interactive reply buttons (1–3)

Send a message with tappable reply buttons. When the user taps a button, the `id` is sent back as an incoming message to your webhook.

```bash cURL theme={null}
curl -X POST https://dash.xobito.com/api/v1/acme/messages/send \
  -H "Authorization: Bearer <your_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155551234",
    "header": "Dropit Wallet",
    "message_body": "💰 Current Wallet Balance: *₹100*\n\nWhat would you like to do?",
    "footer": "Powered by Dropit",
    "message_type": "interactive",
    "buttons": [
      { "id": "view_details", "title": "View Details" },
      { "id": "redeem_now", "title": "Redeem Now" },
      { "id": "share_friend", "title": "Share" }
    ]
  }'
```

**Limits:**

* Max 3 buttons per message
* Button `title` max 20 chars
* Button `id` max 256 chars (returned verbatim on tap)

## CTA URL button (1 URL)

Send a message with a single button that opens a URL in the browser.

```bash cURL theme={null}
curl -X POST https://dash.xobito.com/api/v1/acme/messages/send \
  -H "Authorization: Bearer <your_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155551234",
    "message_body": "Thank you for choosing Dropit\n\nCurrent Wallet Balance: *₹100*\nTap below to explore.",
    "message_type": "cta_url",
    "button_text": "View Details",
    "button_url": "https://drop-it.co/get-app"
  }'
```

**Limits:**

* Only 1 URL button per message (Meta restriction)
* `button_text` max 20 chars
* `button_url` must be a valid HTTPS/HTTP URL

<Note>
  **Button type limits:** Meta allows only ONE action type per free-form message — either 1–3 reply buttons OR 1 CTA URL button. To mix button types (Reply + URL + Call), you need a pre-approved Meta template via [`POST /messages/template`](/api-reference/endpoints/send-template-message).
</Note>

## Example response

```json 200 OK theme={null}
{
  "status": "success",
  "message": "Message sent successfully",
  "data": {
    "message_id": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI5QUQ1N...",
    "contact_id": 451,
    "phone": "+14155551234",
    "message": "Hi John, your order is on the way.",
    "status": "sent",
    "sent_at": "2026-04-16T12:00:00.000000Z",
    "chat_id": 88,
    "contact_created": false
  }
}
```

See [Data Types → Message send response](/developers/data-types#message-send-response).

### Auto-created contacts

If `phone_number` does not match an existing contact, Xobito auto-creates one. The `firstname` defaults to the phone number unless you supply `contact.firstname`. `contact_created: true` indicates a new contact was created.

## Error responses

| Status | When                    | Example body                                                                                                                               |
| ------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `401`  | Missing / invalid token | `{"status":"error","message":"Invalid API token"}`                                                                                         |
| `403`  | Missing ability         | `{"status":"error","message":"Token does not have the required ability: messages.send"}`                                                   |
| `422`  | Validation              | `{"status":"error","message":"Validation failed","errors":{"message_body":["The message body may not be greater than 4096 characters."]}}` |
| `429`  | Rate limit              | `{"message":"Too many requests","retry_after":45}`                                                                                         |
| `500`  | Send failure            | `{"status":"error","message":"Failed to send message"}`                                                                                    |

## Tracking delivery status

This endpoint returns the **initial** `status` (usually `sent`). To follow delivery progress (`delivered`, `read`, `failed`):

* **Push (recommended)** — configure a webhook URL and Xobito will POST to it as each status transition arrives from Meta. See [Webhook Events → Message Delivery Status](/developers/webhook-events#message-delivery-status-events).
* **Pull** — call [`GET /messages/{messageId}/status`](/api-reference/endpoints/message-status) with the returned `message_id` for one-off lookups.

The `message_id` (Meta `wamid.*`) is the join key between this send response and the status webhook payload's `data.message_id`.
