Oui, mais seulement avec un template approuvé par Meta : vous le créez (POST /v1/templates), attendez APPROVED, puis l'envoyez (POST /v1/messages). Un message libre hors des 24 h est impossible.
Astuce
Oui, mais seulement avec un template approuvé par Meta : vous le créez (POST /v1/templates), attendez APPROVED, puis l'envoyez (POST /v1/messages). Un message libre hors des 24 h est impossible.
Vous voulez prévenir un client qui ne vous a pas écrit dans les dernières 24 h (ou jamais) : colis prêt, rendez-vous demain, facture disponible.
Oui, c'est possible, mais jamais en texte libre. WhatsApp n'autorise un message libre que dans les 24 h qui suivent le dernier message du client. En dehors, il faut un template : un modèle de message que Meta a approuvé à l'avance. Le parcours tient en trois appels : créer, attendre APPROVED, envoyer.
Vous : (création du template « colis_pret », statut PENDING)
Meta : (approbation, relue par GET /v1/templates → APPROVED)
Vous : POST /v1/messages to=+237690000001 variables=["Awa", "CMD-1042"]
Client : reçoit « Bonjour Awa, votre commande CMD-1042 est prête à être retirée. »
curl
curl -X POST "https://wa-api.geskap.com/v1/templates" \
-H "Authorization: Bearer $GESKAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "colis_pret",
"language": "fr",
"category": "UTILITY",
"body_text": "Bonjour {{1}}, votre commande {{2}} est prête à être retirée.",
"example_values": ["Awa", "CMD-1042"],
"footer_text": "Boutique Exemple"
}'
Node.js
const res = await fetch("https://wa-api.geskap.com/v1/templates", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "colis_pret",
"language": "fr",
"category": "UTILITY",
"body_text": "Bonjour {{1}}, votre commande {{2}} est prête à être retirée.",
"example_values": ["Awa", "CMD-1042"],
"footer_text": "Boutique Exemple"
}),
});
console.log(res.status, await res.json());
PHP
$ch = curl_init("https://wa-api.geskap.com/v1/templates");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("GESKAP_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "colis_pret",
"language" => "fr",
"category" => "UTILITY",
"body_text" => "Bonjour {{1}}, votre commande {{2}} est prête à être retirée.",
"example_values" => ["Awa", "CMD-1042"],
"footer_text" => "Boutique Exemple",
]),
]);
$res = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $res;
Python
import os, requests
res = requests.request(
"POST",
"https://wa-api.geskap.com/v1/templates",
headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
json={
"name": "colis_pret",
"language": "fr",
"category": "UTILITY",
"body_text": "Bonjour {{1}}, votre commande {{2}} est prête à être retirée.",
"example_values": ["Awa", "CMD-1042"],
"footer_text": "Boutique Exemple",
},
timeout=15,
)
print(res.status_code, res.json())
curl
curl -X GET "https://wa-api.geskap.com/v1/templates" \
-H "Authorization: Bearer $GESKAP_API_KEY"
Node.js
const res = await fetch("https://wa-api.geskap.com/v1/templates", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
},
});
console.log(res.status, await res.json());
PHP
$ch = curl_init("https://wa-api.geskap.com/v1/templates");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("GESKAP_API_KEY"),
],
]);
$res = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $res;
Python
import os, requests
res = requests.request(
"GET",
"https://wa-api.geskap.com/v1/templates",
headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
timeout=15,
)
print(res.status_code, res.json())
curl
curl -X POST "https://wa-api.geskap.com/v1/messages" \
-H "Authorization: Bearer $GESKAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+237690000001",
"template": {
"name": "colis_pret",
"language": "fr",
"variables": ["Awa", "CMD-1042"]
},
"idempotency_key": "colis-CMD-1042"
}'
Node.js
const res = await fetch("https://wa-api.geskap.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"to": "+237690000001",
"template": {
"name": "colis_pret",
"language": "fr",
"variables": ["Awa", "CMD-1042"]
},
"idempotency_key": "colis-CMD-1042"
}),
});
console.log(res.status, await res.json());
PHP
$ch = curl_init("https://wa-api.geskap.com/v1/messages");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("GESKAP_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "+237690000001",
"template" => [
"name" => "colis_pret",
"language" => "fr",
"variables" => ["Awa", "CMD-1042"],
],
"idempotency_key" => "colis-CMD-1042",
]),
]);
$res = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $res;
Python
import os, requests
res = requests.request(
"POST",
"https://wa-api.geskap.com/v1/messages",
headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
json={
"to": "+237690000001",
"template": {
"name": "colis_pret",
"language": "fr",
"variables": ["Awa", "CMD-1042"],
},
"idempotency_key": "colis-CMD-1042",
},
timeout=15,
)
print(res.status_code, res.json())
La réponse est un 202 : le message est accepté, pas encore livré. Le statut de livraison (sent, delivered, read, failed) arrive sur votre webhook message.status ou se relit par GET /v1/messages/{id}.
| Code | Cause |
|---|---|
| 400 | template inconnu, pas encore APPROVED, ou nombre de variables incorrect |
| 402 | solde de crédits insuffisant |
| 403 | catégorie MARKETING non activée sur votre compte, ou template non autorisé pour cette clé |
| 429 | débit ou quota dépassé (en-tête Retry-After) |
| 502 | Meta a refusé l'envoi : les crédits sont remboursés |
Attention
Piège : un template est propre à chaque numéro WhatsApp. Approuvé sur un numéro, il n'existe pas sur un autre : créez-le sur chacun (?number= à la création, from à l'envoi). Voir Plusieurs numéros.
Attention
Piège : la catégorie compte. UTILITY (une information liée à une commande ou un rendez-vous) s'envoie tout de suite. MARKETING (promo, relance commerciale) répond 403 tant que l'envoi marketing n'est pas activé sur votre compte : voir Marketing et promo.
Attention
Piège : si le client répond, il rouvre la fenêtre de 24 h : vous pouvez alors continuer en texte libre ou avec des boutons, sans nouveau template.
Si Meta refuse le template, voir Template refusé : les causes. Pour ne pas payer pendant vos essais : Tester sans payer. Coût : Coût d'un message. Pour ne jamais envoyer deux fois au même client : Idempotence.
Preuve — vérifié dans le code de l'API : public_api.py, api_schemas.py, api_send.py.