Skip to content

Configuración de Webhooks

Introducción

Los webhooks de TSALVA te permiten recibir notificaciones en tiempo real sobre cambios de estado en tus servicios. Esta guía te ayudará a configurar correctamente tu endpoint para recibir estas notificaciones.

Requisitos Previos

🌐 Endpoint Público

Tu servidor debe tener un endpoint públicamente accesible:

✅ https://tu-dominio.com/webhook/tsalva
✅ https://api.tu-empresa.com/webhooks/cambio-estado
❌ http://localhost:3000/webhook (no accesible desde internet)
❌ http://192.168.1.100/webhook (IP privada)

🔒 HTTPS Recomendado

Aunque HTTP está soportado, HTTPS es altamente recomendado para:

  • Proteger los datos en tránsito
  • Validar la autenticidad del servidor
  • Cumplir con buenas prácticas de seguridad

🔑 API Key

Debes proporcionar una API Key que TSALVA usará para autenticar las peticiones hacia tu endpoint.

Proceso de Configuración

1. Desarrollo del Endpoint

Primero, implementa tu endpoint que recibirá los webhooks:

javascript
// Node.js + Express
app.put('/webhook/tsalva', (req, res) => {
  try {
    // Validar API Key
    const apiKey = req.headers['x-apikey'];
    if (apiKey !== process.env.TSALVA_API_KEY) {
      return res.status(401).json({ error: 'API Key inválida' });
    }

    // Procesar webhook
    const { idServicio, codigoEstado, detalleEstado } = req.body;
    console.log(`Servicio ${idServicio} cambió a: ${codigoEstado}`);
    
    // Tu lógica de negocio aquí
    procesarCambioEstado(req.body);

    // Responder con éxito
    res.status(200).json({ received: true });
    
  } catch (error) {
    console.error('Error procesando webhook:', error);
    res.status(500).json({ error: 'Error interno' });
  }
});

2. Hacer el Endpoint Público

Opción A: Servidor en la nube

bash
# Desplegar en tu servidor de producción
# El endpoint estará disponible en:
https://tu-dominio.com/webhook/tsalva

Opción B: Túnel para desarrollo (ngrok)

bash
# Instalar ngrok
npm install -g ngrok

# Exponer puerto local
ngrok http 3000

# Usarás la URL temporal que ngrok proporciona:
# https://abc123.ngrok.io/webhook/tsalva

3. Solicitar Configuración

Contacta al equipo de TSALVA con la siguiente información:

📧 Email: soporte@robpixels.com

📋 Información requerida:

Empresa: [Nombre de tu empresa]
NIT: [Tu NIT/documento]
Contacto técnico: [Nombre y email]

Configuración webhook:
- URL: https://tu-dominio.com/webhook/tsalva
- API Key: [tu_api_key_secreta]
- Método: PUT
- Content-Type: application/json

Servicios a monitorear:
- Todos los servicios: ✅
- Solo servicios específicos: ❌ [especificar cuáles]

4. Prueba de Conectividad

El equipo de TSALVA realizará pruebas iniciales enviando webhooks de prueba:

json
{
  "idServicio": "TEST_123",
  "codigoEstado": "TEST",
  "dniGestor": "00000000",
  "detalleEstado": {
    "test": true,
    "mensaje": "Webhook de prueba",
    "fechaCambioEstado": "2025-07-03T10:00:00Z"
  }
}

Tu endpoint debe responder con:

json
{
  "received": true,
  "test": true
}

Implementaciones por Tecnología

Node.js + Express

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

const app = express();
app.use(express.json());

// Middleware de validación
const validarApiKey = (req, res, next) => {
  const apiKey = req.headers['x-apikey'];
  
  if (!apiKey) {
    return res.status(401).json({ error: 'API Key requerida' });
  }
  
  if (apiKey !== process.env.TSALVA_API_KEY) {
    return res.status(401).json({ error: 'API Key inválida' });
  }
  
  next();
};

