Skip to main content

¿Qué son los webhooks?

Los webhooks son notificaciones HTTP que NotMeta envía a tu aplicación cuando ocurren eventos específicos. Esto te permite reaccionar en tiempo real a cambios en conversaciones, mensajes y usuarios.

Eventos disponibles

Conversaciones

  • conversation.created - Nueva conversación creada
  • conversation.assigned - Conversación asignada a un usuario
  • conversation.unassigned - Conversación desasignada
  • conversation.closed - Conversación cerrada
  • conversation.reopened - Conversación reabierta

Mensajes

  • message.received - Nuevo mensaje recibido
  • message.sent - Mensaje enviado
  • message.delivered - Mensaje entregado
  • message.read - Mensaje leído
  • message.failed - Error al enviar mensaje

Usuarios

  • user.created - Nuevo usuario creado
  • user.updated - Usuario actualizado
  • user.deleted - Usuario eliminado
  • user.online - Usuario conectado
  • user.offline - Usuario desconectado

Sistema

  • webhook.verified - Webhook verificado
  • webhook.failed - Error en webhook

Configurar webhooks

Crear webhook

curl -X POST "https://api.notmeta.com/v1/webhooks" \
  -H "Authorization: Bearer TU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tu-dominio.com/webhook",
    "events": ["message.received", "conversation.assigned"],
    "secret": "tu_secret_webhook"
  }'

Respuesta

{
  "id": "wh_123456789",
  "url": "https://tu-dominio.com/webhook",
  "events": ["message.received", "conversation.assigned"],
  "secret": "whsec_abc123...",
  "created_at": "2024-01-01T00:00:00Z",
  "status": "active"
}

Estructura de eventos

Evento de mensaje recibido

{
  "id": "evt_123456789",
  "type": "message.received",
  "created_at": "2024-01-01T12:00:00Z",
  "data": {
    "message": {
      "id": "msg_123456789",
      "conversation_id": "conv_123456789",
      "from": "1234567890",
      "to": "0987654321",
      "type": "text",
      "text": "Hola, necesito ayuda",
      "timestamp": "2024-01-01T12:00:00Z"
    },
    "conversation": {
      "id": "conv_123456789",
      "status": "open",
      "assigned_to": null,
      "created_at": "2024-01-01T11:55:00Z"
    }
  }
}

Evento de conversación asignada

{
  "id": "evt_123456790",
  "type": "conversation.assigned",
  "created_at": "2024-01-01T12:05:00Z",
  "data": {
    "conversation": {
      "id": "conv_123456789",
      "status": "assigned",
      "assigned_to": "user_123456789",
      "assigned_at": "2024-01-01T12:05:00Z"
    },
    "user": {
      "id": "user_123456789",
      "name": "Juan Pérez",
      "email": "[email protected]",
      "role": "agent"
    }
  }
}

Implementar endpoint webhook

Node.js con Express

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-notmeta-signature'];
  const payload = JSON.stringify(req.body);
  
  // Verificar firma
  if (!verifySignature(payload, signature)) {
    return res.status(400).send('Invalid signature');
  }
  
  // Procesar evento
  const event = req.body;
  handleWebhookEvent(event);
  
  res.status(200).send('OK');
});

function verifySignature(payload, signature) {
  const secret = process.env.NOTMETA_WEBHOOK_SECRET;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return signature === `sha256=${expectedSignature}`;
}

function handleWebhookEvent(event) {
  switch (event.type) {
    case 'message.received':
      handleNewMessage(event.data.message);
      break;
    case 'conversation.assigned':
      handleConversationAssigned(event.data.conversation);
      break;
    default:
      console.log(`Evento no manejado: ${event.type}`);
  }
}

function handleNewMessage(message) {
  console.log(`Nuevo mensaje de ${message.from}: ${message.text}`);
  // Tu lógica aquí
}

function handleConversationAssigned(conversation) {
  console.log(`Conversación ${conversation.id} asignada a ${conversation.assigned_to}`);
  // Tu lógica aquí
}

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

Python con Flask

from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-NotMeta-Signature')
    payload = request.get_data()
    
    # Verificar firma
    if not verify_signature(payload, signature):
        return 'Invalid signature', 400
    
    # Procesar evento
    event = request.json
    handle_webhook_event(event)
    
    return 'OK', 200

def verify_signature(payload, signature):
    secret = os.environ.get('NOTMETA_WEBHOOK_SECRET')
    expected_signature = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    return signature == f'sha256={expected_signature}'

def handle_webhook_event(event):
    event_type = event['type']
    
    if event_type == 'message.received':
        handle_new_message(event['data']['message'])
    elif event_type == 'conversation.assigned':
        handle_conversation_assigned(event['data']['conversation'])
    else:
        print(f'Evento no manejado: {event_type}')

def handle_new_message(message):
    print(f'Nuevo mensaje de {message["from"]}: {message["text"]}')
    # Tu lógica aquí

def handle_conversation_assigned(conversation):
    print(f'Conversación {conversation["id"]} asignada')
    # Tu lógica aquí

