All client work
Fresh Start Health Retreat Centers Ltd., Canada
Client · Canada · 5.0 on Upwork
healthretreat.ca
Case study 02 · CRM migration · Keap API · GoHighLevel API · Python · Email deliverability

Keap to GoHighLevel: 19 years of data moved in four weeks, and the business never stopped

Fresh Start Health Retreat Centers, a Canadian residential health retreat, had run its CRM on Keap since 2012, with guest history back to 2007. They hired us on Upwork to move everything to GoHighLevel: 8,000+ contacts deduplicated and cleaned, 19 years of history carried over record by record, and the daily operational layer rebuilt. Delivered in four weeks around a business taking registrations and payments every day. Then we built the automations, fixed email deliverability, and stayed on as their GoHighLevel engineer.

8,000+ contacts deduped and cleaned45,750 API calls, 0 rate limits3,172 tasks + 2,031 appointment histories12 registration workflows, 90 trigger tagsDKIM 100% at Google Postmaster
8,000+
contacts deduplicated and cleaned
45,750
logged API calls, 0 rate limits
4 wks
from hire to Keap switched off
5.0
Upwork review plus a video testimonial
Client
Fresh Start Health Retreat Centers Ltd., Canada · healthretreat.ca
Our role
CRM migration engineer, then ongoing GoHighLevel engineer
Timeline
July 2026 to today (Keap off 16 August 2026)
Stack
Python · Keap REST + XML-RPC API · GoHighLevel API · GoHighLevel workflows, funnels, email builder · Microsoft 365 · Mailgun via LeadConnector · Google Postmaster · DNS
The brief

19 years of data, and still trading

Keap held 8,000+ contacts, a large share of them duplicates, test records and dead entries, plus 19 years of invoices, tasks, notes and appointments. The job was to clean the list, move everything that mattered without losing a record, and rebuild the daily operational layer in GoHighLevel so Keap could be switched off.

We were hired on Upwork in July 2026 with a fixed deadline: GoHighLevel as the single source of truth by the end of the month, with a live business running registrations and payments through the CRM the whole time. Keap Classic since 2012. GoHighLevel adopted in February 2026 for marketing, full move decided in May. Keap decommissioned mid-August 2026.

8,000+
Keap contacts deduplicated and cleaned
3,723
invoices in Keap history
4,380
task and note records
194
custom fields across 16 folders
Source

Keap Classic, 2012 to 2026

Contacts, companies, tags, invoices, tasks, appointments and person notes. Exports plus the Keap REST and XML-RPC APIs for what the exports could not give.

Engine

Python migration scripts, reconciled

Per-contact extraction, a validated field map, idempotent writes through the GoHighLevel API, and an action log for every call so every record can be traced.

Destination

GoHighLevel as the single source of truth

Contacts, history, workflows, templates, calendars, invoicing and email. The client's in-house portal now reads guest and program data from GoHighLevel.

What was delivered

One migration. Nine workstreams.

01 · History

Every invoice, task, appointment and note

For each contact: Keap tasks became real GoHighLevel tasks; invoices, appointments and notes became dated history notes in the client's own layout. Matched by email, idempotent on re-runs.

02 · Data model

Field map verified against the live account

Every Keap field mapped to its GoHighLevel field and re-verified against the live account, with a dedicated folder for Keap-only data so nothing historical got mixed into live fields.

03 · Registration

12 workflows, 90 trigger tags

When a consultant registers a guest, one tag sets 14 fields (dates, cycle, package, deposit, program), adds the guest tag, writes a note, and marks the opportunity won.

04 · Lead intake

4 webform workflows, 15 forms

Capitalises names, fixes Canadian postal codes, assigns the consultant, creates the opportunity with the right program value, guards against duplicates, and moves the card when a call is booked.

05 · Email assets

71 templates, 48 snippets, 15 folders

Marketing and sales templates rebuilt in the GoHighLevel editor to the client's exact fonts, links and booking widget. 70 legacy templates archived, campaigns left inactive until approved.

06 · Landing pages

Two funnels rebuilt in GoHighLevel

Two program landing pages the team could not edit, rebuilt in the GoHighLevel funnel builder with the consultation calendar embedded.

07 · Deliverability

DKIM and DMARC fixed for two senders

Broken DNS records repaired for the GoHighLevel sending domains and Microsoft 365. Verified at Gmail and Yahoo, DKIM 100% at Google Postmaster within 48 hours.

08 · Tracking

