AccediInizia la prova gratuita
Meta Tech Provider

Invia e ricevi WhatsApp da PHP e Laravel

Per ricevere serve una route Laravel che risponde alla GET di verifica di Meta e accetta le POST, leggendo i parametri come hub_mode, hub_verify_token e hub_challenge, perché PHP trasforma i punti in underscore. Per inviare basta una POST con Http::withToken() all'endpoint dei messaggi della Cloud API, con il phone number id e il token del numero. Non serve nessun SDK.

hub_challenge

PHP converte i punti nei nomi delle variabili in ingresso in underscore, quindi hub.challenge si legge come hub_challenge.

routes/api.php

Le route API di Laravel sono stateless, create da install:api, servite sotto /api e fuori dalla protezione CSRF del gruppo web.

v25.0

La versione della Graph API è quella degli esempi attuali di Meta per l'invio dei messaggi, su POST /{phone-number-id}/messages.

Come si ricevono i messaggi WhatsApp in Laravel?

Con una GET e una POST sullo stesso percorso in routes/api.php. Esegui prima php artisan install:api se il file non esiste. Le route API sono stateless e fuori dal gruppo web, quindi la POST di Meta non viene rifiutata per un token CSRF mancante, cosa che invece succede su una route in routes/web.php.

La GET risponde alla verifica di Meta. Meta invia hub.mode, hub.verify_token e hub.challenge, ma PHP riscrive i punti nei nomi delle variabili in ingresso come underscore, quindi Laravel vede hub_mode, hub_verify_token e hub_challenge. Il codice che legge i nomi con il punto ottiene null e non verifica mai. Restituisci il challenge come testo semplice, non con response()->json().

La POST passa il payload a un job in coda e risponde subito 200. Metti una chiave casuale e lunga nel percorso e controllala su entrambe le route: Meta firma le consegne con l'app secret del Tech Provider, quindi l'intestazione della firma non può essere validata da parte tua.

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);
});

Come si invia un messaggio WhatsApp da Laravel?

Con il client HTTP di Laravel: una POST all'endpoint dei messaggi per il phone number id, con il token di accesso come bearer token. Entrambi i valori vengono dalla connessione, visibili nella dashboard e restituiti da get_api_credentials, e vanno in config/services.php alimentato da variabili d'ambiente, non nel codice.

La forma del corpo dipende dai tempi. Entro 24 ore dall'ultimo messaggio del cliente funziona un oggetto text, come qui sotto. Fuori da quella finestra Meta accetta solo un oggetto template che indica un template approvato e la sua lingua, e il testo libero fallisce con l'errore 131047. La risposta è il posto dove vederlo: una chiamata fallita restituisce un oggetto error il cui code dice cosa è andato storto, quindi registralo nei log invece di scartarlo.

Nulla nella richiesta è specifico di EasyCoexistence. È la chiamata della documentazione di Meta, quindi qualsiasi client HTTP PHP, Guzzle o cURL puro, funziona allo stesso modo.

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', []));
}

Cosa serve in più a un handler Laravel in produzione?

Una coda vera, la deduplicazione e un controllo sulla finestra.

La coda conta perché Meta ripete l'invio quando la risposta è lenta. HandleWhatsAppEvent::dispatch() ritorna in fretta solo se la connessione della coda è reale e c'è un worker in esecuzione. Con il driver sync il job gira dentro la richiesta, e una chiamata lenta a un modello o una scrittura sul database diventa una consegna duplicata.

Deduplica sull'id del messaggio, il wamid presente in ogni messaggio, perché i nuovi tentativi sono la norma e non l'eccezione. Basta un indice univoco su quella colonna, oppure un lock in cache con quella chiave.

E salva quando ogni contatto ha scritto per l'ultima volta. Con quel timestamp il percorso di invio sceglie da solo tra text e template, invece di scoprirlo dall'errore 131047 quando il cliente stava già aspettando. Le risposte scritte dal telefono nell'app WhatsApp Business arrivano come eventi echo sulla stessa route, quindi l'handler può anche capire quando una persona ha preso in mano una conversazione.

Errori comuni

  • Leggere hub.challenge con il punto. PHP l'ha rinominato hub_challenge, quindi l'handler restituisce un corpo vuoto e Meta non verifica mai.
  • Mettere il webhook in routes/web.php. La POST di Meta non ha un token CSRF e viene rifiutata con 419.
  • Inviare i job alla coda sync. Il job gira prima del 200, la gestione lenta viene ritentata e i messaggi vengono elaborati due volte.
  • Restituire il challenge con response()->json(). Le virgolette attorno al valore fanno fallire il controllo di Meta.
Farlo con EasyCoexistence

Collega il numero su easycoexistence.com, imposta come destinazione del webhook la tua route Laravel con la chiave nel percorso e leggi il phone number id e il token dalla dashboard. Da US$ 9 per numero al mese, fino a US$ 1 man mano che si aggiungono numeri, con i primi 3 giorni gratuiti.

Domande frequenti

Esiste un SDK PHP?

Non da parte nostra, e non serve. L'API è di Meta, documentata da Meta, e il client HTTP di Laravel o Guzzle la raggiunge direttamente.

Funziona anche senza Laravel?

Sì. In PHP puro leggi $_GET['hub_challenge'], restituiscilo come text/plain e invia con cURL. La regola dell'underscore vale per ogni framework PHP.

Perché l'URL ha bisogno di una chiave nel percorso?

Perché è l'autenticazione che funziona davvero. La firma di Meta usa l'app secret del Tech Provider, che non viene mai condiviso, quindi il segreto nell'URL è ciò che impedisce a sconosciuti di inviare eventi falsi.

Il team può continuare a usare il telefono?

Sì. Coexistence mantiene attiva l'app WhatsApp Business sul numero, e i messaggi inviati dal telefono arrivano alla tua route come eventi echo.

Continua a leggere

Pronto per iniziare?

Configura WhatsApp Coexistence in pochi minuti, non in mesi. L'app continua a funzionare sul telefono.

Inizia la prova gratuita

Verificato il

Inviare WhatsApp da PHP e Laravel