Dev

Enviar WhatsApp com PHP e cURL: exemplo de API (Guzzle e Laravel)

Para enviar WhatsApp com PHP você faz um POST com cURL em /api/v1/{instanceId}/send/text, com os cabeçalhos x-api-key e x-api-secret e um JSON com number e text. Funciona em qualquer hospedagem com a extensão cURL. Abaixo: cURL puro, Guzzle, envio de arquivo, um job no Laravel e um receptor de webhook.

Publicado em · 3 min de leitura

Este artigo também está disponível em: English · Español

O que você precisa antes de começar

Conta no ZapZap, instância conectada por QR code, chave e segredo da API e o ID da instância. A autenticação usa x-api-key e x-api-secret. Em PHP, guarde as credenciais em .env ou em variáveis do servidor, fora do repositório.

Teste com cURL na linha de comando antes de passar para o PHP.

cURL

curl -X POST https://api.zapzapapi.com/api/v1/SEU_INSTANCE_ID/send/text \
  -H "Content-Type: application/json" \
  -H "x-api-key: SUA_API_KEY" \
  -H "x-api-secret: SEU_API_SECRET" \
  -d '{"number":"5511999999999","text":"Olá! Teste de envio pela API."}'

Como enviar uma mensagem de texto com PHP e cURL

A função abaixo usa a extensão cURL do PHP, define timeouts e devolve o status e o corpo. Use json_encode para montar o JSON: concatenar string à mão quebra com aspas e emojis.

Sempre confira o código HTTP, não só se a chamada retornou algo. Um 401 volta com corpo e não lança exceção sozinho.

PHP (cURL)

<?php
function zapzap_post(string $caminho, array $dados): array {
    $instancia = getenv('ZAPZAP_INSTANCE_ID');
    $ch = curl_init("https://api.zapzapapi.com/api/v1/$instancia$caminho");
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'x-api-key: ' . getenv('ZAPZAP_API_KEY'),
            'x-api-secret: ' . getenv('ZAPZAP_API_SECRET'),
        ],
        CURLOPT_POSTFIELDS     => json_encode($dados, JSON_UNESCAPED_UNICODE),
    ]);
    $corpo = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $erro = curl_error($ch);
    curl_close($ch);
    if ($corpo === false) {
        throw new RuntimeException("Falha de rede: $erro");
    }
    return ['status' => $status, 'corpo' => json_decode($corpo, true)];
}

$r = zapzap_post('/send/text', [
    'number' => '5511999999999',
    'text'   => 'Olá! Seu pedido saiu para entrega.',
]);
print_r($r);

Como enviar com Guzzle e mandar PDF ou imagem

Com composer require guzzlehttp/guzzle, o cliente reaproveita a conexão e já traz base_uri, cabeçalhos e timeout. Para mídia, use /send/media com type (image, video, audio, ptt, document, sticker e outros), file (URL pública), text (legenda) e docName.

Com http_errors em false você decide o que fazer com cada status, em vez de tratar exceções.

PHP (Guzzle)

<?php
use GuzzleHttp\Client;

$http = new Client([
    'base_uri' => 'https://api.zapzapapi.com/api/v1/' . getenv('ZAPZAP_INSTANCE_ID') . '/',
    'headers'  => [
        'x-api-key'    => getenv('ZAPZAP_API_KEY'),
        'x-api-secret' => getenv('ZAPZAP_API_SECRET'),
    ],
    'timeout'     => 30,
    'http_errors' => false,
]);

$r = $http->post('send/media', ['json' => [
    'number'  => '5511999999999',
    'type'    => 'document',
    'file'    => 'https://seusite.com/arquivos/proposta.pdf',
    'docName' => 'Proposta.pdf',
    'text'    => 'Segue a proposta.',
]]);
echo $r->getStatusCode(), ' ', $r->getBody();

Como enviar pelo Laravel com um job de fila

No Laravel, não envie WhatsApp dentro da requisição do usuário. Despache um job para a fila: se a API demorar ou falhar, a página continua rápida e o job repete sozinho. Coloque as credenciais em config/services.php, lidas do .env.

