funnelchat20 / wapi-gateway
WAPI Gateway: librería Composer para integrar ZApi, UAZAPI, Funapi y Meta con webhooks automáticos
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.2
- laravel/framework: ^11.0|^12.0
- sentry/sentry-laravel: ^4.7
- spatie/laravel-data: ^3.11|^4.0
This package is auto-updated.
Last update: 2026-03-18 21:32:27 UTC
README
SDK puro para integrar proveedores de WhatsApp soportados por Funnelchat: Z-API, UAZAPI, Funapi (Whatsmeow Bridge) y Meta (WhatsApp Cloud), sin rutas ni middlewares. La app host consume métodos programáticos mediante un Facade y decide si expone endpoints propios.
⚠️ Estado: Esta librería está en desarrollo activo (v0.x.x). La API puede cambiar entre versiones menores. Se recomienda fijar versiones específicas en composer.json hasta v1.0.0.
Requisitos
- PHP ^8.2
- Laravel ^11.0 | ^12.0
Instalación
composer require funnelchat20/wapi-gateway
Publicar configuración
php artisan vendor:publish --tag=wapi-config
Configuración
Variables de entorno por proveedor
Global (Todos los proveedores)
# URL base para webhooks (requerida para recibir notificaciones) WEBHOOK_BASE_URL=https://tu-app.com
Z-API
ZAPI_TOKEN=tu_token_aqui ZAPI_CLIENT_TOKEN=tu_client_token ZAPI_TIMEOUT=29 ZAPI_MAX_ATTEMPTS=2 ZAPI_RETRY_DELAY=500
UAZAPI
UAZAPI_BASE_URL=https://funnelchat.uazapi.com UAZAPI_ADMIN_TOKEN=tu_admin_token UAZAPI_TIMEOUT=120 # Opcional: deshabilitar configuración automática de webhooks UAZAPI_AUTO_CONFIGURE_WEBHOOKS=true
Funapi (Whatsmeow Bridge)
FUNAPI_BASE_URL=https://api.funapi.example.com FUNAPI_TOKEN=tu_token FUNAPI_CLIENT_TOKEN=tu_client_token FUNAPI_TIMEOUT=29 FUNAPI_MAX_ATTEMPTS=2 FUNAPI_RETRY_DELAY=500
Meta (WhatsApp Cloud)
META_APP_ID=tu_app_id AWS_BUCKET_URL=https://tu-bucket.s3.amazonaws.com
Uso básico
use Funnelchat\WapiGateway\Facades\WapiGateway; use Funnelchat\WapiGateway\Enums\ProviderEnum; // Enviar mensaje de texto $response = WapiGateway::messages(ProviderEnum::ZApi) ->sendText($uid, $token, $phone, 'Hola mundo', [ 'delayMessage' => 0, 'delayTyping' => 0, 'retry' => true, // Habilita reintentos automáticos ]); if (isset($response['error'])) { // Manejar error } // Crear instancia $data = WapiGateway::instances(ProviderEnum::ZApi)->create($userId, $deviceId); // $data = ['uid' => '...', 'token' => '...'] o ['error' => '...'] // Obtener estado $status = WapiGateway::instances(ProviderEnum::ZApi)->status($uid, $token); // Incluye 'accountStatus' y 'qrCode' automáticamente
Compatibilidad de Proveedores
📨 Mensajería
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
sendText() |
✅ | ✅ | ✅ | ✅ |
sendFile() |
✅ | ✅ | ✅ | ✅ |
sendLocation() |
✅ | ✅ | ✅ | ✅ |
sendButtons() |
✅ | ✅ | ⚠️ | ✅ |
sendButtonLink() |
✅ | ✅ | ⚠️ | ✅ |
sendOptionList() |
✅ | ✅ | ⚠️ | ✅ |
sendPoll() |
✅ | ✅ | ⚠️ | ✅ |
sendLink() |
✅ | ✅ | ⚠️ | ✅ |
sendEvent() |
✅ | ✅ | ⚠️ | ✅ |
sendTemplate() |
✅ | ✅ | ⚠️ | ✅ |
pinMessage() |
✅ | ✅ | ⚠️ | ❌ |
🔧 Instancias
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
create() |
✅ | ✅ | ✅ | ✅ |
status() |
✅ | ✅ | ✅ | ✅ |
qrCode() |
✅ | ✅ | ✅ | ✅ |
logout() |
✅ | ✅ | ✅ | ✅ |
reboot() |
✅ | ✅ | ✅ | ✅ |
me() |
✅ | ✅ | ✅ | ✅ |
checkPhone() |
✅ | ✅ | ❌ | ✅ |
subscribe() |
✅ | ✅ | ✅ | ✅ |
unsubscribe() |
✅ | ✅ | ✅ | ✅ |
👥 Grupos
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
groups() |
✅ | ✅ | ✅ | ❌ |
group() |
✅ | ✅ | ✅ | ❌ |
createGroup() |
✅ | ✅ | ✅ | ❌ |
updateGroupName() |
✅ | ✅ | ✅ | ❌ |
updateGroupDescription() |
✅ | ✅ | ✅ | ❌ |
updateGroupSettings() |
✅ | ✅ | ✅ | ❌ |
updateGroupPhoto() |
✅ | ✅ | ✅ | ❌ |
addParticipants() |
✅ | ✅ | ✅ | ❌ |
addAdmins() |
✅ | ✅ | ✅ | ❌ |
removeParticipants() |
✅ | ✅ | ✅ | ❌ |
removeAdmins() |
✅ | ✅ | ✅ | ❌ |
leaveGroup() |
✅ | ✅ | ✅ | ❌ |
adGroups() |
✅ | ✅ | ✅ | ❌ |
groupInvitationMetadata() |
✅ | ✅ | ✅ | ❌ |
groupInvitationLink() |
✅ | ✅ | ✅ | ❌ |
lightGroupMetadata() |
✅ | ✅ | ✅ | ❌ |
groupMetadata() |
✅ | ✅ | ✅ | ❌ |
📇 Contactos
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
contact() |
✅ | ✅ | ✅ | ❌ |
contacts() |
✅ | ✅ | ✅ | ❌ |
sendContact() |
✅ | ✅ | ✅ | ✅ |
addContacts() |
✅ | ✅ | ✅ | ❌ |
🏘️ Comunidades
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
community() |
✅ | ✅ | ⚠️ | ❌ |
communities() |
✅ | ✅ | ⚠️ | ❌ |
communitiesMetadata() |
✅ | ✅ | ⚠️ | ❌ |
📢 Newsletters / Canales
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
createNewsletter() |
✅ | ✅ | ⚠️ | ❌ |
updateNewsletterName() |
✅ | ✅ | ⚠️ | ❌ |
updateNewsletterDescription() |
✅ | ✅ | ⚠️ | ❌ |
updateNewsletterPicture() |
✅ | ✅ | ⚠️ | ❌ |
newsletters() |
✅ | ✅ | ⚠️ | ❌ |
newsletterMetadata() |
✅ | ✅ | ⚠️ | ❌ |
📋 Cola de Mensajes
| Método | Z-API | UAZAPI | Funapi | Meta |
|---|---|---|---|---|
showQueue() |
✅ | ✅ | ✅ | ❌ |
queueCount() |
✅ | ✅ | ✅ | ❌ |
deleteQueueMessage() |
✅ | ✅ | ✅ | ❌ |
clearQueue() |
✅ | ✅ | ✅ | ❌ |
📝 Templates (Solo Meta)
| Método | Meta |
|---|---|
listTemplates() |
✅ |
getTemplate() |
✅ |
createTemplate() |
✅ |
updateTemplate() |
✅ |
deleteTemplate() |
✅ |
uploadHeaderHandle() |
✅ |
Leyenda:
- ✅ Completamente funcional
- ⚠️ En desarrollo (retorna error descriptivo)
- ❌ No soportado por el proveedor
Funcionalidades Avanzadas
Administración de Templates (Meta/WhatsApp Cloud)
use Funnelchat\WapiGateway\Facades\WapiGateway; use Funnelchat\WapiGateway\Enums\ProviderEnum; // Listar templates $templates = WapiGateway::templates(ProviderEnum::WhatsAppCloud) ->listTemplates($wabaId, $token, [ 'limit' => 20, 'after' => 'cursor123' // Para paginación ]); // Crear template $template = WapiGateway::templates(ProviderEnum::WhatsAppCloud) ->createTemplate($wabaId, $token, [ 'name' => 'welcome_message', 'language_code' => 'es', 'category' => 'MARKETING', 'header' => 'Bienvenido', 'body' => 'Hola {{1}}, gracias por contactarnos', 'footer' => 'Equipo de soporte', 'buttons' => [ ['type' => 'URL', 'text' => 'Visitar sitio', 'url' => 'https://example.com'] ] ]); // Eliminar template $result = WapiGateway::templates(ProviderEnum::WhatsAppCloud) ->deleteTemplate($wabaId, $token, 'welcome_message', $templateUid);
Eliminación Concurrente de Mensajes
$deleteRequests = [ ['messageId' => 'msg1', 'phone' => '123456789', 'owner' => true], ['messageId' => 'msg2', 'phone' => '987654321', 'owner' => true], // ... más mensajes ]; $results = WapiGateway::groups(ProviderEnum::ZApi) ->deleteMessagesConcurrently($uid, $token, $deleteRequests); // Performance: ~2s para 100 mensajes vs ~30s+ secuencial foreach ($results as $messageId => $result) { if (isset($result['error'])) { echo "Error eliminando {$messageId}: {$result['error']}"; } }
Procesamiento Asíncrono de Videos
// Automático para .mp4, .mov, .gif $response = WapiGateway::messages(ProviderEnum::ZApi) ->sendFile($uid, $token, $phone, 'https://example.com/video.mp4', [ 'caption' => 'Mi video', 'retry' => true, // 'async' => false // Desactivar si es necesario ]);
Retry Automático
$response = WapiGateway::messages(ProviderEnum::ZApi) ->sendText($uid, $token, $phone, 'Mensaje importante', [ 'retry' => true // Reintenta automáticamente en errores de conexión ]); // El SDK: // - Solo reintenta en ConnectionException // - NO reintenta en timeouts (evita duplicados) // - Configurable vía ZAPI_MAX_ATTEMPTS y ZAPI_RETRY_DELAY
Configuración Automática de Webhooks
El SDK configura webhooks automáticamente al crear instancias para recibir notificaciones en tiempo real de mensajes, conexión/desconexión, y otros eventos.
⚙️ Configuración
Variable requerida:
WEBHOOK_BASE_URL=https://tu-app.com
Si no defines WEBHOOK_BASE_URL, el SDK usará APP_URL como fallback. Si ninguna está configurada, no se configurarán webhooks automáticamente.
📡 Webhooks por Proveedor
Z-API
Configurados en el payload de creación de instancia:
/webhooks/zapi/received- Mensajes recibidos/webhooks/zapi/received-and-delivery- Mensajes y confirmaciones de entrega/webhooks/zapi/disconnected- Instancia desconectada/webhooks/zapi/connected- Instancia conectada/webhooks/zapi/message-status- Estados de mensajes enviados/webhooks/zapi/block- Bloqueos/desbloqueos
UAZAPI
Configurados vía POST /webhook después de crear la instancia:
/webhooks/uazapi/messages- Mensajes recibidos/webhooks/uazapi/messages_update- Actualizaciones de mensajes/webhooks/uazapi/connection- Cambios de conexión/webhooks/uazapi/messages- Eventos de grupos (comparte ruta con messages)
Opciones:
# Deshabilitar webhooks automáticos para UAZAPI UAZAPI_AUTO_CONFIGURE_WEBHOOKS=false
Funapi
Configurados en el payload de creación de instancia:
/webhooks/funapi/received- Mensajes recibidos/webhooks/funapi/received-and-delivery- Mensajes y confirmaciones de entrega/webhooks/funapi/disconnected- Instancia desconectada/webhooks/funapi/connected- Instancia conectada/webhooks/funapi/message-status- Estados de mensajes enviados/webhooks/funapi/block- Bloqueos/desbloqueos
💡 Ejemplo de Uso
// 1. Configurar .env // WEBHOOK_BASE_URL=https://mi-app.com // 2. Crear instancia (webhooks se configuran automáticamente) $instance = WapiGateway::instances(ProviderEnum::Uazapi)->create($userId, $deviceId); // Los webhooks ya están configurados en UAZAPI // 3. La app host debe tener rutas para recibir webhooks Route::post('/webhooks/uazapi/messages', [WebhookController::class, 'uazapiMessages']); Route::post('/webhooks/uazapi/connection', [WebhookController::class, 'uazapiConnection']); // etc...
🔍 Logging
El SDK registra automáticamente el éxito/error de la configuración de webhooks:
// Logs en UAZAPI Log::info('UAZAPI webhook configured successfully', [...]); Log::warning('UAZAPI webhook configuration failed', [...]); Log::warning('UAZAPI webhook configuration skipped: WEBHOOK_BASE_URL not configured', [...]);
Device Logging (Request/Response a DB)
El SDK puede loggear cada request HTTP a los proveedores en la tabla device_logs, incluyendo el payload enviado y la respuesta recibida. El logging está deshabilitado por defecto — solo se activa explícitamente en el proyecto que lo necesite.
Configuración
# Habilitar logging a DB (deshabilitado por defecto) WAPI_GATEWAY_LOGGING_ENABLED=true # Conexión de base de datos a usar (por defecto: mysql) WAPI_GATEWAY_DATABASE_CONNECTION=mysql
Importante: No habilites
WAPI_GATEWAY_LOGGING_ENABLED=truesin antes publicar y correr la migración. Conqueue: sync(común en desarrollo), el job fallará directamente si la tabla no existe.
Migración
La migración no se ejecuta automáticamente al instalar el paquete. Solo debe publicarse en el proyecto que será dueño de la tabla device_logs (actualmente user-api):
# Solo en user-api:
php artisan vendor:publish --tag=wapi-migrations
php artisan migrate
Los demás proyectos que usen el paquete (new-groups, sync-group-api, etc.) no necesitan publicar la migración — solo configurar WAPI_GATEWAY_DATABASE_CONNECTION apuntando a la misma base de datos donde user-api creó la tabla.
Estructura de la tabla device_logs
| Campo | Tipo | Descripción |
|---|---|---|
instance_uid |
string | UID de la instancia de WhatsApp |
provider |
string | Proveedor usado (zapi, uazapi, funapi, meta) |
method |
string | Método ejecutado (sendText, status, createGroup, etc.) |
url |
text | URL completa del request HTTP |
request_payload |
json | Body enviado al proveedor (ej: {"phone": "549...", "message": "Hola"}) |
response_body |
json | Respuesta del proveedor |
status_code |
smallint | Código HTTP de respuesta |
duration_ms |
int | Tiempo de respuesta en milisegundos |
is_error |
boolean | Si el request resultó en error |
sent_at |
timestamp | Fecha y hora real del request HTTP (distinto de created_at que es cuando se persistió el registro) |
Queue
Los logs se despachan vía el job StoreDeviceLogJob en la cola device-logs. Asegurate de tener un worker procesando esa cola:
php artisan queue:work --queue=device-logs
El dispatch del job está protegido con try-catch — si la infraestructura de cola falla (Redis caído, etc.), el error se loggea pero no interrumpe la operación de WhatsApp que lo originó.
Protección contra payloads grandes
Los payloads de request y response se truncan automáticamente si superan 200KB (para evitar exceder el límite de 256KB de SQS). Cuando esto ocurre, el campo almacena {"_truncated": true, "original_size": 350000}.
Notas
- Los métodos GET (
status,qrCode,me,contact, etc.) registranrequest_payloadcomo[]ya que no envían body. - Los métodos POST (
sendText,sendFile,createGroup,addContacts, etc.) registran el payload completo enviado al proveedor. - El logging es asíncrono y no impacta el tiempo de respuesta de las operaciones.
- El job usa 3 reintentos con backoff progresivo (5s, 15s, 30s) y timeout de 10s.
Sistema de Resources
El SDK transforma las respuestas de cada proveedor para mantener compatibilidad con WAPI original:
// Ejemplo: status() con fetch automático de QR $status = WapiGateway::instances(ProviderEnum::ZApi)->status($uid, $token); // Retorna: ['accountStatus' => 'authenticated', 'qrCode' => null] // o: ['accountStatus' => 'got qr code', 'qrCode' => 'data:image/png;base64,...'] // checkPhone() normalizado $result = WapiGateway::instances(ProviderEnum::ZApi)->checkPhone($uid, $token, $phone); // Retorna: ['result' => true] (no 'exists', sino 'result') // me() con campos adicionales $me = WapiGateway::instances(ProviderEnum::ZApi)->me($uid, $token); // Retorna: ['phone' => '...', 'name' => '...', 'avatar' => '...', 'locale' => 'es', 'isBusiness' => true]
Limitaciones de Funapi
Funapi (Whatsmeow Bridge) tiene las siguientes limitaciones conocidas:
❌ No Soportado
checkPhone()- No existe endpoint equivalente
⚠️ En Desarrollo
Los siguientes métodos retornan error descriptivo indicando que están en desarrollo:
sendButtons()sendButtonLink()sendOptionList()sendPoll()sendLink()sendEvent()sendTemplate()
✅ Totalmente Funcional
- Mensajería básica: texto, archivos, ubicación, contactos
- Gestión de instancias completa
- Operaciones de grupos completas
- Cola de mensajes completa
Manejo de Errores
Todos los métodos retornan arrays con:
- Éxito:
['messageId' => '...', 'status' => 'queued', ...] - Error:
['error' => 'mensaje descriptivo']
$response = WapiGateway::messages(ProviderEnum::ZApi)->sendText(...); if (isset($response['error'])) { // Error normalizado del proveedor Log::error('Error enviando mensaje', ['error' => $response['error']]); return response()->json(['error' => $response['error']], 409); } // Éxito return response()->json($response);
Integración en Controladores
use Funnelchat\WapiGateway\Facades\WapiGateway; use Funnelchat\WapiGateway\Enums\ProviderEnum; class WhatsAppController extends Controller { public function send(Request $request) { $provider = match($request->provider) { 'zapi' => ProviderEnum::ZApi, 'uazapi' => ProviderEnum::Uazapi, 'funapi' => ProviderEnum::Funapi, 'meta' => ProviderEnum::WhatsAppCloud, }; $response = WapiGateway::messages($provider) ->sendText( $request->uid, $request->header('token'), $request->phone, $request->message, ['retry' => true] ); return isset($response['error']) ? response()->json(['error' => $response['error']], 409) : response()->json($response); } }
Changelog
v0.3.0 - 2025-XX-XX (En desarrollo)
Added:
- ✨ Soporte completo para Newsletters/Canales:
createNewsletter(),updateNewsletterName(),updateNewsletterDescription(),updateNewsletterPicture(),newsletters(),newsletterMetadata() - ✨ Soporte para Comunidades:
community(),communities(),communitiesMetadata() - ✨ Sección de Contactos:
contact(),contacts(),sendContact(),addContacts() - ✨ Métodos de grupos:
adGroups(),groupInvitationMetadata(),groupInvitationLink(),lightGroupMetadata(),groupMetadata() - ✨ Método
pinMessage()en MessagesContract - ✨ Device Logging:
StoreDeviceLogJobregistra request payload y response en tabladevice_logsvía cola asíncrona - ✨ Campo
sent_atendevice_logspara capturar fecha/hora real del request HTTP - ✨ Migración publicable vía
--tag=wapi-migrations(solo para el proyecto dueño de la tabla) - ✨ Trait
LogsDeviceRequests— elimina ~160 líneas duplicadas entre los 4 clientes
Fixed:
- 🐛
request_payloadendevice_logsahora contiene el body HTTP real enviado al proveedor en todos los métodos POST (antes se guardaba[]) - 🐛
StoreDeviceLogJob::dispatch()envuelto en try-catch para no interrumpir operaciones de WhatsApp si la cola falla - 🐛 Payloads >200KB se truncan automáticamente para no exceder límite de SQS (256KB)
- 🐛
ZApiClient::create()ahora pasainstance_uidcorrectamente (antes pasaba string vacío) - 🐛 Variable de entorno
WAPI_GATEWAY_DATABASE_CONNECTIONconsistente entre config y README - 🐛
$backoffcorregido de[5, 15]a[5, 15, 30]para matchear$tries = 3 - 🐛 Métodos
sendFile,sendLocation, y newsletter updates en FunapiClient/ZApiClient ahora logean el payload del request
Changed:
- 🔧 Default de
logging_enabledcambiado detrueafalse(opt-in explícito) - 🔧 Migración no se auto-carga — solo se publica en el proyecto que la necesite (
user-api) - 🔧 Casts de
DeviceLogampliados:status_code => integer,duration_ms => integer
v0.2.0 - 2025-01-XX
Added:
- ✨ Configuración automática de webhooks al crear instancias
- ✨ Variable de entorno
WEBHOOK_BASE_URLpara configuración centralizada - ✨ Método privado
configureWebhooks()en UazapiClient con logging completo - ✨ Soporte para deshabilitar webhooks con
UAZAPI_AUTO_CONFIGURE_WEBHOOKS=false
Changed:
- 🔧 ZApiClient, UazapiClient y FunapiClient ahora usan
WEBHOOK_BASE_URLen lugar deAPP_URL - 🔧 Webhooks solo se configuran si
WEBHOOK_BASE_URLestá definida
v0.1.0 - 2025-01-07
Added:
- ✨ Sistema completo de Resources para transformación de respuestas
- ✨ Soporte para Funapi (Whatsmeow Bridge) - 36/44 métodos funcionales
- ✨ Fetch automático de QR code en método
status()cuando la instancia está desconectada - ✨ Normalización de respuestas entre proveedores (accountStatus, result, locale, etc.)
- ✨ 20 Resources implementados (10 Z-API + 10 UAZAPI)
- ✨ Administración completa de templates para Meta/WhatsApp Cloud
- ✨ Método
deleteMessagesConcurrently()para eliminación paralela de mensajes - ✨ Retry automático configurable en métodos de envío
- ✨ Procesamiento asíncrono automático para videos
- ✨ Timeouts configurables por proveedor
Fixed:
- 🐛 Corregidos endpoints de Funapi (
/send-imagey/send-documentsin sufijos) - 🐛 Métodos no soportados de Funapi ahora retornan errores descriptivos
Changed:
- 🔧 Timeout por defecto reducido de 120s a 29s (configurable)
- 🔧 Retry inteligente: no reintenta en timeouts, solo en ConnectionException
Performance:
- ⚡ Eliminación de 100 mensajes: ~2s con HTTP Pool vs ~30s+ secuencial
- ⚡ Reducción de timeouts en envíos de videos gracias a procesamiento async
Roadmap
- Completar endpoints faltantes de Funapi (botones, listas, encuestas)
- Tests automatizados para todos los proveedores
- Documentación de DTOs y respuestas por operación
- Soporte para webhooks (configuración automática implementada)
- v1.0.0 - Release estable cuando todos los proveedores estén 100% completos
Contribución
Esta es una librería privada de Funnelchat. Para reportar bugs o solicitar features, contacta a: soporte@funnelchat.io
Licencia
Privado (Funnelchat). Todos los derechos reservados.