Tutorial: Como Integrar o Laravel com Twilio para Enviar e Receber Mensagens no WhatsApp
Criando uma Conta no Twilio e Pegando as Credenciais
Pra começar, você precisa de uma conta no Twilio. É bem simples: vai lá no site deles, cria sua conta e, assim que logar, você vai cair no painel principal. Não precisa mexer em nada no topo por enquanto, só desce a página até encontrar os Account Info. Lá você vai ver dois campos importantes: o Account SID e o Auth Token. Esses dois são o coração da integração, então anota eles com cuidado.
Link: https://twilio.com
Configurando o .env e o arquivo de Config
Depois de pegar essas credenciais, abre o arquivo .env da sua aplicação Laravel. Eu gosto de organizar tudo direitinho, então criei três variáveis no final do meu .env:
TWILIO_ACCOUNT_SID=seu_account_sid_aqui
TWILIO_AUTH_TOKEN=seu_auth_token_aqui
TWILIO_PHONE_NUMBER=numero_do_twilio
O TWILIO_PHONE_NUMBER a gente vai pegar mais pra frente, porque ele não vem dessa tela inicial — vamos usar o número do sandbox do Twilio pra testar.
Depois de preencher o .env, eu não acesso essas variáveis direto no código. Por quê? Porque, se você rodar um comando como php artisan optimize, o Laravel vai cachear tudo e o acesso direto ao .env pode quebrar. Então, eu criei um arquivo de configuração no diretório config chamado twilio.php. Ele é simples, só retorna um array com as configs:
<?php
return [
'account_sid' => env('TWILIO_ACCOUNT_SID'),
'auth_token' => env('TWILIO_AUTH_TOKEN'),
'phone_number' => env('TWILIO_PHONE_NUMBER'),
];
Agora, no código, eu acesso essas variáveis com o helper config() assim: config('twilio.account_sid'). Fica mais seguro e organizado.
Configurando as Rotas
Beleza, com as credenciais no lugar, vamos configurar as rotas da nossa aplicação. Eu criei duas rotas no arquivo routes/api.php, porque vamos trabalhar com webhooks (comunicação entre o Twilio e nossa app via API). Aqui estão elas:
Route::post('/new-message', [MessageController::class, 'newMessage'])->name('new-message');
Route::post('/status', [MessageController::class, 'status'])->name('status');
A rota /new-message vai receber as mensagens enviadas pelo usuário no WhatsApp, e a /status vai pegar os status da mensagem (tipo "enviado", "entregue" ou "lido").
Expondo a Aplicação com o Expose
Como estamos desenvolvendo localmente, o Twilio não consegue acessar nossa aplicação direto no localhost. Pra resolver isso, eu uso o Expose, uma ferramenta massa do BeyondCode que cria um túnel seguro pra expor nossa app na internet. Se você não tem o Expose instalado, é só seguir o processo no site deles: https://expose.dev/docs/introduction
- Rode o comando de instalação:
wget -qO- expose.dev/install.sh | bash. - Dê permissão:
chmod +x expose. - Mova pra um diretório global:
mv expose /usr/local/bin/.
Depois disso, pra expor minha aplicação, eu rodei:
expose share http://localhost:8888
O Expose vai te dar duas URLs: uma HTTP e outra HTTPS. Eu sempre pego a HTTPS pra garantir segurança. No meu caso, ficou algo como https://meu-tunel.expose.dev. Testei acessando no navegador e caiu direitinho na rota raiz da minha app. Agora, as rotas completas pra configurar no Twilio são:
Configurando o Sandbox do Twilio
Voltando pro Twilio, eu fui na seção Messaging > Try it out > Sandbox. Se é a primeira vez que você acessa, vai aparecer um QR code. Escaneia ele no WhatsApp do seu celular e envia a mensagem que eles pedem (no meu caso, foi join magic-automobile). Isso conecta seu número ao sandbox.
Depois disso, o Twilio atualiza a tela e mostra seu número no sandbox (começa com +1415..., por exemplo). Anota esse número e coloca no .env como TWILIO_PHONE_NUMBER. No meu .env, ficou assim:
TWILIO_PHONE_NUMBER=+14155238886
Agora, na mesma tela do sandbox, desce até Sandbox Settings. Em When a message comes in, cola a URL da rota new-message que o Expose te deu (no meu caso, https://meu-tunel.expose.dev/api/new-message). Em Status Callback URL, cola a rota status. Salva tudo e pronto, o Twilio já sabe pra onde mandar as mensagens e os status.
Testando a Chegada das Mensagens e Respondendo
Antes de ir pro código pesado, eu gosto de testar se os webhooks estão funcionando. Primeiro, instalei o SDK do Twilio:
sail composer require twilio/sdk
No meu MessageController, eu comecei com algo simples pra logar as requisições e já responder a mensagem:
use Twilio\Rest\Client;
public function newMessage(Request $request)
{
// Validação simples pra garantir que é do Twilio
if ($request->input('AccountSid') !== config('twilio.account_sid')) {
return response('Unauthorized', 401);
}
// Log pra debug
Log::build(['driver' => 'single', 'path' => storage_path('logs/twilio.log')])
->info(json_encode($request->all()));
// Pegando a mensagem enviada
$message = $request->input('Body');
// Enviando uma resposta simples de volta
$twilio = new Client(config('twilio.account_sid'), config('twilio.auth_token'));
$twilio->messages->create(
$request->input('From'), // Número de quem enviou
[
'from' => config('twilio.phone_number'),
'body' => "Recebi sua mensagem: '$message'. Beleza, funcionou!",
]
);
return response('OK', 200);
}
public function status(Request $request)
{
Log::build(['driver' => 'single', 'path' => storage_path('logs/twilio.log')])
->info("--- STATUS --- " . json_encode($request->all()));
return response('OK', 200);
}
Depois, peguei meu celular, abri o WhatsApp e mandei um "teste" pro número do sandbox. Pra ver os logs em tempo real, rodei:
sail artisan logs -f
E olha só: a mensagem chegou direitinho no log! O payload tinha um campo Body com o texto "teste". Poucos segundos depois, recebi no WhatsApp a resposta: "Recebi sua mensagem: 'teste'. Beleza, funcionou!". Isso significa que o Twilio tá conversando com nossa app e enviando mensagens de volta sem problemas.
Monitorando os Status
No método status, eu deixei o log pra ver os status das mensagens:
public function status(Request $request)
{
Log::build(['driver' => 'single', 'path' => storage_path('logs/twilio.log')])
->info("--- STATUS --- " . json_encode($request->all()));
return response('OK', 200);
}
Quando a mensagem foi entregue, vi no log os status delivered e read. Isso mostra que a mensagem chegou no meu celular e eu abri ela. Se eu quisesse, poderia usar isso pra atualizar uma interface em tempo real (com Laravel Reverb, por exemplo), mas pra esse tutorial, só saber que tá chegando já é o suficiente.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Você também pode gostar
Laravel Wayfinder: Integrando seu backend Laravel ao frontend TypeScript com facilidade
Neste post, vou mostrar como o Laravel Wayfinder pode simplificar a integração entre suas rotas e controllers Laravel com o seu código TypeScript frontend, eliminando URLs hardcoded e garantindo tipos totalmente seguros.
Laravel Auto CRUD Generator: Automatize CRUD no Laravel
O Laravel Auto CRUD Generator, desenvolvido por Abdelrahman Muhammed, automatiza operações CRUD no Laravel. Com um comando, detecta modelos, gera controladores, rotas, validações e mais, seguindo boas práticas. Inclui opções como cURL e Postman para APIs, ideal para agilizar projetos com qualidade.
Como alcancei pontuações quase perfeitas no Google Lighthouse em um blog feito com Laravel / Filament
Veja como consegui atingir pontuações acima de 90 no Google Lighthouse, melhorando drasticamente o desempenho dos meus sites e proporcionando uma experiência excepcional para os usuários.
O Poder do Método Boot em Models no Laravel
Neste artigo, compartilho minha experiência com o método boot em models no Laravel, uma funcionalidade poderosa e pouco explorada. Explico como ele funciona, especialmente ao integrar traits, e mostro exemplos práticos, como configurar eventos e escopos globais.