HELP CENTER

Webhooks

Receive automatic HTTP notifications on your server when important events occur in WAzion.

1. Introduction

Webhooks allow your server to receive automatic notifications when specific events occur in WAzion. Instead of periodically checking for updates, WAzion sends an HTTP POST request to your server when something relevant happens.

How does it work?

  1. 1 You configure a URL of your server in the WAzion Dashboard
  2. 2 You select which events you want to receive
  3. 3 When an event occurs, WAzion sends a POST to your URL
  4. 4 Your server processes the event and responds with HTTP 200

Use cases

  • Synchronize new contacts with your CRM
  • Automatically generate leads
  • Trigger automations in Zapier/Make
  • Notify internal systems

2. Available Events

phone.detected

Main

It triggers when a new phone number is detected in a conversation. Useful for capturing leads automatically.

Sample payload

{
  "event_type": "phone.detected",
  "phone": "+34612345678",
  "detected_at": "2025-01-15T14:30:00Z",
  "shop_id": 123,
  "conversation_hash": "a1b2c3d4e5f6..."
}
Payload fields
event_type Type of event
phone Detected number (E.164)
detected_at ISO 8601 Date/Time
shop_id Your store ID in WAzion
conversation_hash Unique conversation hash

followup.detected

Follow-up

It is triggered when the Smart Tracking system detects a conversation with purchase intent. It includes the suggested follow-up message and the result of the analysis.

Sample payload

{
  "event": "followup.detected",
  "event_type": "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"
}
Payload fields
intent_level Level of intention: medium or high
product_mentioned Mentioned product (may be null)
outcome State: abandoned, unclear, converted, support_only
suggested_message Follow-up message generated by AI
attempt_number Attempt number (1-3)

followup.replied

Follow-up

It is triggered when a customer responds to the follow-up message sent by Smart Follow-up.

Sample payload

{
  "event": "followup.replied",
  "event_type": "followup.replied",
  "phone": "+34612345678",
  "intent_level": "high",
  "product_mentioned": "Funda iPhone 15 negro",
  "attempt_number": 1,
  "followup_message": "Hola, vi que te interesaba la funda...",
  "days_to_reply": 2.5
}
Payload fields
intent_level Level of intention: medium or high
product_mentioned Mentioned product (may be null)
followup_message Follow-up message sent to the client
days_to_reply Days elapsed from the follow-up to the response

followup.converted

Follow-up

It triggers when a customer makes a purchase after receiving a follow-up message. The purchase is detected via Shopify, WooCommerce, PrestaShop, VTEX, or your CRM verification endpoint.

Sample payload

{
  "event": "followup.converted",
  "event_type": "followup.converted",
  "phone": "+34612345678",
  "intent_level": "high",
  "product_mentioned": "Funda iPhone 15 negro",
  "attempt_number": 1,
  "followup_message": "Hola, vi que te interesaba la funda...",
  "days_to_reply": 3.0
}
Payload fields
intent_level Level of intention: medium or high
product_mentioned Mentioned product (may be null)
followup_message Follow-up message sent to the client
days_to_reply Days elapsed from follow-up to purchase

plugin_chat.session_closed

Web Chat

It is triggered when a web chat widget session ends. It includes an AI-generated summary, the complete message history, and the customer satisfaction score.

Sample payload

{
  "event_type": "plugin_chat.session_closed",
  "shop_id": 123,
  "session_id": "chat_abc123",
  "ai_summary": "Customer asked about shipping times and return policy. Satisfied with the answers provided.",
  "messages": [
    { "role": "user", "content": "How long does shipping take?", "timestamp": "2026-01-15T14:30:00Z" },
    { "role": "assistant", "content": "Shipping usually takes 3-5 business days.", "timestamp": "2026-01-15T14:30:05Z" }
  ],
  "satisfaction_score": 4.5,
  "closed_at": "2026-01-15T14:35:00Z"
}
Payload fields
event_type Type of event
shop_id Your store ID in WAzion
session_id Unique identifier of the chat session
ai_summary AI-generated conversation summary
messages Complete message history (role, content, timestamp)
satisfaction_score Customer satisfaction rating (1-5)
closed_at ISO 8601 date/time of session closure