if __name__ == '__main__':
    app.run(port=3000)

PHP

<?php
// webhook.php

$secret = $_ENV['NOTMETA_WEBHOOK_SECRET'];
$signature = $_SERVER['HTTP_X_NOTMETA_SIGNATURE'];
$payload = file_get_contents('php://input');

// Verificar firma
if (!verifySignature($payload, $signature, $secret)) {
    http_response_code(400);
    echo 'Invalid signature';
    exit;
}

// Procesar evento
$event = json_decode($payload, true);
handleWebhookEvent($event);

echo 'OK';

function verifySignature($payload, $signature, $secret) {
    $expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
    return hash_equals($expectedSignature, $signature);
}

function handleWebhookEvent($event) {
    $eventType = $event['type'];
    
    switch ($eventType) {
        case 'message.received':
            handleNewMessage($event['data']['message']);
            break;
        case 'conversation.assigned':
            handleConversationAssigned($event['data']['conversation']);
            break;
        default:
            error_log("Evento no manejado: $eventType");
    }
}

function handleNewMessage($message) {
    error_log("Nuevo mensaje de {$message['from']}: {$message['text']}");
    // Tu lógica aquí
}

function handleConversationAssigned($conversation) {
    error_log("Conversación {$conversation['id']} asignada");
    // Tu lógica aquí
}
?>

Verificación de webhooks

Verificación automática

Cuando creas un webhook, NotMeta envía un evento de verificación:
{
  "id": "evt_verify_123",
  "type": "webhook.verified",
  "created_at": "2024-01-01T00:00:00Z",
  "data": {
    "webhook_id": "wh_123456789",
    "challenge": "challenge_string_123"
  }
}

Responder al desafío

app.post('/webhook', (req, res) => {
  if (req.body.type === 'webhook.verified') {
    // Responder al desafío de verificación
    return res.status(200).send(req.body.data.challenge);
  }
  
  // Procesar otros eventos...
});

Manejo de errores

Reintentos automáticos

NotMeta reintenta automáticamente webhooks que fallan:
  • Primer reintento: 1 minuto
  • Segundo reintento: 5 minutos
  • Tercer reintento: 15 minutos
  • Cuarto reintento: 1 hora
  • Máximo: 4 reintentos

Manejo de errores en tu endpoint

app.post('/webhook', async (req, res) => {
  try {
    // Procesar evento
    await handleWebhookEvent(req.body);
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error procesando webhook:', error);
    
    // Para errores temporales, devolver 5xx
    if (isTemporaryError(error)) {
      res.status(503).send('Service Unavailable');
    } else {
      // Para errores permanentes, devolver 4xx
      res.status(400).send('Bad Request');
    }
  }
});

Mejores prácticas

Idempotencia

const processedEvents = new Set();

function handleWebhookEvent(event) {
  // Evitar procesar el mismo evento dos veces
  if (processedEvents.has(event.id)) {
    console.log(`Evento ${event.id} ya procesado`);
    return;
  }
  
  // Procesar evento
  processEvent(event);
  
  // Marcar como procesado
  processedEvents.add(event.id);
}

Procesamiento asíncrono

const Queue = require('bull');
const webhookQueue = new Queue('webhook processing');

app.post('/webhook', (req, res) => {
  // Verificar firma
  if (!verifySignature(req.body, req.headers['x-notmeta-signature'])) {
    return res.status(400).send('Invalid signature');
  }
  
  // Agregar a cola para procesamiento asíncrono
  webhookQueue.add('process-event', req.body);
  
  res.status(200).send('OK');
});

webhookQueue.process('process-event', async (job) => {
  const event = job.data;
  await handleWebhookEvent(event);
});

Logging

function logWebhookEvent(event) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    event_id: event.id,
    event_type: event.type,
    webhook_url: req.url
  }));
}

Monitoreo y debugging

Estado de webhooks

curl -X GET "https://api.notmeta.com/v1/webhooks" \
  -H "Authorization: Bearer TU_TOKEN"

Logs de webhook

curl -X GET "https://api.notmeta.com/v1/webhooks/wh_123456789/logs" \
  -H "Authorization: Bearer TU_TOKEN"

Estadísticas de webhooks

curl -X GET "https://api.notmeta.com/v1/webhooks/wh_123456789/stats" \
  -H "Authorization: Bearer TU_TOKEN"

Solución de problemas

Webhook no recibe eventos

  1. Verifica que la URL sea accesible desde internet
  2. Confirma que uses HTTPS en producción
  3. Revisa los logs del webhook en NotMeta
  4. Verifica que los eventos estén configurados correctamente

Errores de firma

  1. Confirma que el secret sea correcto
  2. Verifica que uses el payload completo
  3. Revisa la codificación (UTF-8)

Timeouts

  1. Asegúrate de responder rápidamente (máximo 30 segundos)
  2. Usa procesamiento asíncrono para tareas pesadas
  3. Implementa circuit breakers para servicios externos

Recursos adicionales

Configuración inicial

Configura tu entorno de desarrollo

Mejores prácticas

Aprende las mejores prácticas para integrar con NotMeta