CENTRO DE AYUDA

Webhooks

Recibe notificaciones HTTP automáticas en tu servidor cuando ocurren eventos importantes en WAzion.

1. Introducción

Los webhooks permiten que tu servidor reciba notificaciones automáticas cuando ocurren eventos específicos en WAzion. En lugar de consultar periódicamente si hay novedades, WAzion envía una petición HTTP POST a tu servidor cuando algo relevante sucede.

¿Cómo funciona?

  1. 1 Configuras una URL de tu servidor en el Dashboard de WAzion
  2. 2 Seleccionas qué eventos quieres recibir
  3. 3 Cuando ocurre un evento, WAzion envía un POST a tu URL
  4. 4 Tu servidor procesa el evento y responde con HTTP 200

Casos de uso

  • Sincronizar nuevos contactos con tu CRM
  • Crear leads automáticamente
  • Disparar automatizaciones en Zapier/Make
  • Notificar a sistemas internos

2. Eventos Disponibles

phone.detected

Principal

Se dispara cuando se detecta un nuevo número de teléfono en una conversación. Útil para capturar leads automáticamente.

Payload de ejemplo

{
  "event_type": "phone.detected",
  "phone": "+34612345678",
  "detected_at": "2025-01-15T14:30:00Z",
  "shop_id": 123,
  "conversation_hash": "a1b2c3d4e5f6..."
}
Campos del payload
event_type Tipo de evento
phone Número detectado (E.164)
detected_at Fecha/hora ISO 8601
shop_id ID de tu tienda en WAzion
conversation_hash Hash único de la conversación

followup.detected

Seguimiento

Se dispara cuando el sistema de Seguimiento Inteligente detecta una conversación con intención de compra. Incluye el mensaje de seguimiento sugerido y el resultado del análisis.

Payload de ejemplo

{
  "event": "followup.detected",
  "shop_id": 123,
  "phone": "+34612345678",
  "intent_level": "high",
  "product_mentioned": "Funda iPhone 15 negro",
  "customer_objection": "Preguntó por el precio de envío",
  "outcome": "abandoned",
  "suggested_message": "Hola, vi que te interesaba la funda...",
  "shopify_order_found": false,
  "crm_order_found": false,
  "attempt_number": 1,
  "timestamp": "2026-02-14T10:00:00Z"
}
Campos del payload
intent_level Nivel de intención: medium o high
product_mentioned Producto mencionado (puede ser null)
outcome Estado: abandoned, unclear, converted, support_only
suggested_message Mensaje de seguimiento generado por la IA
attempt_number Número de intento (1-3)

test

Evento de prueba que puedes disparar manualmente desde el Dashboard para verificar que tu endpoint funciona correctamente.

{
  "event_type": "test",
  "message": "This is a test webhook from WAzion",
  "timestamp": "2025-01-15T14:30:00+01:00",
  "shop_id": 123,
  "test": true
}

3. Headers HTTP

WAzion envía los siguientes headers en cada petición webhook:

Header Descripción Ejemplo
Content-Type Tipo de contenido application/json
User-Agent Identificador de WAzion WAzion-Webhooks/1.0
X-Webhook-ID ID único del webhook (formato wh_XXXXXXXX) wh_00012345
X-Webhook-Event Tipo de evento phone.detected
X-Webhook-Attempt Número de intento (1-6) 1
X-Webhook-Timestamp Unix timestamp de envío 1705329000
X-Webhook-Signature Firma HMAC-SHA256 hex (si hay secret) a1b2c3d4e5f6...

4. Firma HMAC-SHA256

Si configuras un webhook secret, WAzion firmará cada petición usando HMAC-SHA256. Esto te permite verificar que la petición realmente viene de WAzion.

Cómo se genera la firma

// 1. Concatenar timestamp + "." + payload JSON
signature_payload = timestamp + "." + payload_json

// 2. Calcular HMAC-SHA256 y convertir a hexadecimal
signature = HMAC-SHA256(signature_payload, webhook_secret).toHex()

// 3. El header X-Webhook-Signature contiene la firma directamente:
"a1b2c3d4e5f6789..."  // Sin prefijo, solo el hash hex

Verificación en PHP

<?php
function verificarFirmaWebhook($payload, $signatureRecibida, $secret, $timestamp) {
    // Verificar que el timestamp no sea muy antiguo (5 min)
    if (abs(time() - intval($timestamp)) > 300) {
        return false;
    }

    // Construir payload firmado
    $signedPayload = $timestamp . '.' . $payload;

    // Calcular firma esperada
    $expectedSignature = hash_hmac('sha256', $signedPayload, $secret);

    // Comparar de forma segura (timing-safe)
    return hash_equals($expectedSignature, $signatureRecibida);
}

// Uso:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$secret = 'tu-webhook-secret';

if (!verificarFirmaWebhook($payload, $signature, $secret, $timestamp)) {
    http_response_code(401);
    exit('Invalid signature');
}

Verificación en Node.js

const crypto = require('crypto');

function verificarFirmaWebhook(payload, signatureRecibida, secret, timestamp) {
    // Verificar que el timestamp no sea muy antiguo (5 min)
    const currentTime = Math.floor(Date.now() / 1000);
    if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
        return false;
    }

    // Construir payload firmado
    const signedPayload = `${timestamp}.${payload}`;

    // Calcular firma esperada
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(signedPayload)
        .digest('hex');

    // Comparar de forma segura (timing-safe)
    return crypto.timingSafeEqual(
        Buffer.from(signatureRecibida),
        Buffer.from(expectedSignature)
    );
}

Importante: También deberías verificar que el timestamp no sea muy antiguo (ej: máximo 5 minutos) para prevenir ataques de replay.

5. Especificaciones Técnicas

Timeouts

