Resources

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.