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 Configuri un URL del tuo server nella Dashboard di WAzion
- 2 Selezioni quali eventi vuoi ricevere
- 3 Quando si verifica un evento, WAzion invia un POST al tuo URL
- 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
PrincipaleSi 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
MonitoraggioSi 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",
"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) |
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 | 10 secondi |
| Massimo in coda | 100 webhooks |
Ritenti
| Numero massimo di tentativi | 6 |
| Strategia | Backoff esponenziale |
Calendario di ritentativi
Dopo il tentativo 6, il webhook viene segnalato come errore permanente.
Risposte accettate
Qualsiasi codice 2xx è considerato un successo.
Codici che attivano il ritentativo
Codici senza ritento (errore permanente)
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
I webhook NON seguono i reindirizzamenti. Il tuo endpoint deve rispondere direttamente senza reindirizzare.
6. Codice di Esempio Completo
<?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" : ""));
}
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'));
Cronologia delle modifiche
Nessuna modifica recente in questa documentazione.