test

Test event that you can manually trigger from the Dashboard to verify that your endpoint works correctly.

{
  "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. HTTP Headers

WAzion sends the following headers in each webhook request:

Header Description Example
Content-Type Content type application/json
User-Agent WAzion Identifier WAzion-Webhooks/1.0
X-Webhook-ID Unique webhook ID (format wh_XXXXXXXX) wh_00012345
X-Webhook-Event Type of event phone.detected
X-Webhook-Attempt Attempt number (1-6) 1
X-Webhook-Timestamp Unix timestamp of sending 1705329000
X-Webhook-Signature HMAC-SHA256 hex signature (if there is a secret) a1b2c3d4e5f6...

4. HMAC-SHA256 Signature

If you set up a webhook secret, WAzion will sign each request using HMAC-SHA256. This allows you to verify that the request really comes from WAzion.

How the signature is generated

// 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

Verification in 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');
}

Verification in 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)
    );
}

Important: You should also verify that the timestamp is not too old (e.g., a maximum of 5 minutes) to prevent replay attacks.

5. Technical Specifications

Timeouts

Timeout by request 180 seconds
Maximum in queue 100 webhooks

Retries

Maximum number of attempts 6
Strategy Exponential backoff

Retry schedule

Attempt 1
Immediate
Attempt 2
1 min
Attempt 3
5 min
Attempt 4
15 min
Attempt 5
1 hora
Attempt 6
4 horas

After attempt 6, the webhook is marked as a permanent failure.

Accepted answers

200 OK 201 Created 202 Accepted 204 No Content

Any 2xx code is considered a success.

Codes that trigger retry

408 Timeout 429 Rate Limit 5xx Server Error

No-retry codes (permanent failure)

400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found

Automatic deduplication

WAzion prevents sending the same phone number twice. A SHA256 hash of each sent phone number is stored. If the phone number has been previously notified, it is not sent again.

Rate Limiting

Maximum 100 webhooks in queue per store. If the limit is exceeded, new webhooks are discarded. The processor sends a maximum of 50 webhooks per execution with a 1-second delay between each.

Connection Settings

Total timeout: 180s
Connect timeout: 5s
SSL verified: Yes
Redirects: No

Webhooks do NOT follow redirects. Your endpoint must respond directly without redirecting.

6. Complete Sample Code

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 'followup.replied':
        // El cliente respondió al mensaje de seguimiento
        error_log("Cliente {$data['phone']} respondió al seguimiento (día {$data['days_to_reply']})");
        echo json_encode(['status' => 'ok', 'message' => 'Reply tracked']);
        break;

    case 'followup.converted':
        // El cliente compró tras recibir el seguimiento
        error_log("Conversión: {$data['phone']} compró tras seguimiento (día {$data['days_to_reply']})");
        echo json_encode(['status' => 'ok', 'message' => 'Conversion tracked']);
        break;

    case 'plugin_chat.session_closed':
        // Sesión del chat web finalizada
        $summary = $data['ai_summary'];
        $messages = $data['messages'];
        $score = $data['satisfaction_score'];

        // Guardar resumen y puntuación en tu sistema
        error_log("Chat cerrado (score: $score): $summary");

        echo json_encode(['status' => 'ok', 'message' => 'Chat session logged']);
        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 'followup.replied':
            console.log(`Cliente ${data.phone} respondió al seguimiento (día ${data.days_to_reply})`);
            // Actualizar estado del lead en tu CRM
            break;

        case 'followup.converted':
            console.log(`Conversión: ${data.phone} compró tras seguimiento (día ${data.days_to_reply})`);
            // Registrar conversión, calcular ROI, etc.
            break;

        case 'plugin_chat.session_closed':
            console.log(`Chat cerrado (score: ${data.satisfaction_score}): ${data.ai_summary}`);
            console.log(`Mensajes: ${data.messages.length}`);
            // Guardar resumen y puntuación en tu sistema
            break;

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

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

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

Change log

No recent changes in this documentation.

Related articles

WAzion Assistant

Commercial information and technical support

Hello! I am the WAzion assistant. I can help you with information about prices and plans, technical questions, configuration, or any questions about our product. How can I assist you?
Developed with WAzion AI