Most clinic operations end up running two systems that both look like the truth. The practice-management system holds the official record. A calendar, somewhere else, is what the people actually doing the work look at every morning. Nobody designed it that way on purpose. It's what happens when the system built for records and the tool built for glancing at a day's work are different pieces of software, and both of them matter.

The moment a build has to keep those two in step, it stops being an AI problem and becomes an honest synchronization problem: two ledgers, one truth, and a lot of ways for them to quietly disagree. What follows is drawn from syncing Plato to Google Calendar for home-screening visits — appointments carried out not at a clinic but at a patient's home, by an external phlebotomy partner who doesn't work in the clinic's systems at all.

Pick one owner, and mean it

The instinct is to treat both systems as equally editable, since both show the same appointment. Resist it. One system has to be the actual source of truth, and the other has to be a mirror of it. Here, Plato owns the record; the calendar exists so the person driving to the appointment can see their day without logging into a practice-management system they have no business holding an account for.

That means the sync only ever flows one direction. Nobody edits the calendar event directly and expects it to matter. If a booking changes, it changes in Plato, and the mirror catches up. The moment you allow edits on both sides, you've built a system that has to resolve conflicts between two versions of the same fact, and that problem is much harder than the one you started with. Better to make the mirror deliberately dumb.

Solve the discriminator problem before anything else

Here's the part that isn't obvious until you hit it. A shared calendar usually isn't empty. People already put their own appointments, leave, and meetings on it. So when a sync process looks at that calendar, it needs a reliable way to tell "an event this sync created" from "an event a person created," or it will happily edit or delete something it never should have touched.

Tagging individual events to mark their origin works, technically, but it's fragile: tags get stripped by other tools, copied incorrectly, or missed entirely on manual entries. The sturdier fix is to give the sync its own dedicated calendar, so the boundary is the calendar itself rather than something written into each event. Anything in that calendar came from the sync, full stop. No inference required, no tag to lose.

Build for the second webhook, not just the first

Real-time syncs are driven by events firing when something changes at the source. Those events do not fire exactly once. Connections retry, webhooks redeliver, and the same appointment update can show up twice within a second of itself. If the sync treats every incoming event as brand new, you get duplicate calendar entries for a single real appointment, which is worse than no calendar at all, because now the mirror is actively lying.

The fix is to key every synced event to a stable identifier from the source system and always upsert against that key, never insert-then-hope. Same identifier, same event, updated in place. It costs a bit more logic up front and removes an entire category of bug later.

Worth knowing if you're building against Plato specifically: a webhook subscription that fails to deliver enough times inside a day gets removed at the source end. Not paused — removed. The sync then stops silently, and the mirror doesn't go wrong so much as go stale, which is considerably harder to notice. Anything that can take your endpoint down for a stretch — including a run of deploys — is a thing that can cost you the subscription. Re-registering should be something you can do deliberately, not something you discover you needed.

The raw event usually isn't enough on its own

The payload that announces "this booking changed" is often thin: an id, a time, maybe a status. That's fine for the source system, which can look up everything else it needs. It's not fine for a calendar entry someone is going to glance at before driving to a patient's home. If the address, the service type, or the contact details aren't in the entry itself, you've built a mirror that sends someone back to a third system anyway, which defeats the point.

So the sync doesn't just relay the event. It resolves the full record behind it and writes an entry that actually stands on its own. A calendar entry should answer the question it exists to answer, without a follow-up lookup.

Write the minimum a patient can be identified by

This one is specific to healthcare, and it should shape the entry before convenience does. The calendar is shared with someone outside the clinic. They need enough to know they're at the right door, seeing the right person, doing the right test — and nothing beyond that.

So identifiers are masked to the last four digits. Enough to confirm the visit; not a copy of a patient's identity sitting in a third party's calendar indefinitely. It takes ten minutes to implement and is very awkward to retrofit once the events already exist.

Count the inputs only a person can supply

Everything else in this build is testable. A failed call can be retried, a webhook that stops arriving can be alerted on, a mirror that drifts can be reconciled against the source. An input that exists only because a person put it there has none of that: no error to catch, no status to poll, nothing to re-register. It doesn't break; it thins out, first on the mornings a clinic is busiest.

How many of those a build leans on is a design decision, and needing several is a sign it hasn't been thought through. The question I go in with isn't how to get people to take on something new — it's what already happens reliably and never gets written down. That gap is usually wider than anyone expects. What this sync needed was in it: already true in practice, never recorded anywhere software could read it, and nothing extra for anyone to do.

None of that is a courtesy to staff. It bears directly on whether the integration still works in six months, because anything genuinely new has to survive a fully booked morning, a staff change, and the week when half the team is covering for someone. Information captured as part of something people already do inherits the reliability of what it's attached to. A new habit has to earn its own, and mostly doesn't.

Prove the simple version before making it bidirectional

It's tempting to build the two-way version straight away, so changes on the calendar side eventually feed back too. I don't. A one-directional sync that's boring and correct is worth more early on than a clever bidirectional one that's half-proven. Ship the direction that matters most, let it run against real data for a while, and only widen scope once the simple version has earned it.

Two systems that both think they're the source of truth is a more common setup than it sounds, and it shows up well outside booking and calendars: a CRM and a spreadsheet, a warehouse system and a courier's app, a CMS and a search index. If your clinic has two places that are supposed to agree and don't quite, that's a build I know how to do properly.

Two systems in your clinic that don't quite agree?

That's usually a smaller job than it feels like, once someone has named which one is the source of truth.

Get in touch

More notes from production →