For developers

Integrate your website with Consulta

Consulta ships the integration surfaces a clinic actually uses, live today: an inline booking widget, the booking endpoints it runs on, signed webhooks for everything that needs to know a consult happened, and one-click data export. This page documents all of them. A general-purpose partner REST API is roadmap. It gets built the day a partner has a concrete use these surfaces cannot serve, so if that is you, tell us the use case.

The inline booking widget

Two lines of paste. Live consult times render inline on your own page, in your brand colour, in each visitor's own timezone, and the whole booking happens there. It is native HTML in a shadow root, not an iframe: your fonts flow in, your CSS cannot break it, and it degrades to a plain booking button if times cannot load (a failed submission shows an inline error instead, so the visitor's details are never thrown away). Payment, when the patient pays, hands off to Stripe's hosted checkout so card details never touch your site or ours.

<div data-consulta-booking="your-page-slug"></div>
<script async src="https://consultahealth.com.au/widget.js"></script>

Optional attributes

FieldTypeNotes
data-topicstringScope the widget to one consult topic
data-limitnumberHow many times to show, 1-12 (default 6)
data-labelstringText for the fallback button if times cannot load

Your page slug and copy-ready snippets live in your portal under Booking page. A live interactive preview is on the embed page.

Booking endpoints

The widget runs on two public cross-origin endpoints, and you can call them directly from your own form instead. Both are rate limited (a throttled request returns HTTP 429 with a Retry-After header). The booking endpoint additionally carries a honeypot and enforces the same terms, server-side pricing and slot-race handling as every other booking path.

GET /api/embed/slots

GET https://consultahealth.com.au/api/embed/slots?slug=your-page-slug&limit=6
    &topic=optional-topic
FieldTypeNotes
clinicNamestringYour clinic's display name
accentstringYour page's brand colour (hex)
bookUrlstringYour hosted booking page URL
modestringpatient_paid or clinic_paid
priceCentsnumberThe patient price your page shows
payOnlinebooleanWhether booking ends at a Stripe checkout
defaultTopicstringTopic used when none is passed
questionnaireobjectYour page's question set: source, questions[] with key / label / required / placeholder, and acknowledgements[] with key / label. Render your form from this and submit answers by key. A page set to collect its questionnaire outside Consulta returns {source: "clinic"} alone
slots[]{start, doctorId}Next open times, pass both values back when booking

POST /api/embed/book

POST https://consultahealth.com.au/api/embed/book
Content-Type: application/json

{
  "slug": "your-page-slug",
  "doctorId": "<from the slots feed>",
  "slotStart": "<from the slots feed>",
  "topic": "optional-topic",
  "name": "Alex Example",
  "email": "alex@example.com",
  "phone": "0400 000 000",
  "dob": "1990-01-31",
  "timezone": "Australia/Adelaide",
  "termsAccepted": true,
  "intake": [{ "key": "reason", "answer": "..." }],
  "acknowledgements": ["ack_1"]
}
FieldTypeNotes
slugstring, requiredYour booking page slug
doctorIdstring, requiredFrom the slots feed
slotStartstring, requiredFrom the slots feed
topicstringFalls back to the page's default topic
name / emailstring, requiredThe patient's details
phonestring, requiredAn Australian mobile (04xx xxx xxx, or +61 4xx xxx xxx) or an international number written as + and 8 to 15 digits. Spaces, brackets, dots and dashes are ignored
dobstringOptional date of birth, YYYY-MM-DD
timezonestringIANA zone, patient-facing times render in it
termsAcceptedboolean, requiredMust be true
intake[]{key, answer}Optional questionnaire answers, see below
acknowledgements[]string[]Keys of your page's tick statements, required when intake is sent and your page defines any

Submitting the questionnaire with the booking: your page's question set, authored in your portal, is the contract, and the slots feed's questionnaire field serves it: render your form from those questions and submit each answer keyed by its key. Labels are snapshotted from your definition, never trusted from the caller. Every required question and every defined acknowledgement must be present or the request is refused with intake_incomplete before anything is booked. Omit intake entirely and the patient is instead invited to complete the questionnaire after booking, chased automatically. Pages set to collect their questionnaire outside Consulta refuse intake with intake_not_accepted.

Responses

// patient pays on your page's terms: redirect the visitor here
{ "ok": true, "checkout": true, "checkoutUrl": "https://checkout.stripe.com/..." }

// no online payment step: booked immediately
{ "ok": true, "reference": "BK-8A3101", "doctorName": "...",
  "slotStart": "...", "priceLabel": "...", "manageUrl": "https://..." }

// refused: nothing was booked or charged
{ "ok": false, "code": "slot_taken", "message": "..." }

