Guia para avanzados
Patrones, webhooks, manejo de errores, rate limits y optimizaciones para llevar tu integracion a produccion.
Arquitectura recomendada
Para alta disponibilidad y observabilidad:
- Encolar localmente: cuando tu sistema genera muchos mensajes, encolalos en tu BD/Redis y manda a la API en lotes.
- Worker async: usa un cron o cola (Bull, Sidekiq, Celery) que procese cada N segundos, respetando los rate limits.
- Webhook receiver robusto: tu endpoint que recibe webhooks debe responder
200 OKrapido (< 1s) y procesar en background. - Dedupe: el campo
msg_ides unico. Guardalo siempre para evitar duplicados.
Webhooks firmados
Cuando configuras un webhook con secret, cada request llega con un header X-WAAPI-Signature con un HMAC-SHA256 del body:
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WAAPI_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Firma invalida');
}
// OK, procesar evento
Eventos disponibles
message.received— el destinatario respondio (o alguien le escribio a tu numero).message.status— cambio el estado de un mensaje tuyo (enviado -> entregado -> leido -> error).session.status— tu celular se conecto/desconecto de WhatsApp.
Rate limits y backoff
Por default, con account_protection: false en la sesion del servidor, puedes mandar hasta 256 mensajes/min por celular. Si excedes, recibes un 429 con retry_after en el body.
Patron de retry con exponential backoff
function enviar_con_retry($params, $max_intentos = 5) {
for ($i = 0; $i < $max_intentos; $i++) {
$response = enviar($params);
if ($response['ok']) return $response;
if ($response['status'] === 429) {
$wait = $response['retry_after'] ?? 5;
sleep($wait);
} elseif ($response['status'] >= 500) {
sleep(pow(2, $i)); // 1, 2, 4, 8, 16 segundos
} else {
return $response; // 4xx no recuperable
}
}
return ['ok' => false, 'error' => 'max_intentos_exceeded'];
}
Idempotencia
Si reintentas un mensaje con el mismo msg_id, el sistema lo detecta y no lo duplica. Esto es util cuando tu worker crashea a mitad de envio.
Modo directo vs cola
Los 11 endpoints de envio (texto + 5 medios + 5 base64) soportan 2 modos:
Cola humanizada (default, recomendado para el 90% de los casos)
Por default, los mensajes se encolan y un worker los envia en los proximos segundos. Esto es lo que parece mas humano a ojos de WhatsApp: el trafico se distribuye en el tiempo en lugar de llegar todo junto.
Ventajas:
- Anti-baneo automatico: distribuye 1000 mensajes en lugar de mandarlos de golpe.
- Tolerancia a fallos: si el servidor se cae 30 segundos, los mensajes siguen encolados y se envian al rato.
- Backoff automatico: el worker reintenta con exponential backoff (60s, 120s, 240s, 480s, 960s) en errores 5xx.
- Manejo de rate limit: si el servidor responde 429, el worker respeta el
retry_aftery reintenta despues.
Modo directo (directo=1, para tiempo real)
Si necesitas envio instantaneo, agrega "directo": 1 al body. La API llama a WhatsApp al instante y registra el resultado como enviado en tu historial.
Casos de uso:
- Codigos 2FA / OTP: el usuario espera el codigo en pantalla, no puede esperar 5 segundos.
- Confirmaciones de pedido en tiempo real: tu frontend confirma y al instante le llega el WhatsApp al cliente.
- Chatbots interactivos: el usuario espera respuesta inmediata.
- Alertas criticas: caida del servidor, fraude detectado, etc.
NO uses este modo para:
- Notificaciones batch (mejor la cola).
- Marketing (mejor la cola para que se envien en horario permitido).
- Cualquier caso donde puedas esperar 5 segundos.
Comportamiento en errores:
- HTTP 200: WhatsApp acepto el mensaje. Modo:
directo.servidor_msg_iddevuelto. - HTTP 429: rate limit del servidor. NO se encola. Tu codigo debe esperar
retry_aftersegundos y reintentar manualmente. - HTTP 502/903: error de red o 5xx del servidor. Se registra como
erroren la BD (no se pierde). Tu codigo debe decidir si reintentar o notificar al usuario. - HTTP 5xx por panel no disponible: el panel de control no responde. La API intenta fallback a cola (modo automatico).
Ejemplo: sistema 2FA
import requests, random
def enviar_otp(destinatario, codigo):
return requests.post(
'https://api.multimensajes.com/api/mensaje_enviar_texto.php',
headers={'Authorization': 'Bearer WA_8_a1b2c3d4e5f6...'},
json={
'destinatario': destinatario,
'mensaje': f'Tu codigo de verificacion es: {codigo}. Valido por 5 min.',
'directo': 1, # envio instantaneo
},
timeout=10,
).json()
# Generar codigo aleatorio unico
codigo = str(random.randint(100000, 999999))
resp = enviar_otp('5218711281619', codigo)
if resp['ok']:
print(f"Enviado en modo {resp['modo']} con id {resp['msg_id']}")
else:
print(f"Error: {resp['error']}")
Ejemplo: envio masivo (cola, 1 llamada por mensaje)
import requests
import time
clientes = [
{'tel': '5218711281619', 'nombre': 'Juan', 'pedido': '004827'},
{'tel': '5218711281619', 'nombre': 'Maria', 'pedido': '004828'},
# ...
]
for cli in clientes:
# Cada mensaje es UNICO (personalizado)
mensaje = f"Hola {cli['nombre']}, tu pedido {cli['pedido']} esta listo. Gracias por tu compra."
requests.post(
'https://api.multimensajes.com/api/mensaje_enviar_texto.php',
headers={'Authorization': 'Bearer WA_8_a1b2c3d4e5f6...'},
json={
'destinatario': cli['tel'],
'mensaje': mensaje,
# NO 'directo' = cola humanizada
},
)
time.sleep(0.1) # 10 msg/seg en el front, la cola los distribuye a 1 cada 5s en el back
Envio de archivos pesados
Tienes 2 opciones para cada tipo (imagen, documento, audio, video, sticker):
Opcion A: Por URL publica
La URL del archivo debe cumplir:
- Publica: accesible sin login.
- HTTPS: requerido (WhatsApp no acepta HTTP para adjuntos).
- Tamano max: imagen 5MB, audio 16MB, video 16MB, documento 100MB, sticker 100KB.
- Content-Type correcto: el servidor deduce del Content-Type, asi que asegurate que tu CDN lo mande bien.
Opcion B: Por base64 (recomendado para contenido dinamico)
Los endpoints con sufijo _base64 aceptan el archivo directamente codificado. El sistema lo guarda temporalmente (24h) en /storage/uploads/, lo sube a WhatsApp, y luego un cron lo limpia.
Ventajas sobre URL:
- No necesitas hospedar el archivo en tu server.
- Sirve para contenido generado dinamicamente (imagenes de QR, PDFs personalizados, etc.).
- El archivo se elimina automaticamente tras 24h (cero residuos).
Limites base64: imagen 16MB, documento 50MB, audio 16MB, video 50MB, sticker 1MB.
Acepta data URI (data:image/png;base64,iVBOR...) o base64 puro.
Ejemplo: enviar una imagen generada con PIL a partir de un texto:
import base64, requests
from PIL import Image, ImageDraw
# Generar imagen en memoria
img = Image.new('RGB', (800, 400), color=(99, 102, 241))
d = ImageDraw.Draw(img)
d.text((50, 180), 'Hola desde MultiMensajes', fill='white')
buf = __import__('io').BytesIO()
img.save(buf, format='PNG')
# Convertir a base64 y enviar
img_b64 = base64.b64encode(buf.getvalue()).decode()
r = requests.post(
'https://api.multimensajes.com/api/mensaje_enviar_imagen_base64.php',
headers={'Authorization': 'Bearer WA_8_a1b2c3d4e5f6...'},
json={
'destinatario': '5218711281619',
'imageBase64': img_b64,
'mime': 'image/png',
'caption': 'Imagen generada al vuelo',
},
timeout=30,
)
print(r.json())
Validacion de numeros
Todos los numeros deben estar en formato internacional sin el signo +:
- Mexico:
5218711281619(52 + 1 + 871 + 1281619) - Espana:
34612345678 - Argentina:
5491112345678
El sistema rechaza numeros que no tengan al menos 10 digitos.
Monitoreo
Para integrar con tus dashboards:
- Endpoint
saldo_consultarpara alertas de saldo bajo. - Endpoint
historial_listarcon filtroestado=errorpara alertar sobre fallos. - Webhook
message.statusconestado=errorpara alertas en tiempo real. - Logs del servidor:
/var/log/plesk-php83-error.logen Plesk, o lo que configures.
Seguridad
- Nunca pongas la API key en el frontend (JS expuesto al usuario). Siempre desde tu backend.
- Rota las API keys periodicamente (boton en el panel admin).
- Usa HTTPS para tu webhook receiver y valida la firma.
- Bloquea IPs sospechosas con tu WAF / fail2ban.
SDKs y librerias
La API es REST pura — no necesitas un SDK. Pero si quieres generar uno para tu lenguaje favorito, importa el OpenAPI 3.0 en openapi-generator o Swagger Codegen.
Casos de uso comunes
- Notificaciones transaccionales: confirma pedidos, envia codigos de verificacion, recordatorios de citas.
- Marketing: campanas, cupones, ofertas personalizadas (con horario permitido).
- Soporte: bots de primera linea que escalan a humano.
- CRM / Seguimiento: recordatorios automaticos de pagos, renovaciones, encuestas post-venta.
Proteccion anti-duplicado y anti-baneo (detalle)
La API implementa 3 capas de proteccion para mantener tu cuenta sana:
- Cola humanizada (capa 1): default. Distribuye el trafico en el tiempo.
- Anti-duplicado con cache en memoria (capa 2): rechaza el mismo mensaje 3 veces en 2 minutos (bloqueo 5 min).
- Rate limit del servidor (capa 3): hasta 256 msg/min por sesion. Si excedes, 429 con
retry_after.
Como se calcula el hash de duplicado
El hash es diferente por tipo de mensaje e incluye destinatario + celular. Asi un cliente NO puede afectar a otro.
texto:md5('texto:' . destinatario . '|' . mensaje)imagen:md5('imagen:' . destinatario . '|' . url . '|' . caption)documento:md5('documento:' . destinatario . '|' . url . '|' . filename)audio:md5('audio:' . destinatario . '|' . url)video:md5('video:' . destinatario . '|' . url . '|' . caption)sticker:md5('sticker:' . destinatario . '|' . url)base64:md5('kind:base64:' . sha256(binario))
Si te banean igual (queja formal a WhatsApp)
Si tu cuenta de WhatsApp fue suspendida por spam (raro si respetas las reglas), puedes apelar en whatsapp.com/contact. Adjunta pruebas de que tus destinatarios dieron opt-in.