---
title: "API de WhatsApp em Laravel: filas e notificação | D-API"
description: "Integre a API de WhatsApp no Laravel com Http facade, config/services.php, Job em fila, controller de webhook e um canal de Notification próprio para WhatsApp."
url: https://d-api.cloud/api-whatsapp-laravel
---

Por linguagem

# API de WhatsApp em Laravel: do config ao canal de Notification

No Laravel, a API de WhatsApp da D-API se encaixa no que o framework já oferece: credenciais em config/services.php, chamadas pela Http facade, envio em Job na fila e um canal de Notification para mandar WhatsApp como se manda e-mail. O webhook entra como uma rota com controller que só enfileira.

Por Time de engenharia D-APIAtualizado em 24 de set de 20266 min de leitura

## A arquitetura em uma frase

Nada de chamar a API no meio do controller. O fluxo que funciona bem em produção tem quatro peças, todas nativas do Laravel: configuração centralizada, um cliente HTTP pré-configurado, Jobs para o envio e para o processamento do que chega, e uma Notification para o código de negócio não precisar saber que existe uma [API de WhatsApp](https://d-api.cloud/) por trás. Não há pacote oficial para PHP; tudo abaixo usa o `Illuminate\Http` que vem com o framework.

## Credenciais em config/services.php

Siga o mesmo padrão usado para Mailgun ou Stripe. As chaves ficam no `.env` e o código lê sempre via `config()`, o que mantém o `php artisan config:cache` funcionando.

```
// config/services.php
'dapi' => [
    'key'           => env('DAPI_API_KEY'),
    'session'       => env('DAPI_SESSION_ID'),
    'webhook_token' => env('DAPI_WEBHOOK_TOKEN'),
],
```

Em seguida, um macro no `AppServiceProvider` entrega um cliente pronto, com URL base, header de autenticação sem Bearer e timeouts explícitos. Assim nenhum ponto do código esquece o timeout.

```
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Http;

public function boot(): void
{
    Http::macro('dapi', fn () => Http::baseUrl('https://api.d-api.cloud')
        ->withHeaders(['Authorization' => config('services.dapi.key')])
        ->acceptJson()
        ->connectTimeout(3)
        ->timeout(10));
}
```

## O Job que envia a mensagem

O Job decide o que é falha temporária e o que é erro definitivo. Resposta 4xx significa que o pedido está errado (número inválido, sessão desconectada, campo faltando) e tentar de novo não muda nada. Erro 5xx e timeout podem passar sozinhos, então o Job relança a exceção e o worker agenda outra tentativa usando o `$backoff`.

```
// app/Jobs/EnviarWhatsApp.php
namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class EnviarWhatsApp implements ShouldQueue
{
    use Queueable;

    public int $tries = 5;
    public array $backoff = [10, 30, 60, 120];

    public function __construct(
        public string $to,
        public string $text,
        public ?string $sessionId = null,
    ) {}

    public function handle(): void
    {
        $response = Http::dapi()->post('/api/v1/messages/send/text', [
            'sessionId' => $this->sessionId ?? config('services.dapi.session'),
            'to'        => $this->to,
            'text'      => $this->text,
        ]);

        if ($response->clientError()) {
            // corpo de erro: { success: false, error, statusCode }
            $this->fail(new RuntimeException($response->json('error', 'Requisição recusada')));
            return;
        }

        $response->throw(); // 5xx vira exceção e o worker tenta de novo
    }
}
```

Um cuidado honesto: quando o timeout estoura, você não sabe se a mensagem saiu. A nova tentativa pode gerar duplicidade. Para avisos em que isso importa, como cobrança, reduza `$tries` ou registre o envio antes de repetir. Para quem dispara avisos de pedido, pagamento e entrega, o guia de [notificações transacionais](https://d-api.cloud/api-whatsapp-notificacoes-transacionais) aprofunda esse ponto.

## Um canal de Notification para WhatsApp

É aqui que a integração fica com cara de Laravel. Com um canal próprio, qualquer Notification pode incluir o WhatsApp no `via()`, ao lado de `mail` e `database`.

```
// app/Notifications/Channels/WhatsAppChannel.php
namespace App\Notifications\Channels;

use App\Jobs\EnviarWhatsApp;
use Illuminate\Notifications\Notification;

class WhatsAppChannel
{
    public function send(object $notifiable, Notification $notification): void
    {
        $numero = $notifiable->routeNotificationFor('whatsapp', $notification);
        if (! $numero) {
            return;
        }
        EnviarWhatsApp::dispatch($numero, $notification->toWhatsApp($notifiable));
    }
}

// app/Notifications/PedidoEnviado.php
public function via(object $notifiable): array
{
    return ['mail', WhatsAppChannel::class];
}

public function toWhatsApp(object $notifiable): string
{
    return "Olá, {$notifiable->name}! Seu pedido {$this->pedido->codigo} saiu para entrega.";
}

// app/Models/User.php
public function routeNotificationForWhatsApp(): ?string
{
    return $this->whatsapp; // formato 5511999999999
}
```

O código de negócio continua limpo: `$user->notify(new PedidoEnviado($pedido))`. Quem decide o canal é a Notification, e quem conhece a API é só o Job.

Esse desenho também facilita regras que costumam aparecer depois: respeitar a preferência de contato do usuário (basta checar um campo dentro do `via()`), evitar mensagens de madrugada atrasando o dispatch com `delay()` ou mandar um texto diferente para cada perfil. Nenhuma dessas mudanças toca no Job nem na configuração do cliente HTTP.

## Rota e controller do webhook

O webhook entra por `routes/api.php`, fora do middleware de CSRF. No Laravel 11 em diante esse arquivo é criado com `php artisan install:api`. O controller valida o token secreto da URL, filtra o evento e despacha um Job. Nada de consulta pesada antes de responder.

```
// routes/api.php
Route::post('/webhooks/whatsapp/{token}', WhatsAppWebhookController::class);

// app/Http/Controllers/WhatsAppWebhookController.php
class WhatsAppWebhookController
{
    public function __invoke(Request $request, string $token): JsonResponse
    {
        abort_unless(hash_equals(config('services.dapi.webhook_token'), $token), 401);

        if ($request->input('event') === 'messages.received' && ! $request->boolean('data.fromMe')) {
            ProcessarMensagemRecebida::dispatch(
                $request->input('sessionId'),
                $request->input('data'),
            );
        }

        return response()->json(['ok' => true]);
    }
}

// dentro de ProcessarMensagemRecebida::handle()
if (! Cache::add('wa:msg:' . $this->data['id'], true, now()->addHour())) {
    return; // evento repetido, já tratado
}
```

O `Cache::add` só grava se a chave não existe, o que resolve reenvios do mesmo evento. O webhook de WhatsApp não é assinado, por isso o token na URL e o `hash_equals` fazem a validação.

## Testar sem mandar mensagem de verdade

Como toda chamada passa pela Http facade, o `Http::fake` intercepta os envios nos testes do PHPUnit ou do Pest. Nenhuma mensagem sai para um número real, e você consegue verificar exatamente o que o Job mandou para a API, inclusive o comportamento diante de uma resposta de erro.

```
Http::fake([
    'api.d-api.cloud/api/v1/messages/send/text' => Http::response(
        ['success' => false, 'error' => 'Session not connected', 'statusCode' => 400], 400
    ),
]);

(new EnviarWhatsApp('5511999999999', 'Teste'))->withFakeQueueInteractions()->handle();

Http::assertSent(fn ($request) =>
    $request->hasHeader('Authorization', config('services.dapi.key'))
    && $request['to'] === '5511999999999'
);
```

Vale cobrir pelo menos três cenários: envio aceito, resposta 4xx marcando o Job como falho sem nova tentativa, e resposta 5xx relançando a exceção. Para a Notification, o `Notification::fake` já confirma que o canal certo foi escolhido sem nem chegar ao Job. Com esses testes no CI, uma mudança de payload ou de configuração quebra o build antes de quebrar o atendimento do cliente.

## Quando o sistema atende vários clientes

Em um SaaS feito em Laravel, cada cliente conecta o próprio número. Guarde o `sessionId` no model do tenant e passe para o Job, em vez de usar o valor fixo do config. No webhook, o campo `sessionId` diz de qual cliente é a mensagem. A organização de muitas conexões está em [múltiplos números no WhatsApp](https://d-api.cloud/api-whatsapp-multiplos-numeros), e o modelo comercial para esse perfil em [API de WhatsApp para SaaS](https://d-api.cloud/api-whatsapp-para-saas). Se parte do seu sistema ainda é PHP sem framework, o [guia de PHP com cURL](https://d-api.cloud/api-whatsapp-php) cobre esse cenário.

## Perguntas frequentes

### Existe pacote Laravel da D-API no Packagist?

Não. A integração usa o Http client que já vem no Laravel, porque a API é REST. Isso evita dependência extra e deixa você controlar timeout, tentativas e logs com as ferramentas do próprio framework.

### Por que mandar a mensagem por Job e não direto no controller?

Porque o envio é uma chamada de rede. Dentro do request, uma lentidão da rede vira lentidão para o usuário. No Job, a tela responde na hora e o Laravel repete a tentativa com intervalo se algo falhar.

### O webhook dá erro 419. O que é?

É a proteção CSRF das rotas web recusando o POST da D-API. Registre a rota do webhook em routes/api.php, que não usa CSRF, ou exclua o caminho da verificação no bootstrap da aplicação.

### Consigo usar Laravel Horizon para acompanhar os envios?

Sim. Os Jobs desta página são Jobs comuns em fila Redis, então aparecem no Horizon com tentativas, falhas e tempo de execução, sem nenhuma configuração específica para WhatsApp.

### Posso ter um número de WhatsApp por cliente do meu sistema?

Pode. Cada número é uma sessão com seu próprio sessionId. Guarde o sessionId no model do cliente e passe para o Job em vez de ler um valor fixo do config.

Teste a API de WhatsApp da D-API3 dias grátis, sem cartão.[Começar trial grátis](https://app.d-api.cloud/register)

## Continue lendo

-   [Por linguagemAPI de WhatsApp em Java](https://d-api.cloud/api-whatsapp-java)
-   [Por linguagemAPI de WhatsApp em C# e .NET](https://d-api.cloud/api-whatsapp-csharp)
-   [Por linguagemAPI de WhatsApp em Go](https://d-api.cloud/api-whatsapp-golang)
-   [Por linguagemAPI de WhatsApp em Node.js](https://d-api.cloud/api-whatsapp-nodejs)

## Teste a API de WhatsApp da D-API

Trial de 3 dias com acesso completo. Sem cartão, sem fidelidade.

[Começar trial grátis](https://app.d-api.cloud/register)[Ver documentação](https://docs.d-api.cloud/)
