Enviar e receber WhatsApp com PHP e Laravel
Receber é uma rota Laravel que responde ao GET de verificação da Meta e aceita POSTs, lendo os parâmetros como hub_mode, hub_verify_token e hub_challenge, porque o PHP converte os pontos em sublinhados. Enviar é um único POST com Http::withToken() para o endpoint de mensagens da Cloud API, com o phone number id e o token do número. Não é preciso nenhum SDK.
O PHP converte os pontos nos nomes das variáveis recebidas em sublinhados, por isso hub.challenge é lido como hub_challenge.
As rotas de API sem estado do Laravel, criadas por install:api, são servidas sob /api e ficam fora da proteção CSRF do grupo web.
A versão da Graph API usada nos exemplos atuais de envio de mensagens da Meta, em POST /{phone-number-id}/messages.
Como se recebem mensagens de WhatsApp no Laravel?
Com um GET e um POST no mesmo caminho em routes/api.php. Execute primeiro php artisan install:api se o ficheiro não existir. As rotas de API não têm estado e ficam fora do grupo web, pelo que o POST da Meta não é rejeitado por falta de token CSRF, o que acontece numa rota em routes/web.php.
O GET responde à verificação da Meta. A Meta envia hub.mode, hub.verify_token e hub.challenge, mas o PHP reescreve os pontos nos nomes das variáveis recebidas como sublinhados, por isso o Laravel vê hub_mode, hub_verify_token e hub_challenge. O código que lê os nomes com ponto obtém null e nunca verifica. Devolva o challenge como texto simples, não através de response()->json().
O POST entrega o payload a um job em fila e devolve 200 de imediato. Coloque uma chave longa e aleatória no caminho e valide-a nas duas rotas: a Meta assina as entregas com o app secret do Tech Provider, pelo que o cabeçalho da assinatura não pode ser validado do seu lado.
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);
});Como se envia uma mensagem de WhatsApp a partir do Laravel?
Com o cliente HTTP do Laravel: um POST ao endpoint de mensagens do phone number id, com o token de acesso como bearer token. Os dois valores vêm da ligação, são mostrados no painel e devolvidos por get_api_credentials, e devem ficar em config/services.php, assentes em variáveis de ambiente, e não no código.
A forma do corpo depende do momento. Nas 24 horas seguintes à última mensagem do cliente, funciona um objeto text, como abaixo. Fora dessa janela, a Meta só aceita um objeto template que indique um template aprovado e o respetivo idioma, e o texto livre falha com o erro 131047. A resposta é o sítio onde se vê isso: uma chamada falhada devolve um objeto error cujo código diz o que correu mal, por isso registe-o em log em vez de o descartar.
Nada no pedido é específico da EasyCoexistence. É a chamada da documentação da própria Meta, pelo que qualquer cliente HTTP de PHP, Guzzle ou cURL simples, funciona da mesma maneira.
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', []));
}O que precisa um handler Laravel em produção, além disto?
Uma fila a sério, deduplicação e uma verificação da janela.
A fila é importante porque a Meta volta a tentar quando a resposta é lenta. HandleWhatsAppEvent::dispatch() só devolve rapidamente quando a ligação da fila é real e há um worker em execução. No driver sync, o job corre dentro do pedido, e uma chamada lenta a um modelo ou uma escrita na base de dados transforma-se numa entrega duplicada.
Deduplique pelo id da mensagem, o wamid de cada mensagem, porque as novas tentativas são normais e não excecionais. Basta um índice único nessa coluna, ou um lock de cache com essa chave.
E guarde quando cada contacto escreveu pela última vez. Com essa data e hora, o envio escolhe sozinho entre texto e template, em vez de descobrir pelo erro 131047 quando o cliente já estava à espera. As respostas escritas no telemóvel, na app WhatsApp Business, chegam como eventos echo à mesma rota, pelo que o handler também consegue ver quando uma pessoa assumiu uma conversa.
Erros comuns
- Ler hub.challenge com ponto. O PHP mudou-lhe o nome para hub_challenge, por isso o handler devolve um corpo vazio e a Meta nunca verifica.
- Colocar o webhook em routes/web.php. O POST da Meta não tem token CSRF e é rejeitado com 419.
- Despachar para a fila sync. O job corre antes do 200, o processamento lento é repetido e as mensagens são processadas duas vezes.
- Devolver o challenge com response()->json(). As aspas à volta do valor fazem falhar a verificação da Meta.
Ligue o número em easycoexistence.com, defina o destino do webhook como a sua rota Laravel com a chave no caminho e leia o phone number id e o token no painel. A partir de US$ 9 por número ligado por mês, descendo até US$ 1 à medida que se adicionam números, com os primeiros 3 dias grátis.
Perguntas frequentes
Existe um SDK de PHP?
Não da nossa parte, e não é preciso nenhum. A API é da Meta, documentada pela Meta, e o cliente HTTP do Laravel ou o Guzzle acedem-lhe diretamente.
Isto funciona sem Laravel?
Sim. Em PHP simples, leia $_GET['hub_challenge'], devolva-o como text/plain e envie com cURL. A regra dos sublinhados aplica-se a todas as frameworks de PHP.
Porque é que o URL precisa de uma chave no caminho?
Porque é a autenticação que funciona. A assinatura da Meta usa o app secret do Tech Provider, que nunca é partilhado, e por isso o segredo no URL é o que impede estranhos de enviar eventos falsos.
A equipa pode continuar a usar o telemóvel?
Sim. A Coexistence mantém a app WhatsApp Business a funcionar no número, e as mensagens enviadas a partir do telemóvel chegam à sua rota como eventos echo.
Continue a ler
Pronto para começar?
Configure a Coexistência do WhatsApp em minutos, não em meses. A aplicação continua a funcionar no telemóvel.
Começar período experimental gratuitoVerificado em