# Send a message

> Send a message on a messaging channel, such as an approved WhatsApp template, with an idempotent request.

`POST https://api-connect.nxiom.com/api/v1/messaging/messages`

One endpoint sends on every messaging channel: `channel` chooses the channel and the fields that go with it. WhatsApp is available today; the WhatsApp fields are below.

## Authorization

Requires `whatsapp.send` to send on the `whatsapp` channel. A key with `whatsapp.full_access` can also send. WhatsApp must be turned on for your organization.

## Headers

| Header | Required | Value |
| --- | --- | --- |
| `Authorization` | Yes | `Bearer YOUR_API_KEY` |
| `Content-Type` | Yes | `application/json` |
| `Idempotency-Key` | Yes | A stable key for this send operation, up to 200 characters. Scoped to your project. |

## Body parameters

| Field | Type | Description |
| --- | --- | --- |
| `channel` | string · required | `whatsapp`. The channel to send on; it decides the other fields. |
| `from` | string · required | Phone number of the connected WhatsApp number to send from, in E.164 format, such as `+213555000111`. It must be connected in the API key's project. |
| `to` | string · required | Recipient phone number in E.164 format: `+`, the country code, and 7 to 15 digits, such as `+213555123456`. |
| `template` | object · required | The approved template to send. |
| `template.id` | string · required | Template ID of an approved template of the sender's WhatsApp Business Account, starting with `wat_`. The template sets the language and category. |
| `template.variables` | object · optional | Values for the template's variables. Omit it for a template without variables. |
| `template.variables.header` | object · optional | Header variable values, keyed by variable number or name, such as `{ "1": "A-1234" }`. |
| `template.variables.body` | object · optional | Body variable values, keyed by variable number or name, such as `{ "1": "Amina", "2": "A-1234" }`. |

