Webhooks
Receba notificações HTTP automáticas no seu servidor quando ocorrerem eventos importantes na WAzion.
1. Introdução
Os webhooks permitem que seu servidor receba notificações automáticas quando eventos específicos ocorrem no WAzion. Em vez de consultar periodicamente se há novidades, o WAzion envia uma requisição HTTP POST ao seu servidor quando algo relevante acontece.
Como funciona?
- 1 Você configura uma URL do seu servidor no Painel de Controle da WAzion
- 2 Você seleciona quais eventos deseja receber
- 3 Quando ocorre um evento, a WAzion envia um POST para o seu URL
- 4 Seu servidor processa o evento e responde com HTTP 200
Casos de uso
- • Sincronizar novos contatos com o seu CRM
- • Criar leads automaticamente
- • Disparar automações em Zapier/Make
- • Notificar aos sistemas internos
2. Eventos Disponíveis
phone.detected
PrincipalDispara-se quando é detectado um novo número de telefone em uma conversa. Útil para capturar leads automaticamente.
Payload de exemplo
{
"event_type": "phone.detected",
"phone": "+34612345678",
"detected_at": "2025-01-15T14:30:00Z",
"shop_id": 123,
"conversation_hash": "a1b2c3d4e5f6..."
}
Campos do payload
| event_type | Tipo de evento |
| phone | Número detectado (E.164) |
| detected_at | Data/hora ISO 8601 |
| shop_id | ID da sua loja na WAzion |
| conversation_hash | Hash único da conversa |
followup.detected
AcompanhamentoDispara quando o sistema de Acompanhamento Inteligente detecta uma conversa com intenção de compra. Inclui a mensagem de acompanhamento sugerida e o resultado da análise.
Payload de exemplo
{
"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 do payload
| intent_level | Nível de intenção: médio ou alto |
| product_mentioned | Produto mencionado (pode ser nulo) |
| outcome | Estado: abandonado, incerto, convertido, apenas_suporte |
| suggested_message | Mensagem de acompanhamento gerada pela IA |
| attempt_number | Número de tentativa (1-3) |
test
Evento de teste que você pode acionar manualmente a partir do Painel para verificar se seu endpoint está funcionando corretamente.
{
"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. Cabeçalhos HTTP
WAzion envia os seguintes headers em cada requisição webhook:
| Header | Descrição | Exemplo |
|---|---|---|
| Content-Type | Tipo de conteúdo | application/json |
| User-Agent | Identificador da WAzion | WAzion-Webhooks/1.0 |
| X-Webhook-ID | ID único do webhook (formato wh_XXXXXXXX) | wh_00012345 |
| X-Webhook-Event | Tipo de evento | phone.detected |
| X-Webhook-Attempt | Número de tentativa (1-6) | 1 |
| X-Webhook-Timestamp | Unix timestamp de envio | 1705329000 |
| X-Webhook-Signature | Assinatura HMAC-SHA256 hex (se houver segredo) | a1b2c3d4e5f6... |
4. Assinatura HMAC-SHA256
Se você configurar um webhook secret, a WAzion assinará cada pedido usando HMAC-SHA256. Isso permite que você verifique se o pedido realmente vem da WAzion.
Como se gera a assinatura
// 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ção em 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ção em 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: Também deves verificar que o timestamp não seja muito antigo (ex: máximo 5 minutos) para prevenir ataques de replay.
5. Especificações Técnicas
Tempos limite
| Tempo esgotado por solicitação | 10 segundos |
| Máximo na fila | 100 webhooks |
Repetições
| Máximo de tentativas | 6 |
| Estratégia | Recuo exponencial |
Calendário de novas tentativas
Após a tentativa 6, o webhook é marcado como falha permanente.
Respostas aceitas
Qualquer código 2xx é considerado sucesso.
Códigos que ativam nova tentativa
Códigos sem reintento (falha permanente)
Desduplicação automática
WAzion evita enviar o mesmo número de telefone duas vezes. É armazenado um hash SHA256 de cada telefone enviado. Se o telefone já foi notificado anteriormente, não é enviado novamente.
Limitação de Taxa
Máximo 100 webhooks na fila por loja. Se o limite for ultrapassado, os novos webhooks serão descartados. O processador envia no máximo 50 webhooks por execução com 1 segundo de atraso entre cada um.
Configuração de conexão
Os webhooks NÃO seguem redirecionamentos. Seu endpoint deve responder diretamente sem redirecionar.
6. Código de Exemplo 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'));
Histórico de alterações
Sem alterações recentes nesta documentação.