Tutorial: Como Integrar o Laravel com Twilio para Enviar e Receber Mensagens no WhatsApp

Fala, galera! Neste post, eu te ensino a integrar o Laravel com o Twilio pra enviar e receber mensagens no WhatsApp. Configuro o ambiente (usei Docker com Sail, mas você escolhe o seu), crio uma conta no Twilio, pego as credenciais e organizo tudo no .env e no config/twilio.php. Defino rotas pra mensagens e status, exponho a app com o Expose e conecto o WhatsApp no Twilio Sandbox. No MessageController, recebo as mensagens e respondo automaticamente com o SDK do Twilio, tudo testado com logs. Simples, prático e direto ao ponto!

Publicado em: 10, março 2025

Logos do Laravel + Logo da Twilio

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.

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

  1. Rode o comando de instalação: wget -qO- expose.dev/install.sh | bash .
  2. Dê permissão: chmod +x expose.
  3. 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.


/ Veja o passo a passo no youtube

/ Autor

Foto do autor do post Lucas Souza (Virgu)

Lucas Souza (Virgu)

{Full-Stack Specialist Engineer}

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.