Se connecterCommencer l’essai gratuit
Meta Tech Provider

Envoyer et recevoir des messages WhatsApp depuis PHP et Laravel

La réception passe par une route Laravel qui répond au GET de vérification de Meta et accepte les POST. Elle lit les paramètres sous les noms hub_mode, hub_verify_token et hub_challenge, car PHP transforme les points en underscores. L'envoi tient en un POST Http::withToken() vers l'endpoint messages de la Cloud API, avec l'identifiant du numéro de téléphone et son token. Aucun SDK n'est nécessaire.

hub_challenge

PHP convertit les points des noms de variables entrantes en underscores, donc hub.challenge se lit hub_challenge.

routes/api.php

Les routes API sans état de Laravel, créées par install:api, sont servies sous /api et hors de la protection CSRF du groupe web.

v25.0

La version de la Graph API utilisée dans les exemples actuels de Meta pour l'envoi de message, sur POST /{phone-number-id}/messages.

Comment recevoir des messages WhatsApp dans Laravel ?

Avec un GET et un POST sur le même chemin dans routes/api.php. Lancez d'abord php artisan install:api si le fichier n'existe pas. Les routes API sont sans état et hors du groupe web : le POST de Meta n'est donc pas rejeté pour un token CSRF manquant, ce qui arrive sur une route de routes/web.php.

Le GET répond à la vérification de Meta. Meta envoie hub.mode, hub.verify_token et hub.challenge, mais PHP réécrit les points des noms de variables entrantes en underscores : Laravel voit donc hub_mode, hub_verify_token et hub_challenge. Un code qui lit les noms avec des points obtient null et ne vérifie jamais rien. Renvoyez le challenge en texte brut, pas avec response()->json().

Le POST confie le payload à un job en file d'attente et renvoie 200 aussitôt. Placez une longue clé aléatoire dans le chemin et contrôlez-la sur les deux routes : Meta signe les envois avec l'app secret du Tech Provider, donc l'en-tête de signature ne peut pas être validé de votre côté.

routes/api.php: verification and event intake
use App\Jobs\HandleWhatsAppEvent;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/whatsapp/{key}', function (Request $request, string $key) {
    abort_unless(hash_equals(config('services.whatsapp.url_key'), $key), 404);

    // PHP turns hub.mode into hub_mode: read the underscored names
    if ($request->query('hub_mode') === 'subscribe'
        && hash_equals(config('services.whatsapp.verify_token'),
                       (string) $request->query('hub_verify_token'))) {
        return response((string) $request->query('hub_challenge'), 200)
            ->header('Content-Type', 'text/plain');
    }

    abort(403);
});

Route::post('/whatsapp/{key}', function (Request $request, string $key) {
    abort_unless(hash_equals(config('services.whatsapp.url_key'), $key), 404);

    HandleWhatsAppEvent::dispatch($request->json()->all());

    return response('', 200);
});

Comment envoyer un message WhatsApp depuis Laravel ?

Avec le client HTTP de Laravel : un POST vers l'endpoint messages de l'identifiant du numéro de téléphone, avec le token d'accès en token Bearer. Ces deux valeurs viennent de la connexion, affichées dans le dashboard et renvoyées par get_api_credentials. Elles vont dans config/services.php, alimenté par des variables d'environnement, et non dans le code.

La forme du corps dépend du délai. Dans les 24 heures qui suivent le dernier message du client, un objet text convient, comme ci-dessous. Hors de cette fenêtre, Meta n'accepte qu'un objet template nommant un template approuvé et sa langue, et un texte libre échoue avec l'erreur 131047. La réponse est l'endroit où le voir : un appel en échec renvoie un objet d'erreur dont le code indique ce qui s'est passé, alors journalisez-le au lieu de l'écarter.

Rien dans la requête n'est propre à EasyCoexistence. C'est l'appel de la documentation de Meta elle-même, donc n'importe quel client HTTP PHP, Guzzle ou cURL natif, fonctionne de la même façon.

