Como integrar a WhatsApp Cloud API a uma aplicação Laravel?
Quem desenvolve um SaaS, um e-commerce ou um CRM em Laravel já percebeu que o WhatsApp deixou de ser um extra do produto e virou parte do fluxo principal.
Com a Cloud API hospedada pela própria Meta, essa integração ficou bem mais direta do que era na época dos intermediários. Neste guia vamos montar a fundação de uma integração séria entre Laravel e Cloud API: configuração de credenciais, serviço de envio e recebimento de eventos por webhook.
Escopo deste guia: aqui montamos a base técnica, ou seja, variáveis de ambiente, serviço de envio e webhook. Para colocar em produção você ainda vai precisar de filas, logs, reprocessamento, validação de assinatura e monitoramento. Falamos de cada um deles no final.
O que você precisa ter em mãos
Antes de abrir o terminal, confirme que a conta no Meta for Developers já tem os ativos da API oficial. Se algum deles faltar, o código abaixo simplesmente não vai autenticar.
- Phone Number ID Identificador da linha que vai enviar as mensagens, seja o número de teste ou o de produção. Ele fica na seção WhatsApp do app, em Configuração da API.
- WhatsApp Business Account ID Identificador da conta comercial vinculada à empresa. Não confunda com o anterior: um aponta para a conta inteira, o outro para uma linha específica.
- Token de acesso permanente Credencial gerada no Gerenciador de Negócios para autenticar as chamadas à Graph API. O token temporário do painel de teste expira em 24 horas e não serve para produção.
- App Secret Você vai usá-lo para validar a assinatura dos webhooks. Ele fica em Configurações básicas do aplicativo, no Meta for Developers.
- HTTPS no ambiente público A Meta só entrega webhook em endpoint com certificado válido. Em desenvolvimento local, um túnel como o ngrok resolve.
Passo 1: configure as credenciais
Comece mapeando tudo no .env. Token nunca vai para dentro do código, nem em controller, nem em serviço versionado.
WHATSAPP_TOKEN=seu_token_permanente_aqui
WHATSAPP_PHONE_NUMBER_ID=seu_phone_number_id
WHATSAPP_VERSION=v20.0
WHATSAPP_VERIFY_TOKEN=um_token_qualquer_que_voce_inventa
WHATSAPP_APP_SECRET=seu_app_secret
O WHATSAPP_VERIFY_TOKEN é uma senha que você mesmo escolhe. Ela não vem da Meta: você define aqui e informa o mesmo valor no painel ao cadastrar o webhook. Serve só para provar, no handshake, que os dois lados combinaram.
Em seguida, exponha as credenciais em config/services.php, que é o lugar padrão do framework para isso.
'whatsapp' => [
'token' => env('WHATSAPP_TOKEN'),
'phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID'),
'version' => env('WHATSAPP_VERSION', 'v20.0'),
'verify_token' => env('WHATSAPP_VERIFY_TOKEN'),
'app_secret' => env('WHATSAPP_APP_SECRET'),
],
Cuidado com o cache de configuração. Depois de mudar qualquer variável em produção, rode php artisan config:clear e reconstrua o cache. Sem isso a aplicação continua lendo os valores antigos e você perde horas procurando um erro que não existe no código.
Passo 2: crie o serviço de envio
O Laravel já traz o cliente Http, então não é preciso instalar nada. O que vale a pena é isolar a conversa com a Meta em uma classe própria, em vez de espalhar chamadas HTTP pelos controllers. Quando a Meta mudar a versão da API, você altera um arquivo só.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class WhatsAppService
{
protected string $baseUrl;
protected string $token;
public function __construct()
{
$this->token = config('services.whatsapp.token');
$this->baseUrl = 'https://graph.facebook.com/'
. config('services.whatsapp.version')
. '/' . config('services.whatsapp.phone_number_id');
}
public function sendTemplateMessage(
string $to,
string $templateName,
string $languageCode = 'pt_BR',
array $components = []
): array {
$response = Http::withToken($this->token)
->post("{$this->baseUrl}/messages", [
'messaging_product' => 'whatsapp',
'to' => $to,
'type' => 'template',
'template' => [
'name' => $templateName,
'language' => ['code' => $languageCode],
'components' => $components,
],
]);
return $response->json();
}
}
Esse método envia mensagens de template, que é o único formato aceito para iniciar conversa fora da janela de atendimento. Se você ainda não cadastrou os seus, o guia sobre modelos de mensagem no WhatsApp API mostra como escrever e submeter cada um.
Para mensagem livre dentro da janela de 24 horas, a URL e a autenticação são as mesmas. Muda só o corpo, que passa a usar o tipo text em vez de template.
Passo 3: publique o webhook
Para receber respostas dos clientes e mudanças de status, a aplicação precisa expor um endpoint público. A Meta faz duas coisas nele: um GET uma única vez, para validar o endereço, e POST a cada evento.
Comece pelas rotas, em routes/api.php:
use App\Http\Controllers\WhatsAppWebhookController;
use Illuminate\Support\Facades\Route;
Route::get('/whatsapp/webhook', [WhatsAppWebhookController::class, 'verify']);
Route::post('/whatsapp/webhook', [WhatsAppWebhookController::class, 'handle']);
Agora o controller. Repare que o handle() devolve 200 imediatamente e joga o trabalho pesado para uma fila, que é o desenho recomendado desde o primeiro dia.
<?php
namespace App\Http\Controllers;
use App\Jobs\ProcessWhatsAppEvent;
use Illuminate\Http\Request;
class WhatsAppWebhookController extends Controller
{
public function verify(Request $request)
{
$verifyToken = config('services.whatsapp.verify_token');
if ($request->query('hub_mode') === 'subscribe'
&& hash_equals($verifyToken, (string) $request->query('hub_verify_token'))) {
return response($request->query('hub_challenge'), 200);
}
return response('Forbidden', 403);
}
public function handle(Request $request)
{
ProcessWhatsAppEvent::dispatch($request->all());
return response('EVENT_RECEIVED', 200);
}
}
A Meta envia os parâmetros do handshake com ponto, como hub.mode e hub.verify_token. O PHP converte esses pontos em sublinhado ao montar o array da query, por isso lemos hub_mode e hub_verify_token. Se preferir não depender desse comportamento, leia a query string bruta da requisição.
Em projetos que ainda passam essa rota pelo middleware VerifyCsrfToken, adicione api/whatsapp/webhook à lista de exceções. A Meta não envia token CSRF e a requisição seria recusada antes de chegar ao controller.
Com o webhook recebendo eventos, o próximo passo natural é montar um chatbot em cima da API do WhatsApp.
Boas práticas para produção
Uma integração que funciona no ambiente de teste costuma quebrar no primeiro pico de volume. Estes quatro pontos evitam a maior parte dos problemas.
- Responda rápido e processe em fila Nada de consultar banco, chamar CRM ou rodar lógica de bot dentro do handle. Receba o payload, despache um job e devolva 200. Se você demorar, a Meta considera falha e reentrega o mesmo evento.
- Valide a assinatura de cada requisição Confira o cabeçalho X-Hub-Signature-256 com o App Secret antes de confiar no conteúdo. Sem isso, qualquer pessoa que descobrir a URL do seu webhook pode injetar mensagens falsas na aplicação.
- Trate a reentrega e a idempotência O mesmo evento pode chegar mais de uma vez. Guarde o identificador da mensagem e ignore o que já foi processado, senão o cliente recebe a mesma resposta automática duas vezes.
- Separe os ambientes Use números, tokens e URLs de webhook diferentes para desenvolvimento, homologação e produção. Um teste disparando na base real é um problema que ninguém quer explicar.
Sobre a fila: no exemplo acima, o ProcessWhatsAppEvent é um job comum do Laravel. Rode a fila com Redis e monitore pelo Horizon. Assim você enxerga a latência de processamento e reprocessa o que falhou sem perder evento.
Quando construir e quando conectar
O código acima resolve a integração. O que costuma consumir meses de time não é ele: é a camada que vem depois. Painel multiatendente, filas por setor, permissões por usuário, histórico por cliente, gestão de templates, auditoria, relatórios e o monitoramento de tudo isso.
A Atys é construída exatamente sobre essa camada. Se a sua necessidade é enviar notificações transacionais a partir do seu produto, o serviço deste guia resolve bem e vale manter. Se a necessidade é uma equipe atendendo pelo mesmo número, com regra de distribuição e indicador de tempo de resposta, construir tudo do zero raramente compensa.
Criação da conta comercial, verificação do número e cadastro do webhook são feitos pelo fluxo oficial da Meta, sem você manter credencial nenhuma.
O processamento de eventos, a reentrega e o tratamento de duplicidade já estão implementados e monitorados.
O seu backend continua no comando: dispara mensagens e recebe eventos pela API da Atys, sem reimplementar a camada de atendimento.
O que o bot não resolve cai em uma fila com histórico, e o gestor acompanha o tempo de resposta por atendente.
Para entender como essa camada se liga ao restante do processo comercial, vale ver como funciona a integração entre CRM e WhatsApp.
Resumindo
A base de uma integração entre Laravel e Cloud API cabe em três arquivos: a configuração das credenciais, um serviço de envio e um controller de webhook. O resto é disciplina de produção, com fila, assinatura validada e idempotência.
Se a sua empresa quer pular a construção da camada operacional e ir direto para o atendimento, fale com o time da Atys. E se o que te trouxe aqui foi segurança e volume, o guia sobre segurança e escalabilidade na API oficial é a leitura seguinte.