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
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.
Continue lendo
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.
