Configurar Webhooks Biométricos Sin IP Fija
Tus dispositivos biométricos no necesitan una IP fija ni un servidor local. Aprende a configurar webhooks en la nube que envían datos de asistencia en tiempo real a cualquier endpoint — con código listo para producción en Node.js, Python y cURL.
Introduction
Tu reloj checador biométrico está en la recepción de la oficina. Tu aplicación corre en AWS. El dispositivo no puede alcanzar tu servidor, y tu servidor no puede alcanzar el dispositivo. Ese es el problema fundamental que todo equipo enfrenta cuando intenta obtener datos de asistencia en tiempo real en su aplicación en la nube.
La solución tradicional — un servidor local con IP fija corriendo 24/7 — fue diseñada para un mundo donde las aplicaciones vivían on-premise. En 2026, tu app corre en Vercel, Railway, Render o un clúster Kubernetes. Necesitas webhooks, no bucles de polling.
Esta guía te lleva paso a paso por la configuración de webhooks biométricos con PunchConnect. De cero a recibir eventos de fichaje en vivo en menos de 15 minutos. Sin IP fija. Sin VPN. Sin servidor local.
Cómo Funcionan los Webhooks Biométricos
Un webhook es simplemente una petición HTTP POST que PunchConnect envía a tu servidor cada vez que ocurre algo — un empleado ficha, se registra una nueva huella, o un dispositivo se desconecta. Le das una URL a PunchConnect y te envía los eventos en tiempo real.
La diferencia clave con las instalaciones tradicionales: tu servidor no hace polling. No se conecta al dispositivo. Simplemente escucha peticiones HTTP entrantes como cualquier endpoint web normal.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 280" fill="none" style="width:100%;max-width:800px;">
<text x="400" y="25" text-anchor="middle" fill="#94a3b8" font-size="17" font-weight="bold" font-family="system-ui">Flujo Webhook: Dispositivo → Nube → Tu App</text>
<rect x="20" y="80" width="160" height="80" rx="12" stroke="#22d3ee" stroke-width="2" fill="none"/>
<text x="100" y="115" text-anchor="middle" fill="#22d3ee" font-size="15" font-family="system-ui">🖐 Reloj</text>
<text x="100" y="137" text-anchor="middle" fill="#22d3ee" font-size="15" font-family="system-ui">Checador</text>
<line x1="180" y1="120" x2="260" y2="120" stroke="#64748b" stroke-width="2" marker-end="url(#whArrowEs)"/>
<text x="220" y="108" text-anchor="middle" fill="#64748b" font-size="13" font-family="system-ui">push</text>
<rect x="260" y="80" width="180" height="80" rx="12" stroke="#a78bfa" stroke-width="2" fill="none"/>
<text x="350" y="115" text-anchor="middle" fill="#a78bfa" font-size="15" font-family="system-ui">☁️ PunchConnect</text>
<text x="350" y="137" text-anchor="middle" fill="#a78bfa" font-size="15" font-family="system-ui">Nube</text>
<line x1="440" y1="120" x2="520" y2="120" stroke="#64748b" stroke-width="2" marker-end="url(#whArrowEs)"/>
<text x="480" y="108" text-anchor="middle" fill="#64748b" font-size="13" font-family="system-ui">webhook</text>
<rect x="520" y="80" width="160" height="80" rx="12" stroke="#34d399" stroke-width="2" fill="none"/>
<text x="600" y="115" text-anchor="middle" fill="#34d399" font-size="15" font-family="system-ui">💻 Tu App</text>
<text x="600" y="137" text-anchor="middle" fill="#34d399" font-size="15" font-family="system-ui">(Cualquier Nube)</text>
<rect x="260" y="200" width="180" height="50" rx="10" stroke="#fb923c" stroke-width="1.5" fill="none"/>
<text x="350" y="230" text-anchor="middle" fill="#fb923c" font-size="14" font-family="system-ui">🔄 Cola de Reintentos (72h)</text>
<line x1="350" y1="160" x2="350" y2="200" stroke="#fb923c" stroke-width="1.5" stroke-dasharray="5,4" marker-end="url(#whArrowEs2)"/>
<text x="375" y="185" fill="#64748b" font-size="12" font-family="system-ui">si 5xx</text>
<defs>
<marker id="whArrowEs" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#64748b"/></marker>
<marker id="whArrowEs2" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#fb923c"/></marker>
</defs>
</svg>
Paso a paso:
1. El empleado ficha en el dispositivo biométrico (huella, rostro, tarjeta)
2. El dispositivo envía el evento al motor de protocolo de PunchConnect (saliente desde el dispositivo — no necesita IP fija)
3. PunchConnect normaliza los datos crudos en un payload JSON limpio
4. El webhook se dispara — PunchConnect envía un HTTP POST a tu URL de callback registrada
5. Tu app procesa el evento (actualizar base de datos, activar workflow, enviar notificación)
Si tu endpoint devuelve un error 5xx o se agota el tiempo, PunchConnect reintenta automáticamente con backoff exponencial durante 72 horas. No se pierden datos de fichaje.
Paso 1: Crear Tu Endpoint Webhook
Tu endpoint webhook es simplemente una ruta HTTP normal que acepta peticiones POST:
Node.js (Express):
Python (Flask):
```python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook/asistencia', methods=['POST'])
def handle_punch():
data = request.json
employee_id = data['employee_id']
timestamp = data['timestamp']
punch_type = data['punch_type']
print(f"Fichaje recibido: Empleado {employee_id} a las {timestamp}")
# Tu lógica: guardar en BD, notificar al gerente, actualizar ERP...
return jsonify({"received": True}), 200
if __name__ == '__main__':
app.run(port=3000)
```
Despliega esto en cualquier plataforma cloud — Railway, Render, DigitalOcean, AWS Lambda, un VPS. Esa URL se convierte en tu callback.
const express = require('express');const app = express();app.use(express.json());app.post('/webhook/asistencia', (req, res) => {const { event_id, employee_id, timestamp, device_serial, punch_type } = req.body;console.log(`Fichaje recibido: Empleado ${employee_id} a las ${timestamp}`);// Tu lógica: guardar en BD, notificar al gerente, actualizar ERP...res.status(200).json({ received: true });});app.listen(3000, () => console.log('Webhook listener en el puerto 3000'));
Paso 2: Registrar Tu Webhook con PunchConnect
Usa la API de PunchConnect para indicarle al sistema dónde enviar los eventos:
El campo secret es fundamental — PunchConnect lo usa para firmar cada payload webhook para que puedas verificar la autenticidad.
curl -X POST https://api.punchconnect.com/v1/webhooks \-H "Authorization: Bearer TU_CLAVE_API" \-H "Content-Type: application/json" \-d '{"url": "https://tu-app.railway.app/webhook/asistencia","events": ["attendance.punch"],"secret": "tu-secreto-webhook"}'
Paso 3: Configurar Tu Dispositivo
Agrega tu dispositivo biométrico a PunchConnect a través del panel de control. El dispositivo se conecta hacia afuera al cloud de PunchConnect — sin redirección de puertos, sin IP fija, sin VPN.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 260" fill="none" style="width:100%;max-width:800px;">
<text x="400" y="25" text-anchor="middle" fill="#94a3b8" font-size="17" font-weight="bold" font-family="system-ui">Arquitectura Tradicional vs. Webhook</text>
<text x="200" y="55" text-anchor="middle" fill="#ef4444" font-size="15" font-weight="bold" font-family="system-ui">❌ Tradicional (IP Fija)</text>
<rect x="50" y="70" width="120" height="50" rx="8" stroke="#ef4444" stroke-width="1.5" fill="none"/>
<text x="110" y="100" text-anchor="middle" fill="#ef4444" font-size="14" font-family="system-ui">Dispositivo</text>
<line x1="170" y1="95" x2="210" y2="95" stroke="#ef4444" stroke-width="1.5" marker-end="url(#redArrEs)"/>
<rect x="210" y="70" width="140" height="50" rx="8" stroke="#ef4444" stroke-width="1.5" fill="none"/>
<text x="280" y="95" text-anchor="middle" fill="#ef4444" font-size="13" font-family="system-ui">Servidor Local</text>
<text x="280" y="110" text-anchor="middle" fill="#ef4444" font-size="11" font-family="system-ui">(IP fija, 24/7)</text>
<text x="200" y="145" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">⚠️ Cambio de IP = pérdida</text>
<text x="600" y="55" text-anchor="middle" fill="#34d399" font-size="15" font-weight="bold" font-family="system-ui">✅ Webhook (Nube)</text>
<rect x="460" y="70" width="120" height="50" rx="8" stroke="#34d399" stroke-width="1.5" fill="none"/>
<text x="520" y="100" text-anchor="middle" fill="#34d399" font-size="14" font-family="system-ui">Dispositivo</text>
<line x1="580" y1="95" x2="620" y2="95" stroke="#34d399" stroke-width="1.5" marker-end="url(#greenArrEs)"/>
<rect x="620" y="70" width="140" height="50" rx="8" stroke="#34d399" stroke-width="1.5" fill="none"/>
<text x="690" y="90" text-anchor="middle" fill="#34d399" font-size="13" font-family="system-ui">PunchConnect</text>
<text x="690" y="105" text-anchor="middle" fill="#34d399" font-size="11" font-family="system-ui">(nube, gestionado)</text>
<line x1="690" y1="120" x2="690" y2="160" stroke="#34d399" stroke-width="1.5" marker-end="url(#greenArrEs)"/>
<rect x="620" y="160" width="140" height="50" rx="8" stroke="#a78bfa" stroke-width="1.5" fill="none"/>
<text x="690" y="185" text-anchor="middle" fill="#a78bfa" font-size="13" font-family="system-ui">Tu App Cloud</text>
<text x="690" y="200" text-anchor="middle" fill="#a78bfa" font-size="11" font-family="system-ui">(donde sea)</text>
<text x="600" y="240" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">✅ Sin IP fija. Sin servidor local.</text>
<defs>
<marker id="redArrEs" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#ef4444"/></marker>
<marker id="greenArrEs" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#34d399"/></marker>
</defs>
</svg>
La configuración toma aproximadamente 5 minutos por dispositivo:
1. Agregar el dispositivo (número de serie + modelo)
2. Apuntar la dirección del servidor del dispositivo al endpoint de PunchConnect
3. El dispositivo se conecta automáticamente y empieza a enviar datos
4. PunchConnect reenvía los eventos a tu webhook registrado
Paso 4: Verificar Firmas de Webhook
Nunca confíes en un webhook entrante ciegamente. PunchConnect firma cada payload usando HMAC-SHA256 con tu secreto webhook. Siempre verifica antes de procesar.
Esto es especialmente importante si tus clientes están en México (cumplimiento con la Ley Federal de Protección de Datos Personales) o Colombia (Ley 1581 de Habeas Data). PunchConnect firma cada evento para garantizar la integridad de los datos biométricos.
const crypto = require('crypto');function verifyWebhookSignature(payload, signature, secret) {const expected = crypto.createHmac('sha256', secret).update(JSON.stringify(payload)).digest('hex');return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));}
Paso 5: Manejar Reintentos e Idempotencia
PunchConnect reintenta las entregas fallidas con backoff exponencial: 1 min → 5 min → 15 min → 1 hora → 4 horas, durante 72 horas máximo.
Siempre implementa idempotencia con el campo `event_id`:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 200" fill="none" style="width:100%;max-width:800px;">
<text x="400" y="25" text-anchor="middle" fill="#94a3b8" font-size="17" font-weight="bold" font-family="system-ui">Secuencia de Reintentos con Backoff Exponencial</text>
<line x1="60" y1="100" x2="740" y2="100" stroke="#334155" stroke-width="2"/>
<circle cx="100" cy="100" r="8" fill="#34d399"/>
<text x="100" y="80" text-anchor="middle" fill="#34d399" font-size="14" font-family="system-ui">1ro</text>
<text x="100" y="130" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">0 min</text>
<circle cx="220" cy="100" r="8" fill="#fb923c"/>
<text x="220" y="80" text-anchor="middle" fill="#fb923c" font-size="14" font-family="system-ui">2do</text>
<text x="220" y="130" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">+1 min</text>
<circle cx="340" cy="100" r="8" fill="#fb923c"/>
<text x="340" y="80" text-anchor="middle" fill="#fb923c" font-size="14" font-family="system-ui">3ro</text>
<text x="340" y="130" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">+5 min</text>
<circle cx="460" cy="100" r="8" fill="#fb923c"/>
<text x="460" y="80" text-anchor="middle" fill="#fb923c" font-size="14" font-family="system-ui">4to</text>
<text x="460" y="130" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">+15 min</text>
<circle cx="580" cy="100" r="8" fill="#fb923c"/>
<text x="580" y="80" text-anchor="middle" fill="#fb923c" font-size="14" font-family="system-ui">5to</text>
<text x="580" y="130" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">+1 h</text>
<circle cx="700" cy="100" r="8" fill="#fb923c"/>
<text x="700" y="80" text-anchor="middle" fill="#fb923c" font-size="14" font-family="system-ui">6to</text>
<text x="700" y="130" text-anchor="middle" fill="#64748b" font-size="12" font-family="system-ui">+4 h</text>
<text x="400" y="170" text-anchor="middle" fill="#64748b" font-size="14" font-family="system-ui">Reintentos durante 72 horas — sin pérdida de datos</text>
</svg>
const processedEvents = new Set(); // Usa Redis o BD en producciónapp.post('/webhook/asistencia', (req, res) => {const { event_id } = req.body;if (processedEvents.has(event_id)) {return res.status(200).json({ received: true, duplicate: true });}processedEvents.add(event_id);// Procesar el evento...res.status(200).json({ received: true });});
Checklist de Producción
Antes de pasar a producción:
- ✅ Endpoint HTTPS — PunchConnect no entrega a HTTP plano en producción
- ✅ Verificación de firma — valida cada petición con HMAC-SHA256
- ✅ Idempotencia — maneja duplicados con event_id
- ✅ Respuesta 200 en menos de 10 segundos — responde rápido, procesa async si necesitas
- ✅ Registro — guarda cada webhook para depuración
- ✅ Cumplimiento normativo — documenta el tratamiento de datos biométricos según la legislación local
Guías Relacionadas
- Asistencia Biométrica Sin IP Fija — por qué las IP fijas son el enfoque equivocado
- Integración Webhook ZKTeco — configuración específica para terminales ZKTeco
- API REST de Dispositivo Biométrico — referencia API completa con ejemplos de código
- Seguridad de Datos Biométricos en Tránsito — cifrado, firmas y cumplimiento
Preguntas Frecuentes
¿Necesito una IP fija para recibir webhooks?
No. Ese es el punto. Tu endpoint webhook solo necesita una URL pública — cualquier plataforma cloud (AWS, DigitalOcean, Railway, Render) la proporciona automáticamente.
¿Qué pasa si mi servidor está caído cuando ocurre un fichaje?
PunchConnect encola el evento y reintenta con backoff exponencial durante 72 horas. Cuando tu servidor vuelva en línea, todos los eventos pendientes se entregan en orden. Cero pérdida de datos.
¿Puedo recibir webhooks en una función serverless?
Sí. Los webhooks de PunchConnect funcionan con AWS Lambda, Vercel Functions, Cloudflare Workers, Google Cloud Functions — cualquier plataforma que pueda recibir peticiones HTTP POST.
¿Cómo depuro las entregas webhook fallidas?
El panel de control de PunchConnect muestra un registro completo de cada webhook: cuerpo de la petición, código de respuesta, tiempo de respuesta e historial de reintentos. También puedes reenviar cualquier entrega fallida con un clic.
¿Funciona con los relojes checadores más comunes en Latinoamérica?
Sí. PunchConnect soporta la mayoría de dispositivos ZKTeco (incluyendo modelos populares en México, Colombia, Perú y Argentina como los de la serie MB, uFace y SpeedFace). La configuración es la misma para todos los modelos compatibles.
---
*¿Listo para recibir datos de asistencia en tiempo real en tu app en la nube? Comienza tu prueba gratuita — primer dispositivo gratis, configuración en menos de 15 minutos.*
Artículos relacionados
Conectar ZKTeco a Odoo: Integración Cloud vía API REST (Sin VPN ni Software Local)
10 min read
TutorialIntegración Webhook ZKTeco: Pipelines de Asistencia en Tiempo Real con API REST
12 min read
TutorialIntegración API de Lector de Huellas: Guía para Desarrolladores sobre Biometría Cloud
8 min read