Every variable of the template's header and body needs a non-empty string value of up to 1,024 characters. Keys are the variable's number (`"1"`) for numbered variables, or its name (`"first_name"`) for named ones. See [Variables](/docs/whatsapp/templates#variables).

## Request

### cURL

```shell
curl -X POST "https://api-connect.nxiom.com/api/v1/messaging/messages" \
  -H "Authorization: Bearer $NEXIOM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-A-1234-shipped" \
  -d '{
  "channel": "whatsapp",
  "from": "+213555000111",
  "to": "+213555123456",
  "template": {
    "id": "wat_example",
    "variables": {
      "body": {
        "1": "Amina",
        "2": "A-1234"
      }
    }
  }
}'
```

### Node.js

```typescript
import { NexiomConnect } from '@nexiom/connect';

const apiKey = process.env.NEXIOM_API_KEY;
if (!apiKey) throw new Error('Set NEXIOM_API_KEY');

const nexiom = new NexiomConnect({ apiKey });

const { data, error } = await nexiom.messaging.send(
  {
    channel: 'whatsapp',
    from: '+213555000111',
    to: '+213555123456',
    template: {
      id: 'wat_example',
      variables: {
        body: { 1: 'Amina', 2: 'A-1234' },
      },
    },
  },
  { idempotencyKey: 'order-A-1234-shipped' },
);

if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data.id, data.status);
```

### PHP

```php
<?php

use Nexiom\Connect\NexiomConnect;

$apiKey = getenv('NEXIOM_API_KEY');
if (!$apiKey) {
    throw new RuntimeException('Set NEXIOM_API_KEY');
}

$nexiom = new NexiomConnect($apiKey);

$message = $nexiom->messaging->send(
    [
        'channel' => 'whatsapp',
        'from' => '+213555000111',
        'to' => '+213555123456',
        'template' => [
            'id' => 'wat_example',
            'variables' => [
                'body' => [1 => 'Amina', 2 => 'A-1234'],
            ],
        ],
    ],
    ['idempotencyKey' => 'order-A-1234-shipped'],
);

echo $message['id'], ' ', $message['status'];
```

## Response

HTTP `200`. Example identifiers below are illustrative.

`data` is the [message object](/docs/api/messaging/retrieve#the-message-object), without `content`. A normal send returns the status `accepted`: WhatsApp accepted the message, which is not yet delivery. Follow it with [Retrieve a message](/docs/api/messaging/retrieve) or the [WhatsApp webhooks](/docs/webhooks/event-types#whatsapp-events).

When WhatsApp doesn't confirm the message in time, the response is still `200` with the status `uncertain`: the message may have been sent, and it is never resent automatically. See [When a message is uncertain](/docs/whatsapp/sending#when-a-message-is-uncertain).

Retrying with the same `Idempotency-Key` and the same body returns the original message as it is now, without sending or charging again. Its status may have moved on.

### 200 OK

Message accepted by WhatsApp. Keep its id to follow delivery.

```json
{
  "message": "Message sent",
  "data": {
    "id": "msg_example",
    "channel": "whatsapp",
    "status": "accepted",
    "sender": "+213555000111",
    "sender_name": "Acme",
    "recipient": "+213555123456",
    "category": "utility",
    "template": {
      "id": "wat_example",
      "name": "order_update",
      "language": "en_US",
      "version": 1
    },
    "is_billable": true,
    "cost": "0.0000",
    "cost_currency": "DZD",
    "failure_code": null,
    "failure_reason": null,
    "created_at": "2026-10-14T09:30:00.000Z",
    "accepted_at": "2026-10-14T09:30:01.000Z",
    "sent_at": null,
    "delivered_at": null,
    "read_at": null,
    "failed_at": null
  }
}
```

### 400 Bad Request

The request is invalid. Correct it before retrying; `details` lists the failing fields.

```json
{
  "error": "invalid_argument",
  "message": "Request doesn't match the schema",
  "correlationId": "req_example",
  "details": {
    "context": "body",
    "issues": []
  }
}
```

### 401 Unauthorized

The API key is missing, malformed, revoked, or expired.

```json
{
  "error": "unauthorized",
  "message": "Invalid API key",
  "correlationId": "req_example"
}
```

### 403 Forbidden

The API key lacks whatsapp.send, or WhatsApp is not turned on for your organization (code: FEATURE_NOT_AVAILABLE). With the code paid_usage_disabled, your included messages are used up and paid usage is off.

```json
{
  "error": "not_allowed",
  "message": "Missing required API key permissions: whatsapp.send",
  "correlationId": "req_example"
}
```

### 404 Not Found

No number with this phone number is connected in the project, the number was removed, the template doesn't exist, or the template belongs to another WhatsApp Business Account.

```json
{
  "error": "not_found",
  "code": "whatsapp_template_not_found",
  "message": "Template not found for this number.",
  "correlationId": "req_example"
}
```

### 409 Conflict

The Idempotency-Key was already used for a different message. Use a new key for a new message.

```json
{
  "error": "conflict",
  "code": "whatsapp_idempotency_key_reused",
  "message": "This idempotency key was already used for a different message.",
  "correlationId": "req_example"
}
```

### 422 Unprocessable Entity

The sender, the template, or WhatsApp can't take the message right now. The code says which; see the table below.

```json
{
  "error": "unexpected_state",
  "code": "whatsapp_template_not_approved",
  "message": "This template is not approved by WhatsApp.",
  "correlationId": "req_example"
}
```

### 429 Too Many Requests

A plan allowance, your wallet, or your organization's request rate refused the send. Nothing was sent or charged. A rate limit can be retried shortly with the same key; a plan or wallet code needs a change first.

```json
{
  "error": "quota_exceeded",
  "code": "wallet_balance_insufficient",
  "message": "Insufficient wallet balance",
  "correlationId": "req_example"
}
```

## Errors

Errors carry an `error` category, a `code` for WhatsApp-specific cases, a readable `message`, and a `correlationId`. A refused request sends nothing and charges nothing, unless it says the message was stored as `failed`.

| Status | `code` | Returned when |
| --- | --- | --- |
| `400` | none | The body doesn't match the schema: a missing or unavailable `channel`, an unknown or missing field, or `from` or `to` not in E.164 format. `details` lists the failing fields. |
| `400` | `INVALID_DATA` | The `Idempotency-Key` header is missing, empty, or longer than 200 characters. |
| `400` | `whatsapp_template_variable_missing` | A variable of the template has no value, or an empty one. |
| `400` | `whatsapp_template_variable_unknown` | A value was given for a variable the template doesn't have. |
| `400` | `whatsapp_template_variable_too_long` | A value is longer than 1,024 characters. |
| `401` | none | The API key is missing, malformed, revoked, or expired. |
| `403` | none | The key lacks the `whatsapp.send` permission. |
| `403` | `FEATURE_NOT_AVAILABLE` | WhatsApp is not turned on for your organization. |
| `403` | `paid_usage_disabled` | The plan's included messages are used up and paid usage is off. |
| `404` | `whatsapp_number_not_found` | No number with this phone number is connected in the key's project. |
| `404` | `whatsapp_sender_removed` | The sender was removed. Connect the number again to send from it. |
| `404` | `whatsapp_template_not_found` | No template with this ID exists for the sender's WhatsApp Business Account. |
| `409` | `whatsapp_idempotency_key_reused` | The key was already used for a different message. |
| `422` | `whatsapp_number_not_ready` | The number is not connected, or WhatsApp is limiting or blocking it. Check the sender's page. |
| `422` | `whatsapp_template_not_approved` | The template, or its latest edit, is not approved, or it was deleted in WhatsApp. |
| `422` | `whatsapp_template_not_supported` | The template has content Nexiom Connect can't send yet, such as a media header. The message explains which. |
| `422` | `whatsapp_message_rejected` | WhatsApp refused the message; `message` includes WhatsApp's reason. The message is stored as `failed`. |
| `422` | `whatsapp_provider_unavailable` | WhatsApp is busy. The message is stored as `failed`; retry later with a new key. |
| `429` | `api_rate_limit_per_second_exceeded` | Your organization sent more requests per second than its plan allows. |
| `429` | `api_key_authentication_rate_limited` | Too many requests with invalid API keys from your network. |
| `429` | `included_usage_exhausted` | The plan's included messages are used up, and the plan has no paid messages. |
| `429` | `usage_spend_limit_exceeded` | The message would go past your spending limit. |
| `429` | `wallet_balance_insufficient` | The wallet cannot cover a message beyond the included ones. |
| `429` | `service_not_in_plan` | Your plan doesn't include WhatsApp messages. |

A `422` stored as `failed` keeps its idempotency key: retrying with the same key returns that failed message instead of sending again.

---

Source: https://connect.nxiom.com/docs/api/messaging/send
