painelzap / sdk
SDK oficial do painelzap: envio de mensagens e midia pelo WhatsApp, grupos, validacao de numero e verificacao de webhooks. Com integracao para Laravel.
Requires
- php: ^8.0.2
- guzzlehttp/guzzle: ^7.2
Requires (Dev)
- phpunit/phpunit: ^9.5
Suggests
- illuminate/support: Para usar a facade Painelzap e o middleware painelzap.webhook no Laravel.
README
SDK oficial do painelzap para PHP: envio de mensagens e mídia pelo WhatsApp, grupos, validação de número e verificação de webhooks. Com integração pronta para Laravel.
composer require painelzap/sdk
Requer PHP 8.0.2+.
Uso direto (qualquer projeto PHP)
Pegue a appkey em Apps → Integração e a authkey no seu perfil.
use Painelzap\Painelzap; $zap = new Painelzap([ 'authkey' => getenv('PAINELZAP_AUTHKEY'), 'appkey' => getenv('PAINELZAP_APPKEY'), ]); $zap->sendMessage('5531988888888', 'Olá! Sua entrega chega hoje.');
Laravel
O provider e a facade são descobertos sozinhos. Publique a configuração:
php artisan vendor:publish --tag=painelzap-config
E preencha o .env:
PAINELZAP_AUTHKEY=... PAINELZAP_APPKEY=... PAINELZAP_WEBHOOK_SECRET=...
use Painelzap\Laravel\PainelzapFacade as Painelzap; Painelzap::sendMessage('5531988888888', 'Olá!');
Ou injetando, se preferir testar com mock:
public function __construct(private \Painelzap\Painelzap $zap) {}
Mensagens
$zap->sendMessage('5531988888888', 'Olá!'); $zap->sendTemplate('5531988888888', 'uuid-do-template', [ 'nome' => 'Maria', 'pedido' => '1234', ]);
Mídia
Por URL pública (precisa ser http/https e resolver para um IP público):
$zap->sendMediaUrl('5531988888888', 'https://exemplo.com/nota.pdf', 'Segue sua nota fiscal');
Por upload:
$zap->sendMediaFile('5531988888888', storage_path('app/foto.jpg'), 'Olha a foto');
Áudio como mensagem de voz (com a onda sonora, não anexo). O WhatsApp espera ogg/opus — outros codecs podem não tocar em todos os aparelhos:
$zap->sendMediaUrl('5531988888888', 'https://exemplo.com/recado.ogg', ptt: true);
O tipo vem da extensão; passe mediaType (image, video, audio, document) para forçar.
Confirmar que a mensagem chegou
Todo envio devolve message_id. Guarde-o e acompanhe o evento message.status:
$r = $zap->sendMessage('5531988888888', 'Olá!'); $id = $r['message_id'];
sentnão é entrega. Significa apenas que o servidor do WhatsApp aceitou. Entrega no aparelho édelivered. Mensagem parada emsentpor muito tempo não chegou.A progressão é
sent→delivered→read(eplayedem áudio);errorindica falha.
Histórico da conversa
$mensagens = $zap->history('5531988888888', 50); foreach ($mensagens as $m) { echo $m['from_me'] ? 'eu: ' : 'ele: ', $m['message'], ' [', $m['status'] ?? '-', "]\n"; }
Vem das mais recentes para as mais antigas, incluindo o que você enviou (com o status de entrega; nas recebidas o status é null). Para paginar, passe before com o sent_at da última linha — aceita string ISO ou DateTimeInterface:
$anteriores = $zap->history('5531988888888', 50, end($mensagens)['sent_at']);
O histórico guarda os últimos 30 dias e começa a partir de quando o recurso foi ativado. O arquivo da mídia não fica guardado (só o tipo e o nome); para receber a mídia, use o webhook.
Grupos
$grupos = $zap->groups(); $grupo = $zap->group($grupos[0]['id']); echo $grupo['name'], count($grupo['participants']); $zap->sendGroupMessage($grupos[0]['id'], 'Bom dia, time!');
Sobre os participantes: o WhatsApp migrou os contatos para LID, então
$participante['id']vem como...@lid, que não é telefone. O camponumbertraz o telefone quando o WhatsApp o expõe, e vemnullquando não. Useidcomo chave estável.
Validar número
Não envia mensagem e não consome a cota do plano:
if (! $zap->numberExists('5531988888888')) { return; } // ou, com o retorno completo ['exists' => $existe, 'jid' => $jid] = $zap->checkNumber('5531988888888');
Webhooks
Configure o endereço em Dispositivos → editar dispositivo → Webhook. Cada entrega vem assinada no header X-Painelzap-Signature.
No Laravel, use o middleware — ele recusa a requisição antes de chegar no seu controller:
Route::post('/webhook', [WebhookController::class, 'handle']) ->middleware('painelzap.webhook');
public function handle(Request $request) { $evento = $request->json()->all(); // Responda rápido: fora da faixa 2xx a entrega é retentada // 5 vezes (10s, 1min, 5min, 15min). if ($evento['event'] === 'message.received' && ! empty($evento['data']['media']['url'])) { // A mídia fica disponível por 3 dias — baixe e guarde do seu lado. BaixarMidia::dispatch($evento['data']['media']['url']); } return response()->noContent(); }
Fora do Laravel, ou para verificar na mão:
use Painelzap\Webhooks; $corpo = file_get_contents('php://input'); $assinatura = $_SERVER['HTTP_X_PAINELZAP_SIGNATURE'] ?? null; // Lança se não conferir: $evento = Webhooks::parse($corpo, $assinatura, $segredo); // Ou, se preferir decidir você mesmo: if (! Webhooks::verify($corpo, $assinatura, $segredo)) { http_response_code(401); exit; }
Use o corpo cru. Se você re-serializar o array já decodificado, os bytes mudam e a assinatura nunca bate — por isso o middleware usa $request->getContent().
Eventos
event |
Quando dispara |
|---|---|
message.received |
mensagem recebida na conversa individual |
message.group |
mensagem em grupo — group_id e author preenchidos |
message.status |
mudou o status do que você enviou — data traz message_id, chat_jid e status |
session.status |
dispositivo conectou ou caiu (data.status) |
webhook.test |
botão Send test event da tela do dispositivo |
data.type é text, image, audio, video, document ou sticker. Mídia sem legenda chega com message: null.
Erros
Toda falha vira PainelzapException, com a mensagem que a API devolveu:
use Painelzap\PainelzapException; try { $zap->sendMessage('5531988888888', 'Olá'); } catch (PainelzapException $e) { if ($e->naoAutorizado()) { /* chave inválida, app de outra conta, ou cota estourada */ } if ($e->invalido()) { /* parâmetro faltando — veja $e->body() */ } report($e); }
Testes
composer install
composer test
Licença
MIT