A webhook bridge between a specialty clinic EMR and GoHighLevel
A specialty clinic runs scheduling and charting in its EMR and patient communication in GoHighLevel, with nothing connecting the two. The build is a single verified webhook endpoint that routes every EMR event to its own handler, so the clinic's follow-up reacts to what the EMR already knows.
The clinic runs its day inside an EMR and practice management platform. Patient records, the schedule, charting, the parts of a clinic that cannot be moved somewhere else without disrupting care. Patient communication lives in GoHighLevel: reminders, follow-up sequences, reactivation campaigns, the messages that go out between visits.
The two systems had nothing between them. Anything that happened in the EMR stayed in the EMR until a person noticed it and did something about it somewhere else. A new patient record was created and no intake follow-up began. A visit moved and the reminder still went out against the old time. A visit was removed and the sequence carried on as though it was still coming.
That gap is where patient communication quietly goes wrong, and it goes wrong in the direction that costs the most: messages that contradict what the patient was told. The staff were the integration, and staff are busy.
What made it hard
The EMR is the system of record and stays that way. This is not a migration, and nothing about the clinical workflow changes. The integration has to fit around a platform that was not designed to be extended, and it has to fail quietly rather than block anything the clinic does.
The platform emits webhooks, which is the right foundation. What it does not hand over freely is the detail needed to build against them: the catalogue of event types, a real payload for each one, and the signing scheme used to prove a request actually came from the platform. Building against an assumed payload shape means rebuilding when the real one arrives, and building without signature verification means accepting anything that reaches the endpoint. Both of those are refusals rather than delays.
The third difficulty is that events are not equal. A patient record being created is not the same job as an appointment being moved, and neither is the same as an appointment being removed. Treating them as one stream produces a workflow with a long chain of conditions inside it, which is the kind of thing nobody can safely change six months later.
The solution
One entry point, many small handlers. A single endpoint receives every event the EMR sends. Before anything else happens, the request is checked against the platform's signature so an unverified call goes no further. What survives that check reaches a switch that reads the event type and sends it to its own handler:
Patient created, which starts the intake follow-up in GoHighLevel and creates the contact if it does not exist yet.
Patient modified, which updates the contact rather than creating a second one, so the history stays in one place.
Appointment created, which puts the visit into the reminder sequence that matches the visit type.
Appointment modified, which moves the reminders with it instead of leaving the old ones queued.
Appointment removed, which stops the sequence rather than letting it run at a patient who is no longer coming.
A fallback, which catches anything the switch does not recognize.
Each handler owns one job and can be read, changed or replayed on its own. That is the point of the shape: when the EMR adds an event type or changes a field, the work is one handler rather than an audit of everything.
The fallback matters as much as the named branches. An event nobody planned for is posted into Slack with its payload attached rather than dropped on the floor. That turns the unknown into a queue somebody can read, and it is how the next handler gets specified: from a real event that actually arrived, not from a guess about what the platform might send.
Slack carries the errors too. An integration that fails silently is worse than no integration, because the clinic keeps believing the follow-up is running.
Why this pattern fits businesses like this
Clinics and practices almost always have a system they cannot leave, and it is almost never the one their marketing lives in. The instinct is to replace something. The better move is to let the clinical system stay exactly where it is and make its events available to the communication layer, which is what a verified webhook endpoint and a handler per event type does.
The same shape covers any platform that emits events and any business that has to answer them: one door, one check at the door, one handler per thing that can happen, and a visible place for whatever nobody anticipated. It survives the vendor changing their mind, which is the part that usually breaks a custom integration built as one large workflow.
Where it goes from here
The current build receives and routes. Writing back to the EMR, so a booking made in GoHighLevel lands in the clinic schedule, is the phase after this one and needs the same documentation work on the write endpoints. If your clinic runs an EMR that your follow-up cannot see, tell us which system runs your schedule.
Certified GoHighLevel Admin and Partner. Koby designs CRM migrations, two-way integrations, billing systems and AI agents for service businesses, and writes from the engagements described on this site.
Published
Questions about this engagement
Does this replace the clinic's EMR?
No. The EMR stays the system of record for patients, the schedule and charting. The integration listens to what the EMR reports and lets GoHighLevel react to it, which is the opposite of a migration.
Why one handler per event type instead of a single workflow?
Because a single workflow becomes a long chain of conditions that nobody can safely change later. One handler per event type can be read, edited and replayed on its own, and a new event type is a new handler rather than a rewrite.
What happens to an event nobody planned for?
It goes to the fallback, which posts it into Slack with its payload rather than discarding it. That is also how the next handler gets written: from an event that really arrived.
What has to be in place before this kind of build starts?
The platform's event catalogue, a real sample payload per event type, and the signing details used to verify requests. Without those you are building against assumptions and verifying nothing, and both mean doing the work twice.
More work
A WhatsApp product assistant for a medical device manufacturer, answering into GoHighLevel
An international medical device manufacturer fields the same product questions over WhatsApp from distributors and clinics, one message at a time. The design is an assistant that answers from the company's own product documentation, with every conversation landing in GoHighLevel and a person one step away.
Learn more
A QR code connector that turns trade show visitors into spa bookings in GoHighLevel
Trade show visitors meet spa businesses on the floor, exchange nothing durable, and the connection ends when the show does. The pilot puts a QR code between them: the visitor registers once, and the introduction continues in GoHighLevel after everyone has gone home.
Learn more
A GoHighLevel hub that routes homeowner referrals into each contractor's own CRM
A home improvement referral network matches homeowners with vetted contractors and needs every lead delivered into whatever system the contractor already uses. Its GoHighLevel hub routes each inquiry out by webhook or compliant text, with no sub-account logins for partners.
Learn more
Have a similar problem?
Book a 30-minute call. We will tell you what we would do first and what it would cost.