Voltar ao blog
apiintegrationtutorialpmsdevelopers

Como integrar seu software de gestão odontológica com o CBCTHub: tutorial completo da API

Cristian Rosas Méndez

Por

Se você desenvolve ou mantém um software de gestão para clínicas odontológicas, seus clientes não deveriam precisar sair do seu app para compartilhar uma tomografia CBCT. Não deveriam gravar CDs, anexar ZIPs DICOM de 800 MB por e-mail, nem copiar nomes de pacientes entre sistemas. Este tutorial percorre uma integração pronta para produção com a API REST do CBCTHub: criar o exame, enviar os arquivos DICOM, confirmar, escutar webhooks e embutir o visualizador dentro do seu PMS.

Tudo abaixo aponta para https://cbcthub.com/api/v1 e funciona contra uma conta real. Os exemplos estão em Node.js, Python e PHP.

O que você ganha integrando

  • Um único fluxo para o usuário da recepção: envia o DICOM de dentro do PMS e recebe um link compartilhável automaticamente.
  • Visualizador 3D embutido na ficha do paciente, sem instalar plugins.
  • Entrega automática por e-mail ou WhatsApp ao dentista solicitante, com o logo e a marca da clínica.
  • Notificações via webhook para que seu PMS marque a consulta como "entregue" no momento exato em que o exame fica pronto.

Pré-requisitos

  • Uma conta CBCTHub (Free serve para desenvolvimento, Pro ou superior para produção).
  • Uma API key em Configurações → API keys. Formato: cbct_live_... para produção ou cbct_test_... para sandbox.
  • Os scopes exams:read e exams:write.
  • Um ambiente server-side. Nunca chame a API pelo navegador — sua key ficaria exposta.

Verificação rápida

Antes de escrever código, confirme que a key funciona:

curl -H "Authorization: Bearer cbct_test_..." \
  https://cbcthub.com/api/v1/me

Uma resposta 200 com seu user_id, plano e cota de armazenamento confirma que você está pronto.

Passo 1: Criar o exame

Faça POST em /api/v1/exams com os metadados do paciente e a lista de arquivos ou o tamanho do ZIP. O endpoint suporta dois modos de envio:

  • upload_mode: "files" (padrão): você envia N arquivos e recebe N URLs pré-assinadas. Recomendado quando seu PMS já conhece a estrutura do DICOM.
  • upload_mode: "zip": você envia um único ZIP e o servidor descompacta. Recomendado quando o tomógrafo gera uma pasta que você quer enviar como está.

Node.js — modo files

const res = await fetch('https://cbcthub.com/api/v1/exams', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.CBCTHUB_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'CBCT mandíbula — planejamento implante #46',
    exam_type: 'cbct',
    patient_name: 'Marina Souza',
    patient_id: 'CPF-123.456.789-00',
    birth_date: '1978-03-12',
    reason: 'Planejamento de implante no dente 46',
    expiration_days: 90,
    upload_mode: 'files',
    files: dicomFiles.map(f => ({ name: f.name, size: f.size })),
  }),
});

const { exam_id, upload_urls, confirm_url, share_url, viewer_url } = await res.json();

Python — modo ZIP

import os, requests

resp = requests.post(
    'https://cbcthub.com/api/v1/exams',
    headers={
        'Authorization': f"Bearer {os.environ['CBCTHUB_KEY']}",
        'Content-Type': 'application/json',
    },
    json={
        'name': 'CBCT mandíbula — planejamento implante #46',
        'exam_type': 'cbct',
        'patient_name': 'Marina Souza',
        'expiration_days': 90,
        'upload_mode': 'zip',
        'zip_size_bytes': os.path.getsize('estudo.zip'),
    },
    timeout=30,
)
resp.raise_for_status()
data = resp.json()

PHP

$ch = curl_init('https://cbcthub.com/api/v1/exams');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . getenv('CBCTHUB_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'name' => 'CBCT mandíbula — implante #46',
    'exam_type' => 'cbct',
    'patient_name' => 'Marina Souza',
    'expiration_days' => 90,
    'upload_mode' => 'files',
    'files' => $fileList,
  ]),
]);
$response = json_decode(curl_exec($ch), true);