Google Ads attribution restored

Diagnosed why ad traffic was not reporting in GoHighLevel and wrote the fix (account-level UTM template), applied by the client.

09 · Handover

Documented, then retained

Run reports, field maps, DNS records and workflow specs on file. The client closed the Upwork project with a 5.0 review and moved to a direct ongoing engagement.

Method

Extract, archive, clean, map, load, reconcile

8,000+ Keap contacts went in; duplicates, test records and dead entries came out before anything was loaded. Data was still changing in Keap while the migration ran, so the method had to be safe against a moving target: pull everything first, keep the raw archive, and prove every import against source counts.

1 · Extract
Pull everything
Contacts, tags, notes, invoices, tasks and appointments from Keap, via export and API, before any in-Keap cleanup.
2 · Archive
Keep the raw
Every source file and every API response archived before a single record is deleted anywhere.
3 · Clean + map
Dedupe, then map
8,000+ contacts deduplicated, test and dead records dropped, then a validated 155-column field map re-verified against the live account.
4 · Load
Batched, idempotent
GoHighLevel API writes with retries. Tasks keyed so a re-run never duplicates.
5 · Reconcile
Prove the counts
Per-contact status columns and a 45,750-row action log. Every contact accounted for.
6 · Cutover
Delta, then freeze
Final delta import of what changed during the project, then Keap switched off.
Rule

Draft first, sign-off gates

Every workflow, template and campaign was built in draft and switched on only after the owner approved it.

Rule

Disclose deviations the day they are known

Every place the build differed from the spec went to the client in writing, with the reason and the cost of the alternative.

Rule

The client's communication system, not our own

Task tracker for instructions, chat for quick questions, three days' notice before big workstreams, end-of-day updates.

The historical run

One run. Every contact accounted for.

The historical records run went through 6,561 contacts in a single pass: fetch from Keap, match the GoHighLevel contact by email, write tasks and history notes, log every call. The 49 errors were contacts the owner had already deleted from GoHighLevel, which is the correct outcome.

  • 0 rate-limit responses across 45,750 API calls
  • 25 transient Keap errors, all retried and recovered
  • Keap quota never exhausted (29,896 down to 11,539)
$ python keap_to_ghl_notes_migration.py --run final
[18:37:02Z] loaded 6,561 contacts (155 columns)
[18:37:04Z] keap quota: 29,896 remaining
...
[summary] contacts processed ........ 6,561
[summary] integration status ........ 6,512 success / 49 no-match
[summary] history notes created ..... 685 contacts
[summary] appointments written ...... 2,031 contacts
[summary] invoice histories ......... 927 contacts
[summary] tasks created ............. 3,172 (1,749 contacts)
[summary] http 200 / 201 ............ 34,352 / 6,816
[summary] http 429 (rate limit) ..... 0
[summary] http 503 (transient) ...... 25, all recovered
[summary] action log rows ........... 45,750
done. report written to Historical-Records-Run-Report.md
GoHighLevel contacts header showing 6,731 contacts
Live account today: 6,731 contacts, 193 columns, smart lists per program
Known, disclosed

852 tasks unassigned

Their Keap owners were not GoHighLevel users. Tasks exist and are linked to the right contacts; only the owner is blank. Logged with the evidence, left for the client to decide.

Cleanup

1,178 duplicate notes removed

A mis-mapped import had doubled person notes on 590 contacts. A content-matching script deleted only exact matches: 0 errors, dry-run first.

Links

366 linked contacts carried over

Family and partner relationships pulled from Keap through both REST and XML-RPC and delivered as a sheet for association in GoHighLevel.

GoHighLevel · Registration automation

One tag registers a guest

Programs run as dated cohorts in three blocks a year, across two program families, three lengths each, and up to three packages. Instead of consultants filling 14 fields by hand, each session, cycle and package combination is a trigger tag. Twelve published workflows cover the 90 combinations with the same six steps: update the fields, add the guest tag, write the note, remove the trigger, find the opportunity, mark it won.

GoHighLevel workflow list filtered to the registration workflows
Workflow list, registration set (names hidden)
One registration workflow with a branch per session
One of the twelve: a trigger-tag branch per session

14 fields from one tag

Program dates, cycle, package, start and finish, deposit, program name, area of focus, person type. No typing, no mismatches.

Opportunity closed automatically

The workflow finds the latest opportunity and marks it Won, so the pipeline matches reality without a second step.

Verified with the owner on day one