Refusals are HTTP 400 with a code and a patient-presentable message, except too_many_requests (429 with Retry-After) and unavailable / lookup_failed (503, safe to retry: nothing was booked). Malformed JSON is a 400 with a message and no code.

FieldTypeNotes
slot_takencodeThat time was just booked, refresh the feed
invalid_details / invalid_phonecodePatient details failed validation
terms_requiredcodetermsAccepted was not true
invalid_intakecodeintake or acknowledgements was not an array
intake_incompletecodeA required question or acknowledgement is missing
intake_not_acceptedcodeThe page collects its questionnaire outside Consulta
intake_too_largecodeQuestions plus acknowledgements exceed the per-booking cap, omit intake
closedcodeThe page is not taking bookings
lookup_failedcodeYour booking page could not be read. Temporary, retry (HTTP 503)
too_many_requestscodeRate limit reached (HTTP 429)
unavailablecodeTransient failure, retry (HTTP 503)
unknowncodeAnything else, nothing was booked or charged

Webhooks

Self-serve in your portal (Booking page tab, "Notify your own systems"): set a URL and a signing secret and Consulta POSTs on ten booking-lifecycle events. This is the route into a CRM, Mailchimp or anything Zapier or Make can reach, with zero custom development on either side.

FieldTypeNotes
booking.createdeventA booking is made, any channel
booking.rescheduledeventThe patient moves the appointment
booking.cancelled_by_patient / _by_clinic / _by_doctoreventA cancellation, with who cancelled in the name
consult.completedeventThe practitioner confirms the consult happened
consult.no_answereventThe practitioner could not reach the patient
consult.script_returnedeventA script comes back to the booking
booking.questionnaire_completedeventThe questionnaire is submitted
booking.patient_documenteventThe patient shares a document (adds file_name)
{
  "event": "booking.created",
  "delivery_id": "5e2f...",
  "occurred_at": "2026-08-19T09:30:00.000Z",
  "booking": {
    "reference": "BK-8A3101",
    "status": "confirmed",
    "slot_start": "2026-08-20T23:30:00+00:00",
    "slot_end": "2026-08-20T23:50:00+00:00",
    "topic": "weight-management",
    "doctor_name": "Dr. Jane Citizen",
    "patient": { "name": "Alex Example",
                 "email": "alex@example.com",
                 "phone": "0400 000 000" }
  }
}

Deliberately absent, by construction: questionnaire answers, clinical notes, outcome detail, prescriptions and pricing never appear in a payload. The payload is built from a fixed field whitelist, so those cannot leak by accident. Payloads do carry the patient's contact details, the consult topic and, for patient documents, the file name, so treat every payload as personal information and point webhooks only at systems entitled to hold it.

Verifying and retries

Every request carries X-Consulta-Event, X-Consulta-Delivery (unique id, reused on retries, use it for idempotency) and X-Consulta-Signature: sha256=<hex>, an HMAC-SHA256 of the raw body keyed with your signing secret. Reject anything that fails verification. Respond 2xx within 6 seconds. Redirects are not followed. Otherwise Consulta makes up to five attempts in total: the immediate send, then retries about 5 minutes, 25 minutes, 105 minutes and 7 hours after the event. Your portal shows the most recent deliveries and their status.

const crypto = require("crypto");
const expected = "sha256=" +
  crypto.createHmac("sha256", process.env.CONSULTA_WEBHOOK_SECRET)
    .update(rawBody).digest("hex");
const given = String(req.headers["x-consulta-signature"] ?? "");
const ok = given.length === expected.length &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));

Data and export

You are never locked in. Your portal's Settings > Data tab exports your records as CSV whenever you like, without asking us. It covers every consult with its practitioner, times, outcome and price, your patient roster with contact details and references, every questionnaire answer, and every prescription with its eScript link. Attached documents and the printable per-consult compliance file download per consult from the booking's records.

Patient fields Consulta holds

FieldTypeNotes
full namestringRequired at booking
emailstringRequired at booking
mobilestringRequired, the practitioner calls it
date of birthdateOptional at booking
your referencestringYour own patient identifier, optional
questionnaire answersper your pageWhatever questions you author, snapshotted as answered

Contact details on old bookings are purged on a schedule as part of Consulta's data-minimisation posture. The clinical record itself is retained.

Security

Patient and clinic records are stored in Australia (Sydney). Payments are processed by Stripe, and anything you point a webhook at is hosted wherever that system lives. Every clinic and practitioner account must enrol multi-factor authentication before using the portal. Access is enforced with row-level security at the database, scoping a clinic's access to its own records, and team roles gate configuration (billing, booking-page settings and webhook changes are manager-only). Card details never touch Consulta's servers or yours.

Questions, or a use case these surfaces don't cover? bookings@consultahealth.com.au