Webhooks vs polling: notificaciones de exámenes en tiempo real para flujos de imagen dental
Toda integración de imagen dental termina enfrentando la misma pregunta arquitectónica: ¿cómo se entera la app del dentista derivador de que una tomografía CBCT está lista? Si tu respuesta es "hacemos polling a la API cada 30 segundos", estás introduciendo latencia, gastando invocaciones serverless y descargando baterías de móvil. Hay un mejor camino por defecto — webhooks event-driven con verificación HMAC y reintentos idempotentes — y este post argumenta por qué deberías cambiar.
Los dos patrones en un párrafo
Polling significa que tu código pregunta "¿ya está listo?" en un temporizador. Webhooks significa que CBCTHub llama a tu endpoint en el instante en que ocurre el evento. Polling es pull, webhooks es push. Ambos funcionan. Tienen perfiles de costo, latencia y operación muy distintos.
Comparación lado a lado
| Dimensión | Polling (cada 30 s) | Webhooks |
|---|---|---|
| Latencia mediana | 15 s (mitad del intervalo) | < 1 s |
| Latencia peor caso | 30 s + presupuesto de reintentos | segundos, con reintentos encima |
| Invocaciones cloud por 100 exámenes (subida de 10 min) | ~2.000 | ~100 |
| Impacto en batería móvil | Alto — despierta la radio cada ciclo | Ninguno — el servidor empuja |
| Detrás de firewall / en laptop | Funciona sin problema | Requiere URL pública |
| Depurabilidad | Trivial — repites cualquier request | Necesita logs de entrega + replay |
| Contrapresión (backpressure) | Natural — tú controlas la frecuencia | El proveedor debe implementar colas |
Para flujos típicos entre PMS y CBCTHub donde quieres que la app del dentista se actualice en el segundo exacto en que el examen está listo, los webhooks ganan en todas las dimensiones que importan al usuario final.
Cuándo el polling todavía tiene sentido
Los webhooks no son una religión. Recurre al polling cuando:
- Estás depurando localmente y no quieres montar un túnel ngrok o Cloudflare hacia tu laptop.
- Tu sistema no puede exponer una URL pública — por ejemplo, un PMS legacy desplegado en la LAN de la clínica sin IP pública ni proxy inverso.
- Procesas resultados en batch — un cron nocturno que importa los exámenes del día funciona perfecto con polling.
- El evento es raro y ya haces polling en un trigger UX (por ejemplo, cuando el usuario abre la lista de exámenes).
Todo lo demás — notificaciones en tiempo real al dentista, relays de push móvil, pantallas de estado en la sala de espera — pertenece a webhooks.
Cómo firma CBCTHub los webhooks
Cada entrega de webhook incluye dos cabeceras:
X-CBCTHub-Signature: HMAC-SHA256 del cuerpo crudo del request, firmado con el secreto que viste una sola vez cuando creaste la subscripción.X-CBCTHub-Delivery: un ID único de este intento de entrega. Úsalo como tu clave de idempotencia.
Verifica la firma antes de hacer cualquier otra cosa. Si falla la verificación, responde 401 y registra el intento — o el secreto está mal configurado o alguien busca una vulnerabilidad.
Verificación en Node.js / Express
import crypto from 'crypto';
import express from 'express';
const app = express();
app.post(
'/cbcthub/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.header('X-CBCTHub-Signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.CBCTHUB_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (
sig.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
) {
return res.status(401).send('firma inválida');
}
const deliveryId = req.header('X-CBCTHub-Delivery');
const event = JSON.parse(req.body.toString());
// Idempotencia — devuelve 200 si ya procesaste esta entrega
if (await deliveries.has(deliveryId)) return res.status(200).send('duplicada');
await deliveries.add(deliveryId);
await handleEvent(event); // tu lógica de negocio
return res.status(200).send('ok');
}
);
Verificación en Python / FastAPI
import hmac, hashlib, os
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
SECRET = os.environ['CBCTHUB_WEBHOOK_SECRET'].encode()
@app.post('/cbcthub/webhook')
async def webhook(req: Request):
body = await req.body()
sig = req.headers.get('x-cbcthub-signature', '')
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
raise HTTPException(401, 'firma invalida')
delivery_id = req.headers.get('x-cbcthub-delivery')
event = await req.json()
if await deliveries.contains(delivery_id):
return {'status': 'duplicada'}
await deliveries.add(delivery_id)
await handle_event(event)
return {'status': 'ok'}
Idempotencia: la regla que no puedes saltarte
CBCTHub reintenta las entregas que no reciben 2xx, con backoff exponencial hasta por 24 horas. Eso significa que tu endpoint recibirá el mismo evento más de una vez en algún momento, ya sea porque hubo un 5xx o porque la respuesta dio timeout. Usa X-CBCTHub-Delivery como clave de deduplicación en Redis, una tabla de la base de datos o incluso una caché LRU, y responde 200 en el segundo intento sin volver a ejecutar los efectos secundarios.
Test mode: valida sin tocar producción
Genera una API key cbct_test_, registra un webhook contra ella y dispara un evento de prueba desde el dashboard. Los eventos disparados con una key de prueba traen "livemode": false en el payload, así tu código puede rutearlos a una base de staging y seguir corriendo el mismo handler. Es la forma más segura de poner una integración de webhooks en producción sin riesgo de que una entrega real termine en la bandeja equivocada.
Patrón: webhooks → cola durable → workers
El patrón más robusto en producción mantiene pequeño tu handler de webhook: verifica firma, encola, responde 200. Un pool separado de workers consume la cola y hace el trabajo lento. Esto aísla el acuse del webhook de tu lógica de negocio, así un hipo de la base nunca hace que CBCTHub retroceda.
Stacks concretos:
- Vercel: ruta del webhook → Inngest o QStash → funciones en background.
- AWS: API Gateway → SQS → consumidores Lambda.
- Self-hosted: Express → stream de Redis → workers BullMQ.
- Cloudflare: Worker → Queue → Worker consumidor.
Cuándo elegir qué
Por defecto, webhooks para cualquier evento en tiempo real que mire al usuario. Deja el polling para importaciones nocturnas, herramientas de debug y sistemas legacy on-prem que no aceptan tráfico entrante. El híbrido también vale — muchas integraciones en producción usan ambos, con webhooks como camino principal y un poll diario de reconciliación como red de seguridad.
Conclusión
El polling es la respuesta fácil al inicio y la equivocada a largo plazo. Los webhooks con firma HMAC, handlers idempotentes y una cola pequeña cuestan menos, escalan mejor y dan a los usuarios finales una experiencia en tiempo real. El sistema de entrega de webhooks de CBCTHub está construido para esto — reintentos automáticos, UI de replay, separación de livemode — así que lo único que te queda por construir es el receptor.
Prueba CBCTHub gratis
Sube, visualiza y comparte examenes DICOM en la nube. Sin instalar nada.
Crear cuenta gratisArticulos relacionados
Cómo integrar tu software de gestión dental con CBCTHub: tutorial completo de la API
Guía paso a paso para integrar un PMS dental con la API REST de CBCTHub. Sube exámenes, embebe el visor 3D y automatiza la entrega en Node, Python y PHP.
APIs de imagen dental con cumplimiento HIPAA: lista de verificación de seguridad para integraciones en salud de EE. UU.
Lista de 10 puntos para garantizar el cumplimiento HIPAA en cualquier integración de API de imagen dental en EE. UU. Cifrado, BAAs, audit logs, notificación de brechas.
Proteccion de datos de pacientes en Mexico: guia LFPDPPP, INAI y NOM-024 para centros radiologicos dentales
Como cumplir la LFPDPPP, su Reglamento, la NOM-024-SSA3-2012 y la NOM-004 en una clinica de imagen dental. INAI, Aviso de Privacidad, multas en UMA. Como ayuda CBCTHub.