Apariencia
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/tsalvaOpció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/tsalva3. 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-stoppedMonitoreo 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
| Problema | Síntoma | Solución |
|---|---|---|
| API Key incorrecta | HTTP 401 | Verificar variable de entorno |
| Timeout | No recibe webhooks | Optimizar procesamiento |
| JSON malformado | HTTP 400 | Validar parsing de JSON |
| SSL/TLS | Conexión rechazada | Verificar 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