Timeout por petición 10 segundos
Máximo en cola 100 webhooks

Reintentos

Máximo de intentos 6
Estrategia Backoff exponencial

Calendario de reintentos

Intento 1
Inmediato
Intento 2
1 min
Intento 3
5 min
Intento 4
15 min
Intento 5
1 hora
Intento 6
4 horas

Después del intento 6, el webhook se marca como fallo permanente.

Respuestas aceptadas

200 OK 201 Created 202 Accepted 204 No Content

Cualquier código 2xx se considera éxito.

Códigos que activan reintento

408 Timeout 429 Rate Limit 5xx Server Error

Códigos sin reintento (fallo permanente)

400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found

Deduplicación automática

WAzion evita enviar el mismo número de teléfono dos veces. Se almacena un hash SHA256 de cada teléfono enviado. Si el teléfono ya fue notificado anteriormente, no se envía de nuevo.

Rate Limiting

Máximo 100 webhooks en cola por tienda. Si se supera el límite, los nuevos webhooks se descartan. El procesador envía máximo 50 webhooks por ejecución con 1 segundo de delay entre cada uno.

Configuración de conexión

Timeout total: 10s
Connect timeout: 5s
SSL verificado:
Redirecciones: No

Los webhooks NO siguen redirecciones. Tu endpoint debe responder directamente sin redireccionar.

6. Código de Ejemplo Completo

PHP
<?php
// webhook-handler.php

header('Content-Type: application/json');

// Configuración
$webhookSecret = 'tu-webhook-secret'; // Deja vacío si no usas firma

// Obtener datos
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$eventType = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';

// Verificar firma (si hay secret configurado)
if ($webhookSecret) {
    $expectedSignature = hash_hmac(
        'sha256',
        $timestamp . '.' . $payload,
        $webhookSecret
    );

    if (!hash_equals($expectedSignature, $signature)) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid signature']);
        exit;
    }

    // Verificar timestamp (máximo 5 minutos de antigüedad)
    if (abs(time() - intval($timestamp)) > 300) {
        http_response_code(401);
        echo json_encode(['error' => 'Timestamp too old']);
        exit;
    }
}

// Decodificar payload
$data = json_decode($payload, true);

// Procesar según tipo de evento
switch ($eventType) {
    case 'phone.detected':
        $phone = $data['phone'];
        $shopId = $data['shop_id'];

        // Guardar en tu base de datos, CRM, etc.
        guardarNuevoLead($phone, $shopId);

        echo json_encode(['status' => 'ok', 'message' => 'Lead saved']);
        break;

    case 'followup.detected':
        // Seguimiento Inteligente: cliente con intención detectada
        $phone = $data['phone'];
        $intentLevel = $data['intent_level']; // "high" o "medium"
        $product = $data['product_mentioned'] ?? null;
        $suggestedMsg = $data['suggested_message'] ?? null;

        // Ejemplo: crear tarea en tu CRM
        procesarSeguimiento($phone, $intentLevel, $product, $suggestedMsg);

        echo json_encode(['status' => 'ok', 'message' => 'Follow-up processed']);
        break;

    case 'test':
        // Evento de prueba
        echo json_encode(['status' => 'ok', 'message' => 'Test received']);
        break;

    default:
        echo json_encode(['status' => 'ok', 'message' => 'Event not handled']);
}

function guardarNuevoLead($phone, $shopId) {
    // Tu lógica aquí: guardar en BD, enviar a CRM, etc.
    error_log("Nuevo lead: $phone de shop $shopId");
}

function procesarSeguimiento($phone, $intentLevel, $product, $suggestedMsg) {
    // Tu lógica aquí: crear tarea, notificar al equipo, etc.
    error_log("Follow-up $intentLevel: $phone" . ($product ? " - $product" : ""));
}
Node.js / Express
const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = 'tu-webhook-secret'; // Deja vacío si no usas firma

// Middleware para raw body
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
    const payload = req.body.toString();
    const signature = req.headers['x-webhook-signature'] || '';
    const timestamp = req.headers['x-webhook-timestamp'] || '';
    const eventType = req.headers['x-webhook-event'] || '';

    // Verificar firma (si hay secret)
    if (WEBHOOK_SECRET) {
        const expectedSignature = crypto
            .createHmac('sha256', WEBHOOK_SECRET)
            .update(`${timestamp}.${payload}`)
            .digest('hex');

        const sigBuffer = Buffer.from(signature);
        const expectedBuffer = Buffer.from(expectedSignature);

        if (sigBuffer.length !== expectedBuffer.length ||
            !crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
            return res.status(401).json({ error: 'Invalid signature' });
        }

        // Verificar timestamp
        if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
            return res.status(401).json({ error: 'Timestamp too old' });
        }
    }

    const data = JSON.parse(payload);

    // Procesar evento
    switch (eventType) {
        case 'phone.detected':
            console.log(`Nuevo lead: ${data.phone} de shop ${data.shop_id}`);
            // Tu lógica aquí
            break;

        case 'followup.detected':
            console.log(`Follow-up ${data.intent_level}: ${data.phone}`);
            if (data.product_mentioned) console.log(`Producto: ${data.product_mentioned}`);
            // Crear tarea en tu CRM, notificar al equipo, etc.
            break;

        case 'test':
            console.log('Test webhook received');
            break;
    }

    res.json({ status: 'ok' });
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Historial de cambios

Sin cambios recientes en esta documentación.

Artículos relacionados

Asistente WAzion

Informacion comercial y soporte tecnico

Hola! Soy el asistente de WAzion. Puedo ayudarte con informacion sobre precios y planes, dudas tecnicas, configuracion, o cualquier pregunta sobre nuestro producto. Como puedo ayudarte?
Desarrollado con WAzion AI