# Envoyer des messages

> Textes, médias, localisation et templates : formats, fenêtre de 24 heures, statuts, historique et erreurs.

`POST /messages` **accepte** un message et répond tout de suite (`202`, statut `queued`). L'envoi réel à Meta, ses reprises éventuelles et le suivi des statuts se font en arrière-plan.

## Les champs communs

| Champ | Description |
|---|---|
| `phoneNumberId` | Le numéro d'envoi : l'`id` renvoyé par la [connexion du compte](/docs/guides/connect-whatsapp). |
| `to` | Le destinataire, **en format international avec « + »** : `+237600000000`. Les espaces, tirets et parenthèses sont ignorés. |
| `type` | `text`, `template`, `image`, `video`, `audio`, `document` ou `location`. |

<Callout type="warning" title="Le « + » est obligatoire">
Un numéro local sans indicatif (`600000000`) est refusé, pour qu'il ne parte jamais dans le mauvais pays. Dans les réponses, le numéro est rendu sans le « + » (`237600000000`).
</Callout>

## Un template (à tout moment)

Un template **approuvé par Meta** peut être envoyé à tout moment, même sans échange préalable. C'est la seule façon de **démarrer** une conversation. Voir [Templates](/docs/guides/templates).

<ApiExample method="POST" path="/messages" />

## Un message libre : la fenêtre de 24 heures

Un message libre n'est accepté que si le destinataire vous a **écrit depuis moins de 24 heures** sur ce numéro. Sinon :

<ErrorExample name="outside_service_window" />

Dans la fenêtre, tous les types de messages sont possibles.

### Texte

<ApiExample method="POST" path="/messages" variant="text" />

Le corps du texte fait de 1 à 4096 caractères.

### Image

<ApiExample method="POST" path="/messages" variant="image" />

Pour les médias (`image`, `video`, `audio`, `document`), indiquez **soit** `link` (une URL `https://` publique), **soit** `id` (un média déjà hébergé par Meta), jamais les deux. La `caption` n'existe pas pour l'audio. Le téléversement et le téléchargement de médias par l'API ne sont pas encore disponibles.

### Document

<ApiExample method="POST" path="/messages" variant="document" />

### Localisation

<ApiExample method="POST" path="/messages" variant="location" />

## Suivre un message

<ApiExample method="GET" path="/messages/{id}" />

`error` vaut `null` tant que tout va bien ; en cas d'échec, il contient le `code` et le `message` de Meta. Pour être notifié sans interroger l'API, utilisez les [webhooks](/docs/guides/webhooks) (`message.sent`, `message.delivered`, `message.read`, `message.failed`).

## L'historique

<ApiExample method="GET" path="/messages" />

L'historique est trié du plus récent au plus ancien et **paginé par curseur** : tant que `nextCursor` n'est pas `null`, passez-le dans `?cursor=` pour obtenir la page suivante. Filtres : `phoneNumberId`, `direction` (`inbound`, `outbound`), `status`, `limit` (1 à 100).

## Les erreurs d'envoi

| Code | HTTP | Cause |
|---|---|---|
| `invalid_message` | 422 | Numéro mal formé, texte vide, média ambigu (`id` et `link` à la fois), lien non `https` |
| `outside_service_window` | 422 | Message libre hors de la fenêtre de 24 heures |
| `template_not_approved` | 422 | Le template existe mais n'est pas (ou plus) approuvé |
| `idempotency_conflict` | 422 | Même `Idempotency-Key` pour une requête différente |
| `quota_exceeded` | 402 | Quota mensuel de votre plan atteint (production) |
| `not_found` | 404 | Numéro inconnu, ou d'un autre environnement |

<ErrorExample name="invalid_message" />

Les refus de Meta pendant l'envoi (template introuvable, destinataire non joignable…) ne se voient pas dans la réponse `202` : le message passe en `failed`, avec le motif de Meta, et vous recevez `message.failed`.

<Callout type="tip" title="Rejouer sans risque">
Ajoutez toujours un en-tête `Idempotency-Key` à vos envois : si votre appel expire ou si votre serveur redémarre, rejouez-le tel quel, le message ne partira jamais deux fois. Voir [Fiabilité](/docs/guides/reliability).
</Callout>