O método retry do Http facade cobre erros transitórios. O job abaixo falha de forma explícita em 4xx, para o Laravel não repetir um pedido que nunca vai funcionar.

PHP (Laravel)

<?php
namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Http;

class EnviarWhatsApp implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public int $tries = 4;
    public array $backoff = [10, 60, 300];

    public function __construct(public string $numero, public string $texto) {}

    public function handle(): void
    {
        $cfg = config('services.zapzap');
        $r = Http::withHeaders([
                'x-api-key'    => $cfg['key'],
                'x-api-secret' => $cfg['secret'],
            ])
            ->timeout(20)
            ->post("https://api.zapzapapi.com/api/v1/{$cfg['instance']}/send/text", [
                'number' => $this->numero,
                'text'   => $this->texto,
            ]);

        if ($r->clientError()) {
            $this->fail(new \RuntimeException('ZapZap ' . $r->status() . ' ' . $r->body()));
            return;
        }
        $r->throw(); // 5xx: o Laravel repete conforme o backoff
    }
}

// Uso: EnviarWhatsApp::dispatch('5511999999999', 'Seu pedido foi enviado.');

Como receber as respostas com um webhook em PHP

Configure a URL em configurar webhook e ligue o evento messages. Leia o corpo bruto com file_get_contents('php://input'), responda 200 logo e processe depois. A mensagem recebida está em data.message (text, chatid, fromMe); ignore fromMe verdadeiro.

No Laravel, a rota do webhook deve ficar fora do middleware de CSRF. Valide a assinatura X-ZapZap-Signature e trate reentregas conforme o guia de webhook em produção.

PHP (puro)

<?php
$raw = file_get_contents('php://input');
$evento = json_decode($raw, true);

http_response_code(200); // confirma primeiro
header('Content-Length: 0');
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request(); // libera a resposta e continua o trabalho
}

$msg = $evento['data']['message'] ?? null;
if (($evento['event'] ?? '') !== 'messages' || !$msg || !empty($msg['fromMe'])) {
    exit;
}
error_log('mensagem de ' . ($msg['chatid'] ?? '?') . ': ' . ($msg['text'] ?? ''));

Checklist antes de colocar em produção

Credenciais fora do repositório; timeouts de conexão e total; checagem do status HTTP; envio por fila, não dentro da requisição; retry só em erro transitório; assinatura do webhook validada; e envio só para quem aceitou receber.

Outras linguagens: Python, Node.js, Ruby, C#. Endpoints completos na referência da API.

Resumo rápido

  • cURL puro funciona em qualquer hospedagem PHP com a extensão cURL.
  • Use json_encode e confira sempre o código HTTP da resposta.
  • No Laravel, envie por job de fila com backoff e falhe direto em 4xx.
  • A rota do webhook fica fora do CSRF; responda 200 antes de processar.

Perguntas frequentes

Funciona em hospedagem compartilhada?

Funciona se a extensão cURL estiver ativa e a hospedagem permitir conexões de saída por HTTPS, o que é o padrão. Para receber webhook, o servidor precisa ter uma URL pública com HTTPS.

Preciso de composer?

Não para o exemplo com cURL puro. Guzzle e Laravel usam composer, mas são opcionais.

Como usar em WordPress?

Chame wp_remote_post com os mesmos cabeçalhos e o mesmo JSON, a partir de um hook do seu plugin ou de um webhook de formulário. Evite colocar a chave e o segredo no código do tema.

Usar a API não oficial pode levar a bloqueio do número?

Pode. Uma API não oficial viola os termos do WhatsApp e o número pode ser limitado ou bloqueado. Opt-in, ritmo humano, aquecimento e lista limpa reduzem o risco, sem garantia. Para canal homologado, use a API oficial da Meta.

Teste o PHP na sua instância

Crie a conta, conecte um número por QR code e rode o exemplo. R$15 por número ativo ao mês, sem cobrança por mensagem.