Passo 2: Enviar os arquivos

As URLs pré-assinadas devolvidas pela criação apontam direto para o Cloudflare R2. Seu cliente (ou seu servidor) faz PUT com os bytes brutos: essas requisições não contam contra o rate limit e não passam pelos servidores do CBCTHub. Uma tomografia de 500 MB sobe na velocidade total da sua conexão.

Modo files — envios concorrentes

// Node.js — enviar todos os slices com concorrência limitada a 5
import pLimit from 'p-limit';
const limit = pLimit(5);

await Promise.all(
  upload_urls.map((u, i) =>
    limit(() => fetch(u.url, { method: 'PUT', body: dicomFiles[i].buffer }))
  )
);

Modo ZIP — um único envio + descompactação no servidor

// 1. PUT do ZIP para a URL de staging
await fetch(upload_urls[0].url, {
  method: 'PUT',
  body: fs.readFileSync('estudo.zip'),
});

// 2. Peça ao servidor que processe
const procRes = await fetch(`https://cbcthub.com/api/v1/exams/${exam_id}/process-zip`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.CBCTHUB_KEY}` },
});
// A resposta inclui a contagem final de arquivos e status: 'ready'

Passo 3: Confirmar o exame

No modo files você precisa fechar o exame explicitamente quando todos os PUT terminarem. O modo ZIP confirma automaticamente via /process-zip.

await fetch(confirm_url, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.CBCTHUB_KEY}` },
});

O exame passa de uploading para ready. As URLs de visualizador e de compartilhamento devolvidas no passo 1 ficam ativas.

Passo 4: Receber webhooks

Em vez de fazer polling em loop em GET /v1/exams/{id}, registre um endpoint webhook e deixe que o CBCTHub avise seu PMS quando o exame estiver pronto, for visto ou assinado. Cada evento vem assinado com HMAC SHA-256 no header X-CBCTHub-Signature e traz um identificador único X-CBCTHub-Delivery para idempotência.

// Verificação no Express
import crypto from 'crypto';

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 (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('assinatura inválida');
  }

  const event = JSON.parse(req.body.toString());
  // event.type: 'exam.created' | 'exam.confirmed' | 'report.published' | ...
  return res.status(200).send('ok');
});

O catálogo completo de eventos está na referência de webhooks.

Passo 5: Embutir o visualizador

A forma mais simples de mostrar o exame dentro do seu PMS é inserir um iframe com a viewer_url devolvida pela criação. O visualizador se adapta a celular e suporta MPR, panorâmica, 3D e malhas STL.

<iframe
  src="https://cbcthub.com/viewer/a3f9b1c2-d4e5-..."
  width="100%"
  height="720"
  allow="fullscreen"
  style="border: 0;"
></iframe>

Se precisar de um embed anonimizado (sem o nome do paciente visível), use o endpoint de embed token descrito na referência de endpoints.

Boas práticas em produção

  • Idempotência: se a criação cair por timeout, faça novamente com o mesmo payload — duplicatas são filtradas no servidor com um hash de (patient_id, name, quantidade de arquivos).
  • Retries: backoff exponencial apenas em 429 (respeite o Retry-After) e 5xx. Nunca tente novamente outros 4xx.
  • Gestão de segredos: guarde a key no seu secret manager (AWS Secrets Manager, Vault, Doppler). Rotacione a cada 90 dias.
  • Sandbox primeiro: use keys cbct_test_ durante o desenvolvimento. Os dados de teste ficam isolados e não consomem storage.
  • Observabilidade: registre o header X-Request-ID de cada resposta. É a forma mais rápida de pedir suporte quando algo falha.

Conclusão

Uma integração completa entre um PMS odontológico e o CBCTHub gira em torno de 200 linhas de código: criar, enviar, confirmar, escutar, embutir. A partir daí seus clientes recebem CBCT instantaneamente sem sair do seu produto, e você ganha uma funcionalidade defensável nas conversas de venda com dentistas solicitantes.

Crie uma conta grátis · Leia a documentação completa

Experimente o CBCTHub grátis

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

Criar conta grátis