Aller au contenu
OmniChannels.ai

Envoyer un message WhatsApp par API : cURL, Node, Python, PHP

Comment envoyer un message WhatsApp par API : exemples cURL, Node.js, Python et PHP pour les modèles et les messages de session, suivi des statuts et erreurs.

Mis à jour le 10 min de lecture

Par OmniChannels.ai Editorial Team · Relu par BroadNet Messaging Solutions Team

Intégration

Réponse courte

Avec l'API OmniChannels.ai, vous envoyez tous les messages WhatsApp via un seul point de terminaison, POST https://api.broadnet.me/whatsapp/message/sendbulk, en données de formulaire multipart avec un en-tête Authorization: Bearer. Le champ messageType sélectionne la charge utile : TEMPLATE pour les modèles approuvés, nécessaires pour démarrer une conversation ou écrire hors de la fenêtre de 24 heures, et TEXT, IMAGE ou INTERACTIVE pour les réponses à l'intérieur de celle-ci.

Sur cette page
  1. Comment envoyer un message WhatsApp via l'API ?
  2. Modèle ou message de session : lequel envoyer ?
  3. Comment envoyer un modèle approuvé ?
  4. Quels champs de la requête comptent le plus ?
  5. Comment envoyer un modèle avec un bouton ou un OTP ?
  6. Comment répondre dans la fenêtre de service client ?
  7. Comment suivre la livraison ?
  8. Quelles erreurs faut-il gérer ?
  9. Exemple concret : un service de notification de commandes
  10. Quelles sont les erreurs courantes ?
  11. Comment OmniChannels.ai vous aide

Points clés

  • Tous les messages WhatsApp passent par POST /whatsapp/message/sendbulk ; messageType (TEMPLATE, TEXT, IMAGE, INTERACTIVE et autres) détermine les champs obligatoires.
  • La requête est en multipart/form-data, pas en JSON : Python a donc besoin de files= et PHP d'un corps sous forme de tableau pour éviter l'encodage URL.
  • Hors de la fenêtre de service client de 24 heures, seul un modèle approuvé peut être envoyé ; à l'intérieur, vous pouvez envoyer du texte libre, des médias et des messages interactifs.
  • L'appel met les messages en file d'attente et renvoie immédiatement un batchId ; les statuts par destinataire arrivent sur votre webhook et via le point de terminaison bulk-insights.
  • L'API n'accepte pas de clé d'idempotence : enregistrez chaque envoi avant de l'effectuer et vérifiez le statut après un délai d'attente dépassé plutôt que de renvoyer.

Ce guide s'adresse aux développeurs qui intègrent l'envoi WhatsApp dans une application. Il présente des requêtes fonctionnelles en cURL, Node.js, Python et PHP, explique la différence entre messages modèles et messages de session, et traite du suivi et des erreurs. Chaque point de terminaison et chaque champ proviennent de notre référence de l'API WhatsApp et de la spécification OpenAPI.

Comment envoyer un message WhatsApp via l'API ?

Vous effectuez un seul POST authentifié vers https://api.broadnet.me/whatsapp/message/sendbulk avec des champs de formulaire. Le champ messageType choisit le type de message, et la réponse confirme que les messages ont été mis en file d'attente.

Élément Valeur
URL de base https://api.broadnet.me (sans préfixe /v1)
Point de terminaison POST /whatsapp/message/sendbulk
Authentification Authorization: Bearer $BROADNET_TOKEN
Type de contenu multipart/form-data (défini automatiquement par votre client HTTP)
Réponse Résumé de lot en JSON avec un batchId

Les identifiants de test sont délivrés après une brève demande d'accès, et les envois de test partent vers vos propres numéros vérifiés. Voir le démarrage rapide et les pages d'authentification. Gardez le jeton uniquement côté serveur.

Modèle ou message de session : lequel envoyer ?

Envoyez un modèle pour démarrer une conversation ou écrire hors de la fenêtre, et un message de session lorsque le client vous a écrit au cours des dernières 24 heures. Cette règle est fixée par Meta, pas par nous.

Message modèle Message de session
messageType TEMPLATE TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, INTERACTIVE et autres
Quand c'est autorisé À tout moment, une fois le modèle approuvé Uniquement tant que la fenêtre de service client est ouverte
Approbation requise Oui, par Meta Non
Usage typique Suivis de commande, OTP, rappels, campagnes Réponses du support, réponses aux questions

Meta facture chaque message délivré selon la catégorie, et ses règles sur les messages de service et les modèles utilitaires ont changé le 1er octobre 2026. Notre guide de la fenêtre en détaille la mécanique, et notre page tarifs et notre calculateur traitent du coût.