Send a text reply inside the 24 hour window
use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.whatsapp.token'))
    ->post('https://graph.facebook.com/v25.0/'
        . config('services.whatsapp.phone_number_id') . '/messages', [
        'messaging_product' => 'whatsapp',
        'to' => '14155550188', // digits only, with country code
        'type' => 'text',
        'text' => ['body' => 'Thanks, we are on it.'],
    ]);

if ($response->failed()) {
    // error.code 131047: outside the window, send a template instead
    logger()->warning('WhatsApp send failed', $response->json('error', []));
}

Que faut-il de plus pour un handler Laravel en production ?

Une vraie file d'attente, de la déduplication et un contrôle de la fenêtre.

La file d'attente compte parce que Meta renvoie les envois quand la réponse est lente. HandleWhatsAppEvent::dispatch() ne rend la main vite que si la connexion de file d'attente est une vraie, avec un worker actif. Avec le driver sync, le job s'exécute dans la requête, et un appel lent à un modèle ou une écriture en base se transforme en envoi en double.

Dédupliquez sur l'identifiant du message, le wamid de chaque message, car les renvois sont normaux et non exceptionnels. Un index unique sur cette colonne, ou un verrou de cache indexé sur elle, suffit.

Enfin, stockez la date du dernier message de chaque contact. Avec cet horodatage, l'envoi choisit seul entre texte et template, au lieu de l'apprendre par l'erreur 131047 alors que le client attendait déjà. Les réponses saisies sur le téléphone dans l'application WhatsApp Business arrivent comme événements echo sur la même route : le handler voit donc aussi quand une personne a repris une conversation.

Erreurs courantes

  • Lire hub.challenge avec un point. PHP l'a renommé hub_challenge : le handler renvoie un corps vide et Meta ne vérifie jamais.
  • Placer le webhook dans routes/web.php. Le POST de Meta n'a pas de token CSRF et est rejeté avec une erreur 419.
  • Dispatcher vers la file sync. Le job s'exécute avant le 200, un traitement lent est renvoyé par Meta et les messages sont traités deux fois.
  • Renvoyer le challenge avec response()->json(). Les guillemets autour de la valeur font échouer la vérification de Meta.
Faire cela avec EasyCoexistence

Connectez le numéro sur easycoexistence.com, réglez la destination du webhook sur votre route Laravel avec sa clé dans le chemin, puis lisez l'identifiant du numéro de téléphone et le token dans le dashboard. À partir de US$ 9 par numéro et par mois, jusqu'à US$ 1 à mesure que vous ajoutez des numéros, avec les 3 premiers jours gratuits.

Questions fréquemment posées

Existe-t-il un SDK PHP ?

Pas chez nous, et il n'en faut pas. L'API est celle de Meta, documentée par Meta, et le client HTTP de Laravel ou Guzzle l'atteint directement.

Cela fonctionne-t-il sans Laravel ?

Oui. En PHP pur, lisez $_GET['hub_challenge'], renvoyez-le en text/plain et envoyez avec cURL. La règle des underscores s'applique à tous les frameworks PHP.

Pourquoi l'URL a-t-elle besoin d'une clé dans le chemin ?

Parce que c'est l'authentification qui fonctionne. La signature de Meta utilise l'app secret du Tech Provider, qui n'est jamais partagé : c'est donc le secret de l'URL qui empêche des inconnus d'envoyer de faux événements.

L'équipe peut-elle continuer à utiliser le téléphone ?

Oui. Coexistence maintient l'application WhatsApp Business active sur le numéro, et les messages envoyés depuis le téléphone arrivent à votre route sous forme d'événements echo.

Poursuivre la lecture

Prêt à commencer ?

Configurez WhatsApp Coexistence en quelques minutes, pas en quelques mois. L’application continue de fonctionner sur le téléphone.

Commencer l'essai gratuit

Vérifié le

Envoyer WhatsApp depuis PHP et Laravel