Published and tested together on real registrations the day they shipped. Spec and QA audit on file.

GoHighLevel · Lead intake

Fifteen forms, one clean intake

Every website form across four brands lands in one workflow: capitalise the first name, upper-case the postal code, assign the consultant if nobody owns the contact, check for an existing open opportunity, then create the card with the right program value in the New Leads pipeline. A fourth workflow moves the card to Consultation Booked the moment a call is booked on either public calendar.

Live-tested the night it shipped: name casing, postal code, assignment, program values, duplicate guard, booking move. All passed, test leads deleted.

The four intake workflows
The intake workflows (plus the booking mover)
Intake workflow canvas with form triggers and branches
Intake: 10 form triggers, formatter, assignment, duplicate guard, program branches

Duplicate guard

An open card anywhere in the pipeline blocks a second one; a resubmission only updates the program field. Disclosed as the policy.

Deviations in writing

Opportunity name kept to First Last; postal code upper-cased without a forced space. Both would need paid custom code, both documented.

Internal calendar excluded

Only the two public booking calendars move cards. Internal bookings never touch the pipeline, on purpose.

GoHighLevel · Email assets

187 Keap templates, sorted

The client's template sheet had 187 Keap emails across 15 sections. Each one was classified: archive, rebuild, or drop. The active set was rebuilt in the GoHighLevel editor with the client's fonts, custom values, brand links and booking widget. Transactional bodies became snippets so consultants can send them in two clicks.

  • 71 templates live in 15 folders
  • 48 snippets for sales and guest logistics
  • 70 legacy templates archived, not deleted
  • Seasonal campaigns built, left inactive until the owner approves
GoHighLevel email templates list
Templates (editor names hidden)
GoHighLevel snippets list
48 snippets
GoHighLevel · Data model

Keap history kept in its own lane

194 custom fields in 16 folders. The migration added a "Keap Historical Data" folder for fields that only exist because of the old system (person notes, created-by, old room number, email status) so the team never confuses legacy values with live ones.

  • 172 contact, 10 opportunity, 12 company fields
  • Program, travel, financial and pre-screening groups mapped 1:1 from Keap
  • Registration fields reused by the 12 workflows, no new field design
GoHighLevel custom fields list, newest first
Custom fields, newest first: the Keap Historical Data folder
Lists

27 smart lists, one per job

Guests room list, master data, arrivals report, allergies, birthdays, arrival and departure: the lists the operations team runs the week from.

Pipelines

Two pipelines, 14 stages

A five-stage New Leads pipeline from form submission to consultation booked and won, and a nine-stage planning pipeline for the next program year.

Integration

Portal reads from GoHighLevel

The client's in-house guest portal pulls program fields over the GoHighLevel API, so one record feeds both systems.

Email deliverability

Two senders. Both signed.

In October the client's email was landing in spam. The DKIM and DMARC records for the GoHighLevel sending domains had been entered incomplete, and Microsoft 365 was not signing the root domain at all. Fixed in two DNS sessions, one record at a time, with a full zone backup first.

100%
DKIM pass at Google Postmaster, 48 h after the fix
0%
spam rate and 0% errors reported
RecordBeforeAfter
k1._domainkey.lck=rsa (key missing)Full 225-char DKIM key
_dmarc.lcv=DMARC1 (no policy)p=none with reporting address
k1._domainkey.notificationsk=rsa (key missing)Full DKIM key, domain verified
selector1/2._domainkeyabsentMicrosoft 365 DKIM CNAMEs added
Gmail testspamSPF, DKIM, DMARC pass, inbox
Both GoHighLevel dedicated sending domains verified
Both GoHighLevel sending domains verified (domains replaced)
Client rules we worked inside

A live business is not a sandbox

The owner set the rules. Our job was to build inside them and never make him wonder what had changed.

Compliance

Unsubscribe links by hand

The platform default stays off. Every marketing email carries the owner's exact footer and is approved before it goes anywhere.

Scope

Phones stay where they are

The existing phone system was not replaced. The CRM handles email, tasks, pipeline and registrations; calls stay on the client's provider.

Process

Pipeline only up to registration

After a guest registers, everything is tasks, not stages. The pipeline reflects sales, not operations.

DNS

Backup, one record, verify, repeat

Full zone export before any change, one record per save, byte-compare before confirm, public DNS check after. Root records never deleted without explicit OK.

Safety

Nothing deleted, nothing live by accident

