CENTRO ASSISTENZA

Webhooks

Ricevi notifiche HTTP automatiche sul tuo server quando si verificano eventi importanti in WAzion.

1. Introduzione

I webhook permettono al tuo server di ricevere notifiche automatiche quando si verificano eventi specifici in WAzion. Invece di interrogare periodicamente se ci sono novità, WAzion invia una richiesta HTTP POST al tuo server quando accade qualcosa di rilevante.

Come funziona?

  1. 1 Configuri un URL del tuo server nella Dashboard di WAzion
  2. 2 Selezioni quali eventi vuoi ricevere
  3. 3 Quando si verifica un evento, WAzion invia un POST al tuo URL
  4. 4 Il tuo server elabora l’evento e risponde con HTTP 200

Casi d’uso

  • Sincronizzare nuovi contatti con il tuo CRM
  • Creare lead automaticamente
  • Attivare automazioni in Zapier/Make
  • Notificare ai sistemi interni

2. Eventi Disponibili

phone.detected

Principale

Si attiva quando viene rilevato un nuovo numero di telefono in una conversazione. Utile per acquisire automaticamente lead.

Payload di esempio

{
  "event_type": "phone.detected",
  "phone": "+34612345678",
  "detected_at": "2025-01-15T14:30:00Z",
  "shop_id": 123,
  "conversation_hash": "a1b2c3d4e5f6..."
}
Campi del payload
event_type Tipo di evento
phone Numero rilevato (E.164)
detected_at Data/ora ISO 8601
shop_id ID del tuo negozio su WAzion
conversation_hash Hash unico della conversazione

followup.detected

Monitoraggio

Si attiva quando il sistema di Monitoraggio Intelligente rileva una conversazione con intenzione di acquisto. Include il messaggio di follow-up suggerito e il risultato dell’analisi.

Payload di esempio

{
  "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"
}
Campi del payload
intent_level Livello di intenzione: medio o alto
product_mentioned Prodotto menzionato (può essere nullo)
outcome Stato: abbandonato, poco chiaro, convertito, solo supporto
suggested_message Messaggio di follow-up generato dall’IA
attempt_number Numero di tentativo (1-3)

followup.replied

Monitoraggio

Si attiva quando un cliente risponde al messaggio di follow-up inviato dal Follow-up Intelligente.

Payload di esempio

{
  "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
}
Campi del payload
intent_level Livello di intenzione: medio o alto
product_mentioned Prodotto menzionato (può essere nullo)
followup_message Messaggio di follow-up inviato al cliente
days_to_reply Giorni trascorsi dal follow-up alla risposta

followup.converted

Monitoraggio

Si attiva quando un cliente effettua un acquisto dopo aver ricevuto un messaggio di follow-up. L’acquisto viene rilevato tramite Shopify, WooCommerce, PrestaShop, VTEX o il tuo endpoint CRM di verifica.

Payload di esempio

{
  "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
}
Campi del payload
intent_level Livello di intenzione: medio o alto
product_mentioned Prodotto menzionato (può essere nullo)
followup_message Messaggio di follow-up inviato al cliente
days_to_reply Giorni trascorsi dal follow-up all’acquisto

plugin_chat.session_closed

Chat Web

Si attiva quando una sessione del widget di chat web termina. Include un riepilogo generato dall’IA, l’intera cronologia dei messaggi e il punteggio di soddisfazione del cliente.

Payload di esempio

{
  "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"
}
Campi del payload
event_type Tipo di evento
shop_id ID del tuo negozio su WAzion
session_id Identificatore unico della sessione di chat
ai_summary Riepilogo della conversazione generato da IA
messages Storico completo dei messaggi (ruolo, contenuto, timestamp)
satisfaction_score Punteggio di soddisfazione del cliente (1-5)
closed_at Data/ora ISO 8601 di chiusura della sessione

test

Evento di prova che puoi attivare manualmente dal Dashboard per verificare che il tuo endpoint funzioni correttamente.

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

WAzion invia i seguenti header in ogni richiesta webhook:

Header Descrizione Esempio
Content-Type Tipo di contenuto application/json
User-Agent Identificatore di WAzion WAzion-Webhooks/1.0
X-Webhook-ID ID univoco del webhook (formato wh_XXXXXXXX) wh_00012345
X-Webhook-Event Tipo di evento phone.detected
X-Webhook-Attempt Numero di tentativo (1-6) 1
X-Webhook-Timestamp Timestamp Unix di invio 1705329000
X-Webhook-Signature Firma HMAC-SHA256 esadecimale (se c’è un segreto) a1b2c3d4e5f6...

4. Firma HMAC-SHA256

Se configuri un webhook secret, WAzion firmerà ogni richiesta utilizzando HMAC-SHA256. Questo ti permette di verificare che la richiesta provenga realmente da WAzion.

Come si 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

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

Verifica 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)
    );
}

Importante: Dovresti anche verificare che il timestamp non sia troppo vecchio (es: massimo 5 minuti) per prevenire attacchi di replay.

5. Specifiche Tecniche

Timeout

Timeout per richiesta 180 secondi
Massimo in coda 100 webhooks

Ritenti

Numero massimo di tentativi 6
Strategia Backoff esponenziale

Calendario di ritentativi

Tentativo 1
Immediato
Tentativo 2
1 min
Tentativo 3
5 min
Tentativo 4
15 min
Tentativo 5
1 hora
Tentativo 6
4 horas

Dopo il tentativo 6, il webhook viene segnalato come errore permanente.

Risposte accettate

200 OK 201 Created 202 Accepted 204 No Content

Qualsiasi codice 2xx è considerato un successo.

Codici che attivano il ritentativo

408 Timeout 429 Rate Limit 5xx Server Error

Codici senza ritento (errore permanente)

400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found

De-duplicazione automatica

WAzion evita di inviare lo stesso numero di telefono due volte. Viene memorizzato un hash SHA256 di ogni telefono inviato. Se il telefono è stato già notificato in precedenza, non viene inviato di nuovo.

Limitazione della velocità

Massimo 100 webhook in coda per negozio. Se si supera il limite, i nuovi webhook vengono scartati. Il processore invia massimo 50 webhook per esecuzione con 1 secondo di intervallo tra ciascuno.

Configurazione della connessione

Timeout totale: 180s
Timeout di connessione: 5s
SSL verificato:
Reindirizzamenti: No

I webhook NON seguono i reindirizzamenti. Il tuo endpoint deve rispondere direttamente senza reindirizzare.

6. Codice di Esempio 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 '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'));

Cronologia delle modifiche

Nessuna modifica recente in questa documentazione.

Articoli correlati

Assistente WAzion

Informazioni commerciali e supporto tecnico

Ciao! Sono l’assistente di WAzion. Posso aiutarti con informazioni su prezzi e piani, dubbi tecnici, configurazione o qualsiasi domanda sul nostro prodotto. Come posso aiutarti?
Sviluppato con WAzion AI