Case study
Speak More Clearly
Integration work syncing a course business's CRM (Ontraport) and LMS (Learnworlds) via webhook middleware.
A course business that had outgrown its platform
Speak More Clearly, led by Lee'at, had built a successful course business on WordPress. It was a sensible place to begin: flexible, familiar and supported by a large ecosystem of plugins.
Over time, that flexibility became a constraint. More of the operation came to depend on separate extensions for course access, payments, customer records and the learning experience. Each tool solved an immediate problem, but together they created a platform that was increasingly difficult to scale, understand and maintain.
The cost showed up in small, repeated moments. Payment and course access did not always move together without intervention. Student information was distributed across systems. The learning environment reflected the limitations of a template more than the quality of the brand. Routine changes carried a wider support burden because it was difficult to know which dependency might be affected.
The brief was to move Speak More Clearly onto a platform designed for course delivery while protecting the business already running on top of it. More than 20 courses, roughly 300 lessons and over 100 student accounts had to move without interrupting access or losing the relationships between customers, purchases and enrolments.
Choosing what to change—and what to keep
A replatforming project makes it tempting to replace everything. In this case, that would have introduced risk without enough benefit.
I began by understanding how the business actually operated: where customer information originated, how payments were recorded, what granted or removed access, which manual interventions were routine and what students needed from the learning experience. I then assessed potential course platforms against those requirements, alongside cost and the quality of experience they could provide.
LearnWorlds was selected as the new home for the course catalogue and student experience. Ontraport remained the system responsible for customer relationships and payment activity. Keeping that established part of the operation in place reduced the migration surface, but it created an important design requirement: the two platforms had to behave like one service.
That decision shaped the rest of the engagement. Moving the content was only one part of it; I also had to redesign the operating relationship between learning, customer data and access.
Separating historical data from live behaviour
The migration had two different kinds of information to handle.
Historical records described what had already happened: contacts, tags, course enrolments, quiz data and cancellations. Live events described what should happen next: a new subscription, a failed payment, a suspended account or a cancellation that needed to change a student's access.
I treated them as separate workstreams to make the transition safer. A dedicated export utility collected the historical information into reviewable datasets for migration and reporting. As the requirements became clearer, I adjusted the export to identify cancelled customers first, preventing an old record from being mistaken for a currently entitled student.
The source inventory shows why a blind bulk transfer would have been misleading. It contained 43 course records, including 26 published courses; 1,278 lesson records; 264 topic records; 16 quizzes with 69 question definitions; nine assignments; nine certificates; and 11,776 raw WordPress user rows. Those were legacy database records, not 11,776 active students or the final migration scope. Drafts, old content and accounts without a current entitlement had to be separated from what the business still relied on.
The migration tooling turned that legacy structure into something the team could reason about. A small Svelte interface kept processed and raw content side by side, while the export scripts rebuilt the relationships between people, subscriptions and courses. Cancelled customers were removed from the active set, overlapping subscriptions were reduced into one account record, and user, tag and enrolment data were separated into reviewable imports.
Moving every row successfully would still have been a failed migration if former customers regained access or active students disappeared between systems. I judged the data by its business meaning, not file completeness alone.
Separating extraction from live synchronisation also made each side easier to verify. Existing records could be checked before import, while new customer events could be tested against the rules they were expected to trigger. The result was a controlled move rather than one large piece of logic trying to translate the past and operate the future at the same time.
Turning payment events into course access
The central operational problem sat between Ontraport and LearnWorlds.
Ontraport knew what had happened in the commercial relationship. LearnWorlds knew which learning experience a student could enter. Without a direct connection, somebody—or a paid automation service—had to carry information between them and keep the two versions of the customer aligned.
I designed a stateless Node.js service on Vercel Serverless Functions to become that switchboard. Ontraport remained the source of truth for the commercial relationship and LearnWorlds remained the source of truth for learning access; the integration stored no third version of either. When Ontraport reported a relevant customer event, the service authenticated the request, interpreted the business rule and asked LearnWorlds to make the corresponding change.
A successful subscription could create or enrol the student in the right course. A cancellation, suspension or failed payment could remove or adjust access. Customer tags could be reflected in the learning platform where they shaped the experience or helped the team understand an account.
The production configuration encoded 33 event rules across monthly, quarterly and yearly products in Australian, British and American course lines. Registering both the start and end of each state produced 66 webhook subscriptions, coordinating access across 22 LearnWorlds courses as well as learner progression, failed-payment and suspension states.
Inside the request path, each CRM event was translated into the appropriate learning entitlement. The service found or created the LearnWorlds account, then coordinated course access, progression tags or account suspension according to the rule. That kept the integration centred on business states rather than scattering platform-specific API calls throughout the operation.
Moving data between two APIs was the straightforward part. The harder and more useful work was expressing the business's access policy clearly enough that the same event produced the same result every time. Once those rules lived in configuration, course access no longer depended on a staff member noticing a payment change and completing the matching administrative task.
Separating the control plane from the event flow
Automating the happy path was only part of the job. I also needed to leave behind a service that was safe and maintainable after handover.
Automated events from Ontraport and administrative actions by the business were given separate access boundaries and credentials. Third-party secrets were supplied through the deployment environment rather than source code, the public event route accepted only the expected request method, and production and test rules were selected explicitly by environment.
That distinction made responsibility clearer. The event path could translate customer-state changes; the control path could create, remove or replace Ontraport subscriptions. Touching the same workflow did not give both roles the same level of trust.
Credential rotation was treated as a normal operating need rather than a future exception. Webhook subscriptions sometimes have to be renewed or registered again when access changes. Instead of asking the client to update each subscription manually, the service included a controlled way to refresh them together. A documented runbook explained how to carry out that process and restore the connection confidently.
The bulk controls limited changes to subscriptions belonging to the selected environment and reported partial outcomes clearly. That gave the operator a practical way to understand what had changed and recover if a provider completed only part of a batch.
Customers would never see these decisions, but they are what separates a useful production integration from a demonstration that works only while its original credentials and assumptions remain untouched.
Testing without a sandbox
The third-party systems did not provide an isolated environment that reproduced the complete webhook flow. Without a conventional sandbox, a theoretical test suite was not enough.
I used a controlled workaround. Test webhook subscriptions were registered through the real provider while pointing at a separate development configuration. The runbook supported both a remote Vercel deployment and a local runtime exposed through a temporary tunnel, allowing the real delivery path to be exercised without changing production routes.
The test cases covered more than successful enrolment. A four-stage course progression checked enrolment, unenrolment and movement of the learner's “coming up” tags. Dedicated failed-payment and suspended-user events checked tag changes, suspension and reversal. This was important because an integration can appear correct while only its purchase path is being exercised.
Integration quality usually reveals itself in the exceptions. Granting access after a purchase is the obvious flow. Keeping both systems aligned when a payment fails, a subscription changes or credentials rotate is what makes the automation dependable over time.
What I would harden now
Looking back, I would explore a stronger webhook boundary: provider-signed requests where available, stricter validation of the events the service accepts, and clearer protection against duplicate or replayed delivery. I would also give privacy more explicit operational treatment, particularly around what appears in logs and how long temporary migration files are retained.
Reliability deserves the same reflection. The original service made partial outcomes visible, but a more mature version could make recovery more deliberate through controlled retries, reconciliation and broader automated testing. The important thought is not to add infrastructure for its own sake; it is to design for the reality that external systems fail, repeat themselves and sometimes disagree.
Migrating without making the business pause
With the destination selected, historical data prepared and the live connection in place, the course catalogue and student records could move without asking the business to stop operating.
More than 20 courses, around 300 lessons and over 100 student accounts were transferred with zero downtime. Existing customers retained continuity while the new learning experience came online, and new commercial events had a defined path into course access once the transition was complete.
The visible result was a modern platform better suited to the catalogue and brand. Behind it sat a cleaner operating model: each system had a clear responsibility, and the relationship between payment and access was now a repeatable service instead of institutional memory.
The outcome
Speak More Clearly finished the engagement with a scalable course-delivery platform, a stronger student experience and automated access management tied directly to customer activity.
The custom integration removed recurring manual administration and retired the subscription cost of the middleware it replaced. The export tooling left the business with a clearer route for reporting and future data work. Operational controls and handover documentation meant the system could be maintained without depending on the person who originally built it.
For me, the project is a good example of how much value can sit between products rather than inside them. Neither Ontraport nor LearnWorlds was missing its core capability. The friction lived in the handoff, where a commercial event had to become a learning entitlement.
Solving that handoff required more than connecting two endpoints. It meant defining the business rules, protecting the trust boundaries, accounting for historical data, testing real failure modes and planning for the day the credentials would change. The webhook itself was small. Most of the value was in the operational thinking around it.