// Endpoint principal
app.put('/webhook/tsalva', validarApiKey, async (req, res) => {
  try {
    const webhook = req.body;
    
    // Log para debugging
    console.log('Webhook recibido:', {
      servicio: webhook.idServicio,
      estado: webhook.codigoEstado,
      timestamp: new Date().toISOString()
    });
    
    // Procesar asíncronamente
    setImmediate(() => procesarWebhook(webhook));
    
    // Responder inmediatamente
    res.status(200).json({ 
      received: true,
      timestamp: new Date().toISOString()
    });
    
  } catch (error) {
    console.error('Error procesando webhook:', error);
    res.status(500).json({ error: 'Error interno' });
  }
});

async function procesarWebhook(webhook) {
  try {
    // Tu lógica de negocio
    await actualizarBaseDatos(webhook);
    await enviarNotificacionCliente(webhook);
    
  } catch (error) {
    console.error('Error en procesamiento asíncrono:', error);
    // Implementar reintentos, dead letter queue, etc.
  }
}

app.listen(3000, () => {
  console.log('Servidor webhook ejecutándose en puerto 3000');
});

Python + Flask

python
from flask import Flask, request, jsonify
import os
import logging
import threading

app = Flask(__name__)

# Configurar logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def validar_api_key():
    api_key = request.headers.get('x-apikey')
    expected_key = os.environ.get('TSALVA_API_KEY')
    
    if not api_key:
        return False, 'API Key requerida'
    
    if api_key != expected_key:
        return False, 'API Key inválida'
    
    return True, None

@app.route('/webhook/tsalva', methods=['PUT'])
def webhook_tsalva():
    try:
        # Validar API Key
        valida, error = validar_api_key()
        if not valida:
            return jsonify({'error': error}), 401
        
        # Obtener datos
        webhook_data = request.get_json()
        
        # Log
        logger.info(f"Webhook recibido para servicio {webhook_data['idServicio']}")
        
        # Procesar en hilo separado
        thread = threading.Thread(
            target=procesar_webhook, 
            args=(webhook_data,)
        )
        thread.start()
        
        # Responder inmediatamente
        return jsonify({
            'received': True,
            'timestamp': datetime.utcnow().isoformat()
        }), 200
        
    except Exception as e:
        logger.error(f'Error procesando webhook: {e}')
        return jsonify({'error': 'Error interno'}), 500

def procesar_webhook(webhook_data):
    try:
        # Tu lógica de negocio
        actualizar_base_datos(webhook_data)
        enviar_notificacion_cliente(webhook_data)
        
    except Exception as e:
        logger.error(f'Error en procesamiento: {e}')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

PHP

php
<?php
// webhook.php

header('Content-Type: application/json');

try {
    // Validar método
    if ($_SERVER['REQUEST_METHOD'] !== 'PUT') {
        http_response_code(405);
        echo json_encode(['error' => 'Método no permitido']);
        exit;
    }
    
    // Validar API Key
    $headers = getallheaders();
    $apiKey = $headers['x-apikey'] ?? '';
    $expectedKey = $_ENV['TSALVA_API_KEY'] ?? '';
    
    if (empty($apiKey) || $apiKey !== $expectedKey) {
        http_response_code(401);
        echo json_encode(['error' => 'API Key inválida']);
        exit;
    }
    
    // Obtener datos del webhook
    $input = file_get_contents('php://input');
    $webhookData = json_decode($input, true);
    
    if (json_last_error() !== JSON_ERROR_NONE) {
        http_response_code(400);
        echo json_encode(['error' => 'JSON inválido']);
        exit;
    }
    
    // Log del webhook
    error_log("Webhook recibido: " . $webhookData['idServicio']);
    
    // Procesar webhook (en background si es posible)
    procesarWebhook($webhookData);
    
    // Responder con éxito
    http_response_code(200);
    echo json_encode([
        'received' => true,
        'timestamp' => date('c')
    ]);
    
} catch (Exception $e) {
    error_log("Error procesando webhook: " . $e->getMessage());
    http_response_code(500);
    echo json_encode(['error' => 'Error interno']);
}

