Enviar e receber WhatsApp com PHP e Laravel
Receber é uma rota do 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 troca pontos por underscores. Enviar é um 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 SDK.
O PHP converte pontos em nomes de variáveis recebidas em underscores, então hub.challenge é lido como hub_challenge.
As rotas de API stateless do Laravel, criadas por install:api, são servidas sob /api e fora da proteção CSRF do grupo web.
A versão da Graph API usada nos exemplos atuais de envio de mensagem da Meta, em POST /{phone-number-id}/messages.
Como receber mensagens do WhatsApp no Laravel?
Com um GET e um POST no mesmo caminho em routes/api.php. Rode php artisan install:api antes, se o arquivo não existir. As rotas de API são stateless e ficam fora do grupo web, então o POST da Meta não é rejeitado por falta de token CSRF, o que acontece em uma rota de 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 pontos em nomes de variáveis recebidas como underscores, então o Laravel enxerga hub_mode, hub_verify_token e hub_challenge. O código que lê os nomes com ponto recebe null e nunca verifica. Devolva o challenge como texto puro, não por response()->json().
O POST entrega o payload a um job em fila e retorna 200 na hora. Coloque uma chave aleatória longa no caminho e confira nas duas rotas: a Meta assina as entregas com o app secret do Tech Provider, então o cabeçalho de 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 enviar uma mensagem do WhatsApp pelo Laravel?
Com o cliente HTTP do Laravel: um POST para o endpoint de mensagens do phone number id, com o token de acesso como bearer token. Os dois valores vêm da conexão, aparecem no painel e são devolvidos por get_api_credentials, e devem ficar em config/services.php, apoiados em variáveis de ambiente, e não no código.
O formato do corpo depende do momento. Dentro de 24 horas da última mensagem do cliente, um objeto text funciona, como abaixo. Fora dessa janela, a Meta aceita apenas um objeto template com o nome de um template aprovado e seu idioma, e texto livre falha com o erro 131047. A resposta é o lugar para ver isso: uma chamada que falha devolve um objeto error cujo code diz o que deu errado, então registre-o em log em vez de descartá-lo.
Nada na requisição é específico do EasyCoexistence. É a chamada da própria documentação da Meta, então qualquer cliente HTTP de PHP, Guzzle ou cURL puro, funciona da mesma forma.
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 um handler Laravel em produção precisa além disso?
Uma fila de verdade, deduplicação e uma verificação da janela.
A fila importa porque a Meta repete a entrega quando a resposta demora. HandleWhatsAppEvent::dispatch() só retorna rápido quando a conexão da fila é real e há um worker rodando. No driver sync o job roda dentro da requisição, e uma chamada lenta a um modelo ou uma escrita no banco vira uma entrega duplicada.
Deduplique pelo id da mensagem, o wamid de cada mensagem, porque repetições são normais, não exceção. Um índice único nessa coluna, ou um lock de cache com ela como chave, basta.
E guarde quando cada contato escreveu pela última vez. Com esse timestamp, o caminho de envio escolhe texto ou template sozinho, em vez de descobrir pelo erro 131047 depois que o cliente já estava esperando. As respostas digitadas no celular, no app do WhatsApp Business, chegam como eventos echo na mesma rota, então o handler também percebe quando uma pessoa assumiu uma conversa.
Erros comuns
- Ler hub.challenge com ponto. O PHP o renomeou para hub_challenge, então 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 roda antes do 200, o processamento lento é repetido e as mensagens são processadas duas vezes.
- Devolver o challenge com response()->json(). As aspas em volta do valor fazem a verificação da Meta falhar.
Conecte o número em easycoexistence.com, defina o destino do webhook como sua rota do 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 conectado por mês, caindo até US$ 1 conforme você adiciona números, com os 3 primeiros dias grátis.
Perguntas frequentes
Existe um SDK de PHP?
Não da nossa parte, e não é preciso. A API é da Meta, documentada pela Meta, e o cliente HTTP do Laravel ou o Guzzle a acessam diretamente.
Isso funciona sem Laravel?
Sim. Em PHP puro, leia $_GET['hub_challenge'], devolva-o como text/plain e envie com cURL. A regra do underscore vale para todo framework PHP.
Por que a 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 é compartilhado, então o segredo na URL é o que impede estranhos de enviar eventos falsos.
A equipe ainda pode usar o celular?
Sim. A Coexistência mantém o app do WhatsApp Business funcionando no número, e as mensagens enviadas pelo celular chegam à sua rota como eventos echo.
Continue lendo
Pronto para começar?
Configure o WhatsApp Coexistence em minutos, não em meses. O aplicativo continua funcionando no celular.
Começar teste grátisVerificado em