Legacy templates archived, not deleted. Campaigns built inactive. Old webforms untouched. Client files never removed.

Hygiene

Test residue cleaned

Every test contact is prefixed and removed after the test. Test sends go from the sales mailbox. No credentials in docs, tasks or chat.

Client review · on video

In Vasili’s words

“He goes above and beyond, advanced GHL Admin, API, Scripting, coding skills and he just kept on pulling more skills out to get the project done. Top skill: SMARTS and EASY to work with. He gives you 100% of his attention and once he is clear, does miracles, plus has positive radiance around him. He honestly told us, Keap is not my top skill but I know what needs to be done to help you move to GHL. Because he has advanced API/Coding skills move became possible. All lists, automations, historical records and more are in GHL before deadline. Highly recommend Razi as top expect on Upwork!”
Vasili Kastashchuk
Vasili Kastashchuk
Owner, Fresh Start Health Retreat Centers
★★★★★
Upwork review · video, September 2026
Outcome

Keap off. Client kept.

Keap was decommissioned in mid-August 2026 with every contact and every historical record accounted for. The client closed the Upwork project with a 5.0 review and a video testimonial, then moved to a direct ongoing engagement for GoHighLevel support and new builds.

Timeline: hired July 19. Historical run July 31. Migration complete and Keap off August 16. Landing pages rebuilt August to September. Webform intake live September 15. Email authentication fixed October 24 to 26. Ongoing engagement from October 27.

4 wks
from hire to Keap switched off
100%
of 6,561 contacts accounted for
16
published workflows built
5.0
client review on Upwork, plus a video testimonial
Stack

Python · Keap REST + XML-RPC · GoHighLevel API

Plus the GoHighLevel workflow, funnel and email builders, Microsoft 365, Mailgun through LeadConnector, Google Postmaster, and DNS at the client's host.

Evidence

Everything is on file

Run report, 45,750-row action log, per-contact result sheet, DNS zone backups, authentication test results, workflow specs and a full handover document.

Next

Still building for them

Lead-nurture email sequences, an affiliate and referral program, and a payments dashboard are the next scoped projects.

What we would tell you

Three things we learned

01
Archive before anyone deletes anything.
The owner was cleaning Keap while the migration ran. The raw archive is what made that safe.
02
Reconcile, do not trust.
49 errors looked like a bug. The log showed they were deletions the owner had made on purpose.
03
Moving the data is the easy part.
The registration tags, the intake rules and the DNS fix are what the team actually lives with.

The full case study as a PDF

The full write-up with the screenshots above, formatted for printing or forwarding. Shared with the client's permission. Private data stays hidden.

Download the PDF

Moving off Keap, HubSpot, ActiveCampaign or a spreadsheet?

Tell us what you are trying to automate or move. We will say what fits, what does not, and what it would cost.

Book a free 15-minute call
More case studies
Case study 01
Outbound WhatsApp AI agent for a real-estate agency
15 n8n workflows in production · 11 WhatsApp templates approved by Meta · 9 Postgres tables on Supabase · 0.8% failure rate across all workflows
Case study 03
Landing page rebuild in the GoHighLevel funnel builder
2 program pages rebuilt, two brands · 4 funnel steps each, call to booking · 3 custom code blocks, all documented · 7 wks from scope to owner sign-off
Case study 04
GoHighLevel agency build for an energy-sector marketing agency
1,592 newsletter sign-ups processed · 8 sub-accounts in one agency · 7 lead automations for a contractor · 5.0 review from the agency that brought us in
Case study 05
AI chat agent on WhatsApp and Instagram for a Pilates studio
3,279 conversations handled · 2,644 AI replies delivered · 465 message bursts batched into one reply · 3 studio locations routed automatically
Case study 06
Bilingual AI voice agent on Retell that books into GoHighLevel
2 languages, matched to the caller · 0.8-1.3 s model latency · $0.14 per minute to run · 5 fields extracted after every call
Case study 07
Multi-agent AI voice system on Retell, n8n, Supabase and GoHighLevel
46+ client sub-accounts on one build · 1.49M+ records behind the system · 5 min from a dropped call to an AI callback · 10 call outcome categories
Case study 08
AI front desk GoHighLevel snapshot a US platform resells to home-service businesses
26 workflows live on the first client · ~1 hr to deploy the next client · 25 s from missed call to text-back · 1st attempt: A2P approved
Book a free callhello@olventra.comWhatsApp usHire us on Upwork