function procesarWebhook($data) {
    // Tu lógica de negocio aquí
    $idServicio = $data['idServicio'];
    $codigoEstado = $data['codigoEstado'];
    
    // Actualizar base de datos, enviar notificaciones, etc.
}
?>

Configuración de Infraestructura

Nginx (Reverse Proxy)

nginx
server {
    listen 443 ssl;
    server_name tu-dominio.com;
    
    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/private.key;
    
    location /webhook/tsalva {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Timeouts
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

Docker Compose

yaml
version: '3.8'

services:
  webhook-app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - TSALVA_API_KEY=${TSALVA_API_KEY}
      - NODE_ENV=production
    restart: unless-stopped
    
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - webhook-app
    restart: unless-stopped

Monitoreo y Logs

Logging Estructurado

javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'webhook-errors.log', level: 'error' }),
    new winston.transports.File({ filename: 'webhook-combined.log' })
  ]
});

app.put('/webhook/tsalva', (req, res) => {
  const startTime = Date.now();
  
  try {
    // Procesar webhook...
    
    logger.info('Webhook procesado exitosamente', {
      servicio: req.body.idServicio,
      estado: req.body.codigoEstado,
      processingTime: Date.now() - startTime
    });
    
  } catch (error) {
    logger.error('Error procesando webhook', {
      error: error.message,
      stack: error.stack,
      body: req.body,
      processingTime: Date.now() - startTime
    });
  }
});

Métricas y Alertas

javascript
// Prometheus metrics example
const client = require('prom-client');

const webhookCounter = new client.Counter({
  name: 'tsalva_webhooks_total',
  help: 'Total de webhooks recibidos',
  labelNames: ['estado', 'status']
});

const webhookDuration = new client.Histogram({
  name: 'tsalva_webhook_duration_seconds',
  help: 'Duración del procesamiento de webhooks',
  buckets: [0.1, 0.5, 1, 2, 5]
});

app.put('/webhook/tsalva', (req, res) => {
  const end = webhookDuration.startTimer();
  
  try {
    // Procesar webhook...
    
    webhookCounter.inc({ 
      estado: req.body.codigoEstado, 
      status: 'success' 
    });
    
  } catch (error) {
    webhookCounter.inc({ 
      estado: req.body.codigoEstado, 
      status: 'error' 
    });
    throw error;
  } finally {
    end();
  }
});

Troubleshooting

Problemas Comunes

ProblemaSíntomaSolución
API Key incorrectaHTTP 401Verificar variable de entorno
TimeoutNo recibe webhooksOptimizar procesamiento
JSON malformadoHTTP 400Validar parsing de JSON
SSL/TLSConexión rechazadaVerificar certificado

Debug Local con ngrok

bash
# Terminal 1: Iniciar tu aplicación
npm start

# Terminal 2: Exponer con ngrok
ngrok http 3000

# Terminal 3: Probar webhook
curl -X PUT "https://abc123.ngrok.io/webhook/tsalva" \
  -H "x-apikey: tu_api_key_de_prueba" \
  -H "Content-Type: application/json" \
  -d '{
    "idServicio": "TEST_123",
    "codigoEstado": "TEST",
    "dniGestor": "00000000",
    "detalleEstado": { "test": true }
  }'

Validar Configuración

bash
# Verificar que tu endpoint responde
curl -X PUT "https://tu-dominio.com/webhook/tsalva" \
  -H "x-apikey: tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

# Respuesta esperada:
# {"received": true}

Recomendación

Implementa un endpoint /health para que el equipo de TSALVA pueda verificar que tu servicio está funcionando correctamente.

Importante

  • Tu endpoint debe responder en menos de 30 segundos
  • Para procesamientos largos, usa colas asíncronas
  • Siempre responde con 200 Correcto si el webhook fue recibido correctamente

Tsalva API - Documentación desarrollada por RobPixels