Publicado em: 10, março 2025
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
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.
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").
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
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:
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.
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.
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.
Mais de 10 anos de experiência com Laravel e sólidos conhecimentos em frameworks front-end, como ReactJS, React Native e Vue JS. Experiência em Design de Serviço. No primeiro projeto profissional como júnior, desenvolveu em e-commerce para a maior indústria de equipamentos odontológicos da América Latina. Atualmente, atua como Full Stack Engineer Specialist em uma grande multinacional. Lidera decisões técnicas e é um suporte fundamental para a equipe de desenvolvimento.