API Reference
Public-facing integration docs — webhooks for connecting your own systems to ZyncoAI.
Overview
ZyncoAI sends a webhook event to your endpoint whenever something happens on a call or booking — configure your URL under Dashboard → Settings → Webhooks and choose which events you want delivered.
Events
booking.created— a new appointment was booked, by Ella or a staff member.booking.completed— an appointment was marked complete.booking.cancelled— an appointment was cancelled.
Sample payloads
{
"event": "booking.created",
"businessId": "clx0a1b2c3d4e5f6g7h8i9",
"data": {
"appointmentId": "clx1a2b3c4d5e6f7g8h9i0",
"providerId": "clx9z8y7x6w5v4u3t2s1r0",
"contactId": "clx5m6n7o8p9q0r1s2t3u4",
"startAt": "2026-08-06T00:00:00.000Z",
"endAt": "2026-08-06T00:30:00.000Z",
"recordType": "consultation",
"directionsUrl": "https://maps.google.com/?q=..."
},
"ts": "2026-08-04T00:00:00.000Z"
}{
"event": "booking.completed",
"businessId": "clx0a1b2c3d4e5f6g7h8i9",
"data": { "appointmentId": "clx1a2b3c4d5e6f7g8h9i0" },
"ts": "2026-08-04T00:00:00.000Z"
}{
"event": "booking.cancelled",
"businessId": "clx0a1b2c3d4e5f6g7h8i9",
"data": { "appointmentId": "clx1a2b3c4d5e6f7g8h9i0" },
"ts": "2026-08-04T00:00:00.000Z"
}Getting your webhook secret
Create a webhook under Dashboard → Settings → Webhooks. Your signing secret is generated automatically and shown once at creation time — copy it immediately, as it's never displayed again (only a last-4-character preview is kept visible). If you lose it, rotate to a new secret from the same screen — the old secret stops working the moment you do.
Authentication — HMAC-SHA256 signature verification
Every delivery includes two headers so you can verify it genuinely came from ZyncoAI:
x-zynco-event— the event name.x-zynco-signature— an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
To verify: compute HMAC-SHA256(secret, rawBody) yourself and compare it to the header value using a constant-time comparison before trusting the payload.
const crypto = require("crypto");
const express = require("express");
const app = express();
// Verify against the RAW request body bytes, not JSON.stringify(req.body)
// after Express has parsed it — key order or whitespace can differ from
// what ZyncoAI actually signed, which breaks verification even for a
// genuine delivery. Use express.raw() on this route, before any JSON
// body-parser touches it.
app.post(
"/webhooks/zynco",
express.raw({ type: "application/json" }),
(req, res) => {
const signatureHeader = req.header("x-zynco-signature");
const rawBody = req.body; // Buffer, thanks to express.raw()
if (!verifySignature(process.env.ZYNCO_WEBHOOK_SECRET, rawBody, signatureHeader)) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(rawBody.toString("utf8"));
console.log("Verified event:", req.header("x-zynco-event"), event);
res.status(200).send("ok");
}
);
function verifySignature(secret, rawBody, signatureHeader) {
if (!signatureHeader) return false;
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(signatureHeader, "hex");
const b = Buffer.from(expected, "hex");
// Guard the length check before timingSafeEqual — it throws on mismatched
// buffer lengths, and signatureHeader is attacker-controlled input.
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}Retry policy
If your endpoint doesn't return a successful response, ZyncoAI retries delivery up to 3 times with exponential backoff. Every attempt — success or failure — is logged and visible in your dashboard's webhook delivery history.
Integration endpoints
For connecting your own practice-management software directly (rather than a generic webhook), here's exactly what each system does today — not a rounded-up summary:
- Live two-way appointment sync, self-serve under Settings → Integrations — Cliniko. Ella checks your real Cliniko calendar before offering a time and books, reschedules, and cancels directly against it — not just a staff roster. After connecting your Cliniko credentials, match your providers to their Cliniko practitioners, pick the appointment type Ella books, verify the connection, and turn it on — no code, no waiting on us.
- Live staff/practitioner sync, configured under Settings → Integrations, no code required — Nookal and Halaxy. Connect once and your roster stays in sync automatically (a nightly sweep, plus a manual "Sync now") — staff/practitioner data only, not appointments.
- Live staff/practitioner sync, API only for now — Mindbody and generic FHIR. The sync adapter is real and runs the same nightly/manual sync as the list above, but there's no dashboard card for either yet — connect them with a direct
PUT /api/business/integrations/{provider}call. - Credentials storable, sync pending vendor approval — Zanda (Power Diary) and Core Plus. The adapter code is real, but both vendors require their own partner API access before sync can activate — it's not self-serve, and we don't control the timeline.
- Credentials storable, live sync not active yet — Pabau. Its API is confirmed and self-service, but the exact staff-list endpoint hasn't been confirmed against a live account — credentials save now, and sync activates once that's verified.
- No public API — CSV import — Best Practice and Medical Director. Export a staff CSV from your software and upload it under Settings → Integrations; there's no live sync for either because neither publishes a public staff-list API.
- Not integrated — no path exists — Jane App. Jane's own developer platform issues no API keys and has no self-serve access of any kind, for anyone. This isn't on our roadmap because there's currently nothing to build against.
One honest limit that applies to every system above except Cliniko: what syncs today is your staff/practitioner roster, so availability stays accurate without manual re-entry — full appointment/booking sync isn't available yet for any of them. Cliniko is the one exception, with real two-way appointment sync live today.
Rate limits
There's no dedicated rate limit specific to webhooks or integration syncs — every authenticated request to ZyncoAI's business API, including creating a webhook or triggering a sync, shares the same general limits: 100 requests/minute per user, and 1,000 requests/hour per IP address. Webhook delivery itself (the outbound POST to your endpoint) runs through a shared worker pool with a platform-wide concurrency of 10 in-flight deliveries at a time — that's not a per-business quota, just how many the whole platform processes at once. If either limit is a real constraint for your integration, contact support.