CBCTHubCBCTHub
PricingBlogHelp
 
 
Back to blog
apiintegrationtutorialpmsdevelopers

How to integrate your dental practice management software with CBCTHub: a complete API tutorial

CBCTHub·June 24, 2026

If you build or maintain a dental practice management system, your customers shouldn't have to leave your app to share a CBCT scan. They shouldn't burn CDs, attach 800 MB DICOM zips to email, or copy patient names between systems. This tutorial walks through a production-ready integration with the CBCTHub REST API: create an exam, upload the DICOM files, confirm, listen for webhooks, and embed the viewer back inside your PMS.

Everything below targets https://cbcthub.com/api/v1 and works against a live account. Code samples are in Node.js, Python and PHP.

What you get out of integrating

  • Single workflow for the front-desk user: upload DICOM from inside the PMS, get a shareable link automatically.
  • Embedded 3D viewer in your patient record screen, no plugin required.
  • Automatic email/WhatsApp delivery to the referring dentist, branded with the clinic's logo.
  • Webhook notifications so your PMS can move the appointment to "delivered" the moment the exam is ready.

Prerequisites

  • A CBCTHub account (Free works for development, Pro or above for production volume).
  • An API key from Settings → API keys. Format: cbct_live_... for production or cbct_test_... for sandbox.
  • Scopes exams:read and exams:write.
  • A server-side environment. Never call the API from a browser — your key would be exposed.

Quick sanity check

Before writing any code, verify the key works:

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

A 200 response with your user_id, plan and storage quota confirms you're ready.

Step 1: Create the exam

POST to /api/v1/exams with patient metadata and either a file list or a single ZIP size. The endpoint supports two upload modes:

  • upload_mode: "files" (default): you send N files and get N presigned URLs back. Best when your PMS already knows the DICOM structure.
  • upload_mode: "zip": you send one ZIP and the server unpacks it. Best when your CBCT scanner produces a folder you want to ship as-is.

Node.js — files mode

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 mandible — implant planning #46',
    exam_type: 'cbct',
    patient_name: 'Sarah Johnson',
    patient_id: 'MRN-449201',
    birth_date: '1978-03-12',
    reason: 'Implant planning, tooth #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 — ZIP mode

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 mandible — implant planning #46',
        'exam_type': 'cbct',
        'patient_name': 'Sarah Johnson',
        'expiration_days': 90,
        'upload_mode': 'zip',
        'zip_size_bytes': os.path.getsize('study.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 mandible — implant #46',
    'exam_type' => 'cbct',
    'patient_name' => 'Sarah Johnson',
    'expiration_days' => 90,
    'upload_mode' => 'files',
    'files' => $fileList,
  ]),
]);
$response = json_decode(curl_exec($ch), true);

Step 2: Upload the files

The presigned URLs returned by the create call point directly at Cloudflare R2. Your client (or server) does PUTs with the raw bytes — these requests do not count against your API rate limit and don't proxy through CBCTHub. A 500 MB CBCT scan uploads at the full speed of your connection.

Files mode — concurrent uploads

// Node.js — upload all slices with a concurrency limit of 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 }))
  )
);

ZIP mode — single upload + server-side unpack

// 1. PUT the ZIP to the staging URL
await fetch(upload_urls[0].url, {
  method: 'PUT',
  body: fs.readFileSync('study.zip'),
});

// 2. Ask the server to unpack it
const procRes = await fetch(`https://cbcthub.com/api/v1/exams/${exam_id}/process-zip`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.CBCTHUB_KEY}` },
});
// Response includes the final file count and status: 'ready'

Step 3: Confirm the exam

In files mode you must explicitly close the exam after every PUT succeeds. ZIP mode confirms automatically via /process-zip.

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

The exam now transitions from uploading to ready. The share and viewer URLs returned in step 1 become active.

Step 4: Receive webhooks

Instead of polling GET /v1/exams/{id} in a loop, register a webhook endpoint and let CBCTHub notify your PMS when the exam is ready, viewed or signed. Each event is signed with HMAC SHA-256 in the X-CBCTHub-Signature header and carries a unique X-CBCTHub-Delivery id for idempotency.

// Express verify
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('invalid signature');
  }

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

See the full event catalog in the webhooks reference.

Step 5: Embed the viewer

The simplest way to surface the exam inside your PMS is to drop an iframe with the viewer_url CBCTHub returned at creation time. The viewer adapts to mobile and supports MPR, panoramic reconstruction, 3D and STL meshes.

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

If you need an anonymized embed (no patient name visible), use the dedicated embed token endpoint described in the endpoint reference.

Production best practices

  • Idempotency: if your create call times out, retry with the same payload — duplicate exams are filtered on the server using a hash of (patient_id, name, file count).
  • Retries: exponential backoff on 429 (respect Retry-After) and 5xx only. Never retry 4xx other than 429.
  • Secret management: store the key in your secret manager (AWS Secrets Manager, Vault, Doppler). Rotate every 90 days.
  • Sandbox first: use cbct_test_ keys during development. Test data is isolated and doesn't consume storage.
  • Observability: log the X-Request-ID response header on every call. It's the fastest way to get help if something goes wrong.

Wrap up

A complete dental PMS ↔ CBCTHub integration is roughly 200 lines of code: create, upload, confirm, listen, embed. From there your customers get instant CBCT delivery without leaving your product, and you have a defensible feature in your sales conversations with referring dentists.

Create a free account · Read the full API docs

Try free viewerSee solutions

Try CBCTHub for free

Upload, view, and share DICOM scans in the cloud. Nothing to install.

Create free account

Related articles

Webhooks vs polling: building real-time exam notifications for dental imaging workflows

Polling every 30 seconds wastes cloud spend and drains mobile batteries. Here is how to build real-time CBCT notifications with HMAC-signed webhooks instead.

HIPAA-compliant dental imaging APIs: security checklist for US healthcare integrations

A 10-point HIPAA security checklist for any dental imaging API integration in the US. Encryption, BAAs, audit logs, breach notification, patient rights.

Best CBCT Viewers 2026: Complete Comparison of 10 Dental Imaging Software Options

In-depth comparison of the best CBCT viewers for dental imaging in 2026. Cloud-based and desktop options, pricing, features, and best-fit scenarios.

CBCTHubCBCTHub

Digital CBCT delivery. 100% local processing. No CDs, ever.

Download on theApp Store
Get it onGoogle Play

Solutions

Imaging centersDental radiologistsOnline CBCT viewer

Product

FeaturesPricingBlogAlternativesLearnEducationNewDevelopersAPIDemo

Support

Help centerFAQContactsoporte@cbcthub.comStatus+56 9 7632 9096

Company

AboutSecurityTerms of servicePrivacy policy

By country

United StatesUnited KingdomCanadaAustralia
HIPAA-readyGDPRLGPDLey 21.719

© 2026 CBCTHub. All rights reserved.

AppLab Software LLC · 1021 E Lincolnway, Cheyenne, WY 82001