EntrarComeçar teste grátis
Meta Tech Provider

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.

hub_challenge

O PHP converte pontos em nomes de variáveis recebidas em underscores, então hub.challenge é lido como hub_challenge.

routes/api.php

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.

v25.0

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.

routes/api.php: verification and event intake
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.

Send a text reply inside the 24 hour window
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.
Fazendo isso com o EasyCoexistence

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átis

Verificado em

Enviar WhatsApp com PHP e Laravel