Comment envoyer un modèle approuvé ?

Réglez messageType=TEMPLATE, nommez le modèle et son code de langue, et transmettez les valeurs des variables. Créez batchId=0 pour démarrer un nouveau lot.

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F campaignName=order-updates \
  -F destinationNo=971500000000 \
  -F messageType=TEMPLATE \
  -F templateName=order_update \
  -F templateLanguage=en_US \
  -F bodyTemplateVariables=INV-400
// Node.js 18+ (fetch et FormData intégrés)
const form = new FormData();
form.append("batchId", "0");
form.append("campaignName", "order-updates");
form.append("destinationNo", "971500000000");
form.append("messageType", "TEMPLATE");
form.append("templateName", "order_update");
form.append("templateLanguage", "en_US");
form.append("bodyTemplateVariables", "INV-400");

const res = await fetch("https://api.broadnet.me/whatsapp/message/sendbulk", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.BROADNET_TOKEN}` }, // fetch définit la frontière multipart
  body: form,
});
console.log(await res.json());
import os
import requests

fields = {
    "batchId": "0",
    "campaignName": "order-updates",
    "destinationNo": "971500000000",
    "messageType": "TEMPLATE",
    "templateName": "order_update",
    "templateLanguage": "en_US",
    "bodyTemplateVariables": "INV-400",
}
# Envoyez chaque champ comme une partie multipart (files=) plutôt qu'encodé en URL (data=).
r = requests.post(
    "https://api.broadnet.me/whatsapp/message/sendbulk",
    headers={"Authorization": f"Bearer {os.environ['BROADNET_TOKEN']}"},
    files={k: (None, v) for k, v in fields.items()},
    timeout=30,
)
print(r.json())
<?php
$ch = curl_init("https://api.broadnet.me/whatsapp/message/sendbulk");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BROADNET_TOKEN")],
    // Un tableau (et non une chaîne) fait envoyer du multipart/form-data par cURL.
    CURLOPT_POSTFIELDS => [
        "batchId" => "0",
        "campaignName" => "order-updates",
        "destinationNo" => "971500000000",
        "messageType" => "TEMPLATE",
        "templateName" => "order_update",
        "templateLanguage" => "en_US",
        "bodyTemplateVariables" => "INV-400",
    ],
]);
echo curl_exec($ch);
curl_close($ch);

La destination est l'indicatif pays suivi du numéro, uniquement des chiffres. L'appel répond immédiatement avec un résumé de lot :

{
  "totalFiles": 1,
  "totalSuccess": 1,
  "totalFailed": 0,
  "totalDuplicates": 0,
  "batchId": "2609091405000000001",
  "fileResults": [
    {
      "fileName": "direct",
      "totalRecords": 1,
      "successCount": 1,
      "failedRecipients": [],
      "duplicateRecipients": []
    }
  ],
  "message": "Messages queued for delivery"
}

Stockez le batchId à côté de votre propre enregistrement. « En file d'attente » ne veut pas dire « délivré ».

Quels champs de la requête comptent le plus ?

Les champs obligatoires dépendent de messageType. Voici les plus courants.

Champ Obligatoire Description
messageType Oui TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, STICKER, LOCATION, CONTACTS, TEMPLATE, INTERACTIVE, REACTION ou FLOW
destinationNo ou destinationFiles L'un des deux Numéros séparés par des virgules, des points-virgules ou des retours à la ligne ; ou un fichier TXT, CSV ou XLSX
batchId Non 0 démarre un nouveau lot
campaignName Non Votre libellé pour le reporting
allowDuplicate Non false supprime les destinataires en double
templateName, templateLanguage Pour TEMPLATE Nom du modèle approuvé et code de langue, comme en_US ou ar
bodyTemplateVariables Selon le modèle Valeur de la variable du corps ; les paramètres nommés sont transmis en JSON
buttonsTemplateVariable Selon le modèle Valeur dynamique du bouton : suffixe d'URL, code promo ou code OTP
content Pour TEXT Corps du texte, jusqu'à 4 096 caractères, ou légende d'un média
mediaUrl Selon le type URL HTTPS publique du média
interactivePayload Pour INTERACTIVE Objet interactif de Meta sous forme de chaîne JSON

La planification utilise scheduled, scheduleTime et gmt, et delay limite le rythme de livraison. La liste complète figure dans la référence de l'API WhatsApp.

Comment envoyer un modèle avec un bouton ou un OTP ?

Ajoutez la valeur du bouton dans buttonsTemplateVariable. Pour un modèle d'authentification avec bouton de copie du code, transmettez le code à la fois dans le corps et dans le champ du bouton.

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=TEMPLATE \
  -F templateName=login_code \
  -F templateLanguage=en_US \
  -F bodyTemplateVariables=482913 \
  -F buttonsTemplateVariable=482913

Ici, login_code est un espace réservé pour votre propre modèle d'authentification approuvé. Pour plusieurs boutons, buttonsTemplateVariable accepte un tableau JSON. Générez le code côté serveur, stockez-le haché avec une expiration et ne le journalisez jamais. Notre guide des OTP couvre la livraison en un appui, sans appui et par copie de code.

Comment répondre dans la fenêtre de service client ?

Utilisez un type de message de session. Une réponse en texte simple nécessite messageType=TEXT et content.

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=TEXT \
  -F campaignName=support \
  -F "content=Your order has shipped. Reply here if you need anything."

Pour des boutons de réponse, envoyez messageType=INTERACTIVE et un interactivePayload conforme au format des messages interactifs de Meta.

curl -X POST https://api.broadnet.me/whatsapp/message/sendbulk \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -F batchId=0 \
  -F destinationNo=971500000000 \
  -F messageType=INTERACTIVE \
  -F campaignName=support \
  -F 'interactivePayload={"type":"button","body":{"text":"Confirm your delivery slot?"},"action":{"buttons":[{"type":"reply","reply":{"id":"slot_ok","title":"Confirm"}},{"type":"reply","reply":{"id":"slot_change","title":"Change"}}]}}'

Pour marquer un message entrant comme lu et, facultativement, afficher un indicateur de saisie, appelez POST /whatsapp/message/read avec du JSON :

curl -X POST https://api.broadnet.me/whatsapp/message/read \
  -H "Authorization: Bearer $BROADNET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "messageId": "wamid.HBgMOTcxNTAwMDAwMDAwFQIAEhgg", "typing": true }'

Consultez notre guide des messages interactifs pour des conseils de conception.

Comment suivre la livraison ?

Les statuts sont poussés vers le webhook configuré sur votre compte et peuvent aussi être lus par lot. Chaque destinataire passe par SUBMITTED, SENT, DELIVERED et READ, ou se termine en FAILED ou REJECTED avec un motif.

curl "https://api.broadnet.me/whatsapp/message/bulk-insights?batch_Id=2609091405000000001&status=FAILED&page=0&size=50" \
  -H "Authorization: Bearer $BROADNET_TOKEN"

batch_Id est obligatoire ; status, destination, search, page et size sont facultatifs, et page commence à zéro. Utilisez l'interrogation en complément pour rattraper les événements manqués, et construisez votre récepteur comme décrit dans notre guide des webhooks. Les lots peuvent aussi être mis en pause, repris et abandonnés via les points de terminaison de lots de la référence.

Quelles erreurs faut-il gérer ?

Lisez d'abord le statut HTTP, puis le corps de l'erreur. L'API renvoie les erreurs dans un corps JSON à cinq champs, et les échecs propres à WhatsApp peuvent venir de Meta.

Statut Signification Action
400 Erreur de validation, par exemple un champ propre au type manquant ou une destination invalide Corriger la requête ; ne pas réessayer telle quelle
401 / 403 Jeton manquant ou invalide, ou opération non autorisée Corriger le jeton ou nous demander d'activer l'opération
404 Modèle ou expéditeur introuvable Vérifier le nom du modèle et la langue
413 Fichier envoyé trop volumineux Réduire le fichier
429 Limite de débit Attendre Retry-After
502 Meta a rejeté l'appel ; message contient le code et le texte d'erreur de Meta Le lire ; ne réessayer que si l'erreur est transitoire
500, 503, 504 Problème de serveur ou en amont Réessayer avec backoff exponentiel et jitter

Notre guide des codes d'erreur explique les codes Meta courants. Faute de clé d'idempotence, suivez les étapes de protection contre les doublons décrites dans Erreurs et limites : écrivez d'abord un enregistrement « en attente », stockez le batchId renvoyé et, après un délai d'attente dépassé, vérifiez le statut avant de renvoyer.

Exemple concret : un service de notification de commandes

« Souq Rafiq » (société fictive), enseigne de distribution aux Émirats arabes unis, envoie un modèle order_update à l'expédition d'une commande.

  1. Le service de commandes écrit une ligne de clé order-8842-shipped avec le statut pending.
  2. Il appelle sendbulk avec le modèle et le numéro de commande comme bodyTemplateVariables.
  3. Sur un 200, il stocke le batchId et marque la ligne accepted.
  4. Le gestionnaire de webhook met à jour la ligne en DELIVERED ou READ, ou en FAILED avec un motif.
  5. Si le client répond, l'outil de support utilise un message de session TEXT dans la fenêtre.
  6. Une tâche de nuit lit bulk-insights pour les lots de la veille et rapproche les statuts manquants.

Quelles sont les erreurs courantes ?

  • Envoyer du JSON. Le point de terminaison d'envoi WhatsApp est en multipart ; Content-Type: application/json ne fonctionnera pas.
  • data= en Python au lieu de files=, ce qui encode le formulaire en URL.
  • Texte libre hors de la fenêtre, qui échoue. Utilisez un modèle.
  • Mauvais code de langue, par exemple en au lieu du en_US du modèle.
  • Considérer 200 comme « délivré ». Cela signifie « en file d'attente ».
  • Nouveaux essais à l'aveugle après un délai dépassé, qui peuvent écrire deux fois au client.
  • Jeton dans le code client. Appelez l'API uniquement depuis votre backend.
  • Limites : les détails de la charge utile varient selon le modèle. Confirmez la structure des variables avec notre équipe pour votre première campagne personnalisée.

Comment OmniChannels.ai vous aide

BroadNet est un Meta Business Solution Provider officiel, et l'onboarding vers l'API WhatsApp Business passe par l'Embedded Signup de Meta ; notre équipe prépare l'enregistrement avec vous, les approbations restant du ressort de Meta. L'API REST est documentée par une spécification OpenAPI 3.1 et un SDK TypeScript, @broadnet/cpaas-sdk, et les statuts de livraison sont normalisés entre les canaux. Commencez par la documentation de l'API WhatsApp ou le démarrage rapide.

Questions fréquentes

Envoie-t-on les messages WhatsApp en JSON ou en données de formulaire ?

En données de formulaire. Le point de terminaison d'envoi WhatsApp, POST /whatsapp/message/sendbulk, accepte du multipart/form-data, chaque champ étant une partie du formulaire. Les envois SMS unitaires utilisent du JSON, source fréquente de confusion. En Python, envoyez les champs via files= plutôt que data=, et en PHP passez un tableau comme corps pour que cURL construise du multipart.

Peut-on envoyer un message WhatsApp sans modèle ?

Uniquement dans la fenêtre de service client. Lorsqu'un client vous écrit, une fenêtre de 24 heures s'ouvre et vous pouvez répondre par du texte, des médias ou des messages interactifs. En dehors, ou pour démarrer une conversation, vous devez envoyer un modèle approuvé. L'API applique les règles de Meta, et un envoi en texte libre vers une fenêtre fermée échouera.

Comment savoir si le message a été délivré ?

L'appel d'envoi confirme seulement que les messages ont été mis en file d'attente et renvoie un batchId. Les statuts par destinataire passent par SUBMITTED, SENT, DELIVERED et READ, ou se terminent en FAILED ou REJECTED. Ils sont poussés vers votre webhook, et vous pouvez aussi les lire par lot via le point de terminaison bulk-insights.

Peut-on envoyer à de nombreux destinataires en une seule requête ?

Oui. destinationNo accepte plusieurs numéros séparés par des virgules, des points-virgules ou des retours à la ligne, et destinationFiles accepte un fichier TXT, CSV ou XLSX. Pour un modèle avec des variables différentes par destinataire, importez un fichier personnalisé avec une ligne par destinataire. Réglez allowDuplicate sur false pour supprimer les numéros en double.

Existe-t-il une clé d'idempotence pour éviter les envois en double ?

Non. L'API n'accepte pas de clé d'idempotence. Si une requête dépasse le délai d'attente, le client ne peut pas savoir si elle a été acceptée. Enregistrez chaque envoi dans votre propre base avant l'appel, stockez le batchId renvoyé et vérifiez le statut ou la liste des lots avant tout nouvel essai.

Faut-il un opt-in avant d'envoyer ?

Oui. La politique de Meta exige que chaque destinataire ait donné son opt-in pour recevoir des messages de votre entreprise, et les modèles doivent être approuvés avant usage. L'API ne vérifie pas le consentement à votre place. Recueillez l'opt-in à la source, conservez la preuve et excluez toute personne qui se désinscrit.

Sources

  1. Politique de messagerie WhatsApp Business
  2. Tarification de la plateforme WhatsApp Business — Meta for Developers
  3. RFC 7578 : Returning Values from Forms: multipart/form-data