laravel-whatsapp-cloud maintained by callcocam
Laravel WhatsApp Cloud
Núcleo reutilizável da WhatsApp Cloud API (Meta) para Laravel: envio de templates / texto de sessão / interativo por-tenant, webhook com assinatura e eventos, e gestão de templates via Artisan.
Extraído do núcleo Meta-only do Coordena. O pacote cuida do transporte + protocolo + ciclo de vida de template; o app cuida da regra de negócio + gatilhos + textos.
- Envio por intenção:
sendTemplate/sendSessionText/sendInteractive. - Multi-tenant: credenciais resolvidas por contrato (
WhatsApp::for($tenant)), com fallback pras credenciais default do config (dev/single-tenant). - Webhook GET (hub-challenge) + POST (assinatura
X-Hub-Signature-256), disparando eventos — sem regra de negócio dentro do pacote. - Templates: registry (config + runtime) e comandos
whatsapp:template:*. - Erros terminais da Meta mapeados (
isTemporaryRestriction()) pra você não re-tentar o que não adianta (janela de 24h fechada, template pausado, etc.).
Requisitos
- PHP
^8.3 - Laravel
^11.0 || ^12.0 || ^13.0
Instalação
Repositório Git privado, consumido via Composer:
// composer.json do app
"repositories": [
{ "type": "vcs", "url": "git@github.com:callcocam/laravel-whatsapp-cloud.git" }
]
composer require callcocam/laravel-whatsapp-cloud
php artisan whatsapp:install # publica config + migration e imprime o checklist
php artisan migrate
Onboarding de um projeto novo (6 passos)
composer require callcocam/laravel-whatsapp-cloud(com orepositories: vcs).php artisan whatsapp:install&&php artisan migrate..env:WHATSAPP_CLOUD_APP_SECRET,WHATSAPP_CLOUD_VERIFY_TOKEN,WHATSAPP_CLOUD_GRAPH_VERSION. Cadastre o número — use o model defaultWhatsAppNumberou implementeWhatsAppCredentialsno seu model + binde um resolver (veja Credenciais).- Declare os templates em
config/whatsapp-cloud.phpe crie-os na Meta comphp artisan whatsapp:template:create <nome>. - (Opcional) Ouça
WhatsAppMessageReceived/WhatsAppStatusReceived. - Envie:
WhatsApp::for($tenant)->sendTemplate('chave', [...params]);.
Uso
Enviar
use Callcocam\WhatsAppCloud\Facades\WhatsApp;
use Callcocam\WhatsAppCloud\Messages\InteractiveMessage;
use Callcocam\WhatsAppCloud\Messages\TemplateMessage;
// Template aprovado (única forma fora da janela de 24h):
WhatsApp::for($team)->sendTemplate('5548999999999', TemplateMessage::make('assignment', [
'name' => 'Maria', 'event' => 'Congresso', 'url' => 'https://app.test/x',
]));
// Texto livre (só dentro da janela de 24h):
WhatsApp::for($team)->sendSessionText('5548999999999', 'Recebido, obrigado!');
// Pergunta com opções (lista interativa da Meta):
WhatsApp::for($team)->sendInteractive('5548999999999',
InteractiveMessage::multiChoice('Quando você pode?', ['Sexta à noite', 'Sábado de manhã']));
WhatsApp::for() sem argumento usa as credenciais default do config (dev /
single-tenant). Prefira injetar WhatsAppManager a usar a Facade em código
testável.
Tratar erros da Meta
use Callcocam\WhatsAppCloud\Exceptions\WhatsAppException;
try {
WhatsApp::for($team)->sendTemplate(...);
} catch (WhatsAppException $e) {
if ($e->isTemporaryRestriction()) {
// Terminal (janela fechada, template pausado…): logue e NÃO re-tente.
}
// Senão: deixe a fila re-tentar (rate limit, rede).
}
Templates
Declare cada chave de mensagem do app → template aprovado na Meta:
// config/whatsapp-cloud.php
'templates' => [
'assignment' => [
'name' => 'coordena_assignment',
'language' => 'pt_BR',
'category' => 'utility',
'params' => ['name', 'event', 'url'], // ordem de {{1}}, {{2}}, {{3}}
],
],
Ou em runtime:
use Callcocam\WhatsAppCloud\Templates\MetaTemplate;
WhatsApp::registerTemplate('assignment', new MetaTemplate('coordena_assignment', 'pt_BR', 'utility', ['name', 'event', 'url']));
Credenciais (multi-tenant)
Implemente o contrato no seu model (ou use a trait) e binde um resolver:
use Callcocam\WhatsAppCloud\Contracts\WhatsAppCredentials;
use Callcocam\WhatsAppCloud\Support\HasWhatsAppCredentials;
class TeamWhatsappConnection extends Model implements WhatsAppCredentials
{
use HasWhatsAppCredentials; // lê phone_number_id, cloud_access_token, waba_id
}
// AppServiceProvider::register()
use Callcocam\WhatsAppCloud\Contracts\WhatsAppCredentialsResolver;
$this->app->bind(WhatsAppCredentialsResolver::class, fn () => new class implements WhatsAppCredentialsResolver {
public function resolve(mixed $context): ?WhatsAppCredentials
{
return $context instanceof \App\Models\Team ? $context->whatsappConnection : null;
}
});
Projeto novo que não quer escrever resolver: guarde os números no model default
WhatsAppNumber e binde o ModelCredentialsResolver (busca por key).
Webhook
O provider registra GET|POST {prefix} (default webhooks/whatsapp/cloud, no
grupo api — sem CSRF). Aponte o webhook da Meta pra essa URL. Para registrar
você mesmo, ponha whatsapp-cloud.webhook.enabled = false e mire o
WebhookController.
// Um listener no app:
use Callcocam\WhatsAppCloud\Events\WhatsAppStatusReceived;
Event::listen(function (WhatsAppStatusReceived $event) {
logger()->info('status', ['id' => $event->status['id'], 'status' => $event->status['status']]);
});
Eventos: WhatsAppMessageReceived, WhatsAppStatusReceived,
WhatsAppWebhookVerified.
Comandos Artisan
php artisan whatsapp:template:list # lista templates da WABA + status
php artisan whatsapp:template:get <nome> # detalha um template
php artisan whatsapp:template:create <nome> # cria na Meta a partir de um arquivo de definição
php artisan whatsapp:template:send <nome> <to> ... # envia um template aprovado
Todos aceitam --tenant= (contexto pro resolver). O create lê
config('whatsapp-cloud.definitions_path')/<nome>.php (ou --path=), um arquivo
que retorna TemplateBuilder::...->toArray().
Configuração
Veja config/whatsapp-cloud.php: graph_version,
app_secret, verify_token, default (creds dev), webhook, model,
templates, definitions_path.
Testes
composer test # pint --test + phpstan + pest
Segurança
Nunca versione tokens. cloud_access_token fica encrypted no model; segredos
(app_secret, verify_token, tokens) só via .env.
Licença
MIT.