Voltar ao blog
webhooksapireal-timedevelopersintegration

Webhooks vs polling: notificações de exames em tempo real para fluxos de imagem odontológica

Cristian Rosas Méndez

Por

Toda integração de imagem odontológica acaba enfrentando a mesma pergunta arquitetural: como o app do dentista solicitante descobre que uma tomografia CBCT ficou pronta? Se a sua resposta é "fazemos polling à API a cada 30 segundos", você está introduzindo latência, gastando invocações serverless e drenando bateria de celular. Existe um caminho padrão melhor — webhooks event-driven com verificação HMAC e retries idempotentes — e este post defende a troca.

Os dois padrões em um parágrafo

Polling significa que seu código pergunta "já ficou pronto?" em um timer. Webhooks significa que o CBCTHub chama seu endpoint no instante em que o evento acontece. Polling é pull, webhooks é push. Ambos funcionam. Têm perfis muito diferentes de custo, latência e operação.

Comparação lado a lado

DimensãoPolling (a cada 30 s)Webhooks
Latência mediana15 s (metade do intervalo)< 1 s
Latência pior caso30 s + orçamento de retriessegundos, com retries por cima
Invocações cloud por 100 exames (upload de 10 min)~2.000~100
Impacto na bateria do celularAlto — acorda o rádio a cada cicloNenhum — o servidor empurra
Atrás de firewall / em laptopFunciona sem problemaPrecisa de URL pública
DepurabilidadeTrivial — você repete qualquer requestPrecisa de logs de entrega + replay
BackpressureNatural — você controla a frequênciaO provedor precisa implementar filas

Para fluxos típicos entre PMS e CBCTHub em que você quer que o app do dentista atualize no segundo exato em que o exame fica pronto, webhooks ganham em todas as dimensões que importam ao usuário final.

Quando polling ainda faz sentido

Webhooks não são uma religião. Use polling quando:

  • Você está depurando localmente e não quer montar um túnel ngrok ou Cloudflare até seu laptop.
  • Seu sistema não consegue expor URL pública — por exemplo, um PMS legado rodando na LAN da clínica sem IP público nem proxy reverso.
  • Você processa resultados em batch — um cron noturno que importa os exames do dia funciona bem com polling.
  • O evento é raro e você já faz polling em um gatilho de UX (por exemplo, quando o usuário abre a lista de exames).

Todo o resto — notificações em tempo real para o dentista, relays de push mobile, telas de status na sala de espera — pertence a webhooks.

Como o CBCTHub assina os webhooks

Cada entrega de webhook inclui dois headers:

  • X-CBCTHub-Signature: HMAC-SHA256 do corpo bruto da requisição, assinado com o segredo que você viu uma única vez ao criar a assinatura.
  • X-CBCTHub-Delivery: um ID único desta tentativa de entrega. Use como sua chave de idempotência.

Verifique a assinatura antes de qualquer outra coisa. Se falhar, responda 401 e registre a tentativa — ou o segredo está mal configurado ou alguém está procurando vulnerabilidade.

Verificação em 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('assinatura inválida');
    }

    const deliveryId = req.header('X-CBCTHub-Delivery');
    const event = JSON.parse(req.body.toString());

    // Idempotência — devolva 200 se já processou esta entrega
    if (await deliveries.has(deliveryId)) return res.status(200).send('duplicada');
    await deliveries.add(deliveryId);

    await handleEvent(event); // sua regra de negócio
    return res.status(200).send('ok');
  }
);

Verificação em 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, 'assinatura 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'}

Idempotência: a regra que você não pode pular

O CBCTHub repete as entregas que não recebem 2xx, com backoff exponencial por até 24 horas. Isso significa que seu endpoint receberá o mesmo evento mais de uma vez em algum momento, seja por um 5xx vazado ou por timeout. Use X-CBCTHub-Delivery como chave de deduplicação no Redis, numa tabela do banco ou até num cache LRU, e responda 200 na segunda tentativa sem reexecutar efeitos colaterais.

Test mode: validar sem tocar em produção

Gere uma API key cbct_test_, registre um webhook contra ela e dispare um evento de teste no dashboard. Eventos disparados por uma key de teste trazem "livemode": false no payload, então seu código pode roteá-los a um banco de staging mantendo o mesmo handler. É a forma mais segura de subir uma integração de webhooks em produção sem risco de uma entrega real chegar na caixa errada.

Padrão: webhooks → fila durável → workers

O padrão mais robusto em produção mantém pequeno o handler de webhook: verifica assinatura, enfileira, responde 200. Um pool separado de workers consome a fila e faz o trabalho lento. Isso isola o ack do webhook da sua regra de negócio, então uma travada do banco nunca faz o CBCTHub recuar.

Stacks concretas:

  • Vercel: rota do webhook → Inngest ou QStash → funções em background.
  • AWS: API Gateway → SQS → consumers Lambda.
  • Self-hosted: Express → stream do Redis → workers BullMQ.
  • Cloudflare: Worker → Queue → Worker consumer.

Quando escolher o quê

Por padrão, webhooks para qualquer evento em tempo real voltado ao usuário. Deixe polling para importações noturnas, ferramentas de debug e sistemas legados on-prem que não aceitam tráfego entrante. O híbrido também vale — várias integrações em produção usam os dois, com webhooks como caminho principal e um poll diário de reconciliação como rede de segurança.

Conclusão

Polling é a resposta fácil no começo e a errada no longo prazo. Webhooks com assinatura HMAC, handlers idempotentes e uma fila pequena custam menos, escalam melhor e dão ao usuário final uma experiência em tempo real. O sistema de entrega de webhooks do CBCTHub foi construído para isso — retries automáticos, UI de replay, separação de livemode — então só falta você construir o receiver.

Leia a referência de webhooks · Crie uma conta grátis

Experimente o CBCTHub grátis

Envie, visualize e compartilhe exames DICOM na nuvem. Sem instalar nada.

Criar conta grátis