Guides
Envoyer des messages
Textes, médias, localisation et templates : formats, fenêtre de 24 heures, statuts, historique et erreurs.
Mis à jour le 12 novembre 2026Voir en Markdown
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. |
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. |
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.
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": "rappel_rdv",
"language": "fr",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "Awa"
},
{
"type": "text",
"text": "14 novembre à 10 h"
}
]
}
]
}
}'const res = await fetch(`${process.env.INBOX_API}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "rdv-2026-11-14-client-42",
},
body: JSON.stringify({
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237600000000",
"type": "template",
"template": {
"name": "rappel_rdv",
"language": "fr",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "Awa"
},
{
"type": "text",
"text": "14 novembre à 10 h"
}
]
}
]
}
}),
});
const data = await res.json();import os
import requests
res = requests.post(
f"{os.environ['INBOX_API']}/messages",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
"Idempotency-Key": "rdv-2026-11-14-client-42",
},
json={
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237600000000",
"type": "template",
"template": {
"name": "rappel_rdv",
"language": "fr",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "Awa",
},
{
"type": "text",
"text": "14 novembre à 10 h",
},
],
},
],
},
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
'Content-Type: application/json',
'Idempotency-Key: rdv-2026-11-14-client-42',
],
CURLOPT_POSTFIELDS => json_encode([
'phoneNumberId' => '4d8e654e-d744-4bf3-8211-9ceffcccfe87',
'to' => '+237600000000',
'type' => 'template',
'template' => [
'name' => 'rappel_rdv',
'language' => 'fr',
'components' => [
[
'type' => 'body',
'parameters' => [
[
'type' => 'text',
'text' => 'Awa',
],
[
'type' => 'text',
'text' => '14 novembre à 10 h',
],
],
],
],
],
]),
]);
$data = json_decode(curl_exec($ch), true);{
"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": "rappel_rdv",
"language": "fr",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "Awa"
},
{
"type": "text",
"text": "14 novembre à 10 h"
}
]
}
]
}
},
"error": null,
"createdAt": "2026-11-12T08:33:45.855Z",
"sentAt": null,
"deliveredAt": null,
"readAt": null
}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 :
{
"error": {
"code": "outside_service_window",
"message": "Ce correspondant ne vous a pas écrit depuis 24 h : seul un template approuvé peut lui être envoyé",
"requestId": "req_4fdba006-98d4-4164-893d-98389fb54c62"
}
}Dans la fenêtre, tous les types de messages sont possibles.
Texte#
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": "Bonjour Awa, nous avons bien reçu votre demande."
}
}'const res = await fetch(`${process.env.INBOX_API}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "text",
"text": {
"body": "Bonjour Awa, nous avons bien reçu votre demande."
}
}),
});
const data = await res.json();import os
import requests
res = requests.post(
f"{os.environ['INBOX_API']}/messages",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
},
json={
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "text",
"text": {
"body": "Bonjour Awa, nous avons bien reçu votre demande.",
},
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'phoneNumberId' => '4d8e654e-d744-4bf3-8211-9ceffcccfe87',
'to' => '+237611111111',
'type' => 'text',
'text' => [
'body' => 'Bonjour Awa, nous avons bien reçu votre demande.',
],
]),
]);
$data = json_decode(curl_exec($ch), true);{
"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": "Bonjour Awa, nous avons bien reçu votre demande.",
"previewUrl": false
}
},
"error": null,
"createdAt": "2026-11-12T08:33:46.048Z",
"sentAt": null,
"deliveredAt": null,
"readAt": null
}Le corps du texte fait de 1 à 4096 caractères.
Image#
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://exemple.com/plan-d-acces.jpg",
"caption": "Plan d'\''accès au cabinet"
}
}'const res = await fetch(`${process.env.INBOX_API}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "image",
"image": {
"link": "https://exemple.com/plan-d-acces.jpg",
"caption": "Plan d'accès au cabinet"
}
}),
});
const data = await res.json();import os
import requests
res = requests.post(
f"{os.environ['INBOX_API']}/messages",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
},
json={
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "image",
"image": {
"link": "https://exemple.com/plan-d-acces.jpg",
"caption": "Plan d'accès au cabinet",
},
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'phoneNumberId' => '4d8e654e-d744-4bf3-8211-9ceffcccfe87',
'to' => '+237611111111',
'type' => 'image',
'image' => [
'link' => 'https://exemple.com/plan-d-acces.jpg',
'caption' => 'Plan d\'accès au cabinet',
],
]),
]);
$data = json_decode(curl_exec($ch), true);{
"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://exemple.com/plan-d-acces.jpg",
"caption": "Plan d'accès au cabinet"
}
},
"error": null,
"createdAt": "2026-11-12T08:33:46.070Z",
"sentAt": null,
"deliveredAt": null,
"readAt": null
}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#
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://exemple.com/ordonnance.pdf",
"filename": "ordonnance.pdf",
"caption": "Votre ordonnance"
}
}'const res = await fetch(`${process.env.INBOX_API}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "document",
"document": {
"link": "https://exemple.com/ordonnance.pdf",
"filename": "ordonnance.pdf",
"caption": "Votre ordonnance"
}
}),
});
const data = await res.json();import os
import requests
res = requests.post(
f"{os.environ['INBOX_API']}/messages",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
},
json={
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "document",
"document": {
"link": "https://exemple.com/ordonnance.pdf",
"filename": "ordonnance.pdf",
"caption": "Votre ordonnance",
},
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'phoneNumberId' => '4d8e654e-d744-4bf3-8211-9ceffcccfe87',
'to' => '+237611111111',
'type' => 'document',
'document' => [
'link' => 'https://exemple.com/ordonnance.pdf',
'filename' => 'ordonnance.pdf',
'caption' => 'Votre ordonnance',
],
]),
]);
$data = json_decode(curl_exec($ch), true);{
"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://exemple.com/ordonnance.pdf",
"caption": "Votre ordonnance",
"filename": "ordonnance.pdf"
}
},
"error": null,
"createdAt": "2026-11-12T08:33:46.098Z",
"sentAt": null,
"deliveredAt": null,
"readAt": null
}Localisation#
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"
}
}'const res = await fetch(`${process.env.INBOX_API}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "location",
"location": {
"latitude": 4.0511,
"longitude": 9.7679,
"name": "Cabinet Dentaire Zokem",
"address": "Akwa, Douala"
}
}),
});
const data = await res.json();import os
import requests
res = requests.post(
f"{os.environ['INBOX_API']}/messages",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
},
json={
"phoneNumberId": "4d8e654e-d744-4bf3-8211-9ceffcccfe87",
"to": "+237611111111",
"type": "location",
"location": {
"latitude": 4.0511,
"longitude": 9.7679,
"name": "Cabinet Dentaire Zokem",
"address": "Akwa, Douala",
},
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'phoneNumberId' => '4d8e654e-d744-4bf3-8211-9ceffcccfe87',
'to' => '+237611111111',
'type' => 'location',
'location' => [
'latitude' => 4.0511,
'longitude' => 9.7679,
'name' => 'Cabinet Dentaire Zokem',
'address' => 'Akwa, Douala',
],
]),
]);
$data = json_decode(curl_exec($ch), true);{
"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
}Suivre un message#
curl "$INBOX_API/messages/ID" \
-H "Authorization: Bearer $INBOX_KEY"const res = await fetch(`${process.env.INBOX_API}/messages/ID`, {
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
f"{os.environ['INBOX_API']}/messages/ID",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages/ID');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
],
]);
$data = json_decode(curl_exec($ch), true);{
"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": "rappel_rdv",
"language": "fr",
"components": [
{
"type": "body",
"parameters": [
{
"text": "Awa",
"type": "text"
},
{
"text": "14 novembre à 10 h",
"type": "text"
}
]
}
]
}
},
"error": null,
"createdAt": "2026-11-12T08:33:45.855Z",
"sentAt": "2026-11-12T08:33:45.891Z",
"deliveredAt": null,
"readAt": null
}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 (message.sent, message.delivered, message.read, message.failed).
L'historique#
curl "$INBOX_API/messages" \
-H "Authorization: Bearer $INBOX_KEY"const res = await fetch(`${process.env.INBOX_API}/messages`, {
headers: {
Authorization: `Bearer ${process.env.INBOX_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
f"{os.environ['INBOX_API']}/messages",
headers={
"Authorization": f"Bearer {os.environ['INBOX_KEY']}",
},
)
print(res.json())<?php
$ch = curl_init(getenv('INBOX_API') . '/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INBOX_KEY'),
],
]);
$data = json_decode(curl_exec($ch), true);{
"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://exemple.com/ordonnance.pdf",
"caption": "Votre ordonnance",
"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"
}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 |
{
"error": {
"code": "invalid_message",
"message": "Numéro destinataire invalide : utilisez le format international avec « + », ex. +237600000000",
"requestId": "req_72871759-6841-4dcf-8065-3425c4a44633"
}
}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.