Webhooks
Erhalte automatische HTTP-Benachrichtigungen auf deinem Server, wenn wichtige Ereignisse in WAzion auftreten.
1. Einführung
Webhooks ermöglichen es deinem Server, automatische Benachrichtigungen zu erhalten, wenn bestimmte Ereignisse in WAzion stattfinden. Anstatt regelmäßig nach Neuigkeiten zu fragen, sendet WAzion eine HTTP-POST-Anfrage an deinen Server, wenn etwas Relevantes geschieht.
Wie funktioniert das?
- 1 Du konfigurierst eine URL deines Servers im WAzion-Dashboard.
- 2 Du wählst aus, welche Veranstaltungen du erhalten möchtest
- 3 Wenn ein Ereignis eintritt, sendet WAzion einen POST an deine URL
- 4 Ihr Server verarbeitet das Ereignis und antwortet mit HTTP 200
Anwendungsfälle
- • Neue Kontakte mit deinem CRM synchronisieren
- • Leads automatisch erstellen
- • Automatisierungen in Zapier/Make auslösen
- • Interne Systeme benachrichtigen
2. Verfügbare Veranstaltungen
phone.detected
Haupt-Es wird ausgelöst, wenn eine neue Telefonnummer in einem Gespräch erkannt wird. Nützlich, um Leads automatisch zu erfassen.
Beispiel-Payload
{
"event_type": "phone.detected",
"phone": "+34612345678",
"detected_at": "2025-01-15T14:30:00Z",
"shop_id": 123,
"conversation_hash": "a1b2c3d4e5f6..."
}
Nutzlastfelder
| event_type | Art der Veranstaltung |
| phone | Erkannte Nummer (E.164) |
| detected_at | Datum/Uhrzeit ISO 8601 |
| shop_id | ID deines Shops bei WAzion |
| conversation_hash | Eindeutiger Hash der Unterhaltung |
followup.detected
NachverfolgungEs wird ausgelöst, wenn das intelligente Verfolgungssystem ein Gespräch mit Kaufabsicht erkennt. Es enthält die vorgeschlagene Follow-up-Nachricht und das Ergebnis der Analyse.
Beispiel-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"
}
Nutzlastfelder
| intent_level | Intention Level: mittel oder hoch |
| product_mentioned | Erwähntes Produkt (kann null sein) |
| outcome | Zustand: verlassen, unklar, umgewandelt, nur Unterstützung |
| suggested_message | Nachricht zur Nachverfolgung, die von der KI generiert wurde |
| attempt_number | Versuchsnummer (1-3) |
followup.replied
NachverfolgungEs wird ausgelöst, wenn ein Kunde auf die Follow-up-Nachricht antwortet, die von der Intelligent Follow-up gesendet wurde.
Beispiel-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
}
Nutzlastfelder
| intent_level | Intention Level: mittel oder hoch |
| product_mentioned | Erwähntes Produkt (kann null sein) |
| followup_message | Nachricht zur Nachverfolgung, die an den Kunden gesendet wurde |
| days_to_reply | Tage seit der Nachverfolgung bis zur Antwort |
followup.converted
NachverfolgungEs wird ausgelöst, wenn ein Kunde einen Kauf tätigt, nachdem er eine Follow-up-Nachricht erhalten hat. Der Kauf wird über Shopify, WooCommerce, PrestaShop, VTEX oder deinen CRM-Überprüfungspunkt erkannt.
Beispiel-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
}
Nutzlastfelder
| intent_level | Intention Level: mittel oder hoch |
| product_mentioned | Erwähntes Produkt (kann null sein) |
| followup_message | Nachricht zur Nachverfolgung, die an den Kunden gesendet wurde |
| days_to_reply | Tage seit der Nachverfolgung bis zum Kauf |
plugin_chat.session_closed
Web-ChatEs wird ausgelöst, wenn eine Sitzung des Web-Chat-Widgets endet. Es enthält eine von KI generierte Zusammenfassung, den vollständigen Nachrichtenverlauf und die Kundenzufriedenheitsbewertung.
Beispiel-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"
}
Nutzlastfelder
| event_type | Art der Veranstaltung |
| shop_id | ID deines Shops bei WAzion |
| session_id | Eindeutige Kennung der Chatsitzung |
| ai_summary | Zusammenfassung des Gesprächs, erstellt von KI |
| messages | Vollständige Nachrichtenhistorie (Rolle, Inhalt, Zeitstempel) |
| satisfaction_score | Kundenzufriedenheitsbewertung (1-5) |
| closed_at | ISO 8601 Datum/Uhrzeit des Sitzungsabschlusses |
test
Testereignis, das du manuell über das Dashboard auslösen kannst, um zu überprüfen, ob dein Endpunkt korrekt funktioniert.
{
"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-Header
WAzion sendet bei jeder Webhook-Anfrage die folgenden Header:
| Header | Beschreibung | Beispiel |
|---|---|---|
| Content-Type | Inhaltstyp | application/json |
| User-Agent | WAzion Kennung | WAzion-Webhooks/1.0 |
| X-Webhook-ID | Einzigartige Webhook-ID (Format wh_XXXXXXXX) | wh_00012345 |
| X-Webhook-Event | Art der Veranstaltung | phone.detected |
| X-Webhook-Attempt | Versuchsnummer (1-6) | 1 |
| X-Webhook-Timestamp | Unix-Zeitstempel der Sendung | 1705329000 |
| X-Webhook-Signature | HMAC-SHA256-Signatur hex (wenn ein Geheimnis vorhanden ist) | a1b2c3d4e5f6... |
4. HMAC-SHA256-Signatur
Wenn du ein Webhook-Geheimnis einrichtest, signiert WAzion jede Anfrage mit HMAC-SHA256. So kannst du überprüfen, ob die Anfrage tatsächlich von WAzion stammt.
Wie die Unterschrift erzeugt wird
// 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
Verifizierung 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');
}
Überprüfung 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)
);
}
Wichtig: Du solltest auch überprüfen, dass der Zeitstempel nicht zu alt ist (z. B. maximal 5 Minuten), um Replay-Angriffe zu verhindern.
5. Technische Spezifikationen
Zeitüberschreitungen
| Zeitüberschreitung auf Anfrage | 180 Sekunden |
| Maximum in der Warteschlange | 100 webhooks |
Wiederholungen
| Maximale Anzahl von Versuchen | 6 |
| Strategie | Exponentielles Backoff |
Wiederholungsplan
Nach dem 6. Versuch wird der Webhook als dauerhafter Fehler markiert.
Akzeptierte Antworten
Jeder 2xx-Code gilt als Erfolg.
Codes, die einen erneuten Versuch auslösen
Codes ohne Wiederholungsversuch (dauerhafter Fehler)
Automatische Duplikaterkennung
WAzion verhindert, dass dieselbe Telefonnummer zweimal gesendet wird. Es wird ein SHA256-Hash jeder gesendeten Telefonnummer gespeichert. Wenn die Telefonnummer bereits zuvor benachrichtigt wurde, wird sie nicht erneut gesendet.
Ratenbegrenzung
Maximal 100 Webhooks in der Warteschlange pro Shop. Wenn das Limit überschritten wird, werden neue Webhooks verworfen. Der Prozessor sendet maximal 50 Webhooks pro Ausführung mit einer Verzögerung von 1 Sekunde zwischen jedem.
Verbindungseinstellungen
Webhooks folgen keinen Weiterleitungen. Dein Endpunkt muss direkt antworten, ohne weiterzuleiten.
6. Vollständiger Beispielcode
<?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" : ""));
}
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'));
Änderungsverlauf
Keine kürzlichen Änderungen in dieser Dokumentation.