← Back to Work
Automation[TOOL][AUTOMATION][REAL BUILD SHOWN]

Relay

Two-way CRM-to-spreadsheet sync engine

A two-way CRM-to-spreadsheet sync built for messy real data: updates in both directions, conflict detection for out-of-order edits, and failures that surface instead of disappearing.

My role
Built both sync directions in n8n, the conflict detection, and the failure logging.
Built with
GoHighLevel, n8n, Webhooks, Google Sheets
Status
Built and running

Before

The problem

Contacts lived in two systems with no sync, so every addition was re-entered by hand. Inconsistent real-world data broke simple sync attempts, so the fix had to survive messy input.

The system

How the pieces connect

  1. GoHighLevelContact or status change
  2. n8nWebhook, both directions
  3. n8nConflict check
  4. Google SheetsRow written back
  5. Failure logSurfaced, not silent

Proof

The real build

Real buildn8n execution
A real execution catching a conflict: the incoming status update is older than the record's last edit, so it is flagged and logged instead of overwriting newer data.
n8n execution · original resolution

Step by step

How it works

  1. 1A new CRM contact creates a matching spreadsheet row automatically.
  2. 2Unsynced sheet rows go the other way, creating or updating the CRM contact.
  3. 3Status changes sync through dedicated logic per state, not one catch-all update.
  4. 4An update older than the record's last edit is flagged and logged, never written over newer data.
  5. 5Failures are logged where someone will see them, before bad data spreads downstream.
Two-Way Sync
GoHighLevel / New Contact
NameDana Whitfield
Emaildana@voltframe.io
StageNew Lead
NameDana Whitfield
Emaildana@voltframe.io
StageNew Lead
Synced At09:41:03 AM
✓ Synced

SIM: a simplified illustration of how the system behaves, with invented data. Not a capture of the real build.

After

What changed

› Manual re-entry between systems eliminated. One update now populates both.
› Sync failures surface proactively instead of being discovered after someone downstream notices bad data.
› Status tracking consolidated to a single accurate view instead of two systems that could silently drift apart.

Stack

For technical readers

Technical detail

The problem, in detail

› Contact records lived in two systems with no sync between them. Adding someone in one meant manually re-entering them in the other.
› Status updates had to be checked by opening each system separately. No single view showed what stage a record was actually in.
› Inconsistent real-world data (missing fields, mismatched formatting) broke naive sync attempts, so any fix needed to survive messy input, not just clean test cases.

Build notes

A two-way sync between a CRM and a spreadsheet, built to handle production-messy data in both directions.

› New CRM contacts create a matching spreadsheet row automatically, no manual re-entry.
› Status changes in the CRM sync back to the spreadsheet through dedicated logic per state, not a single catch-all update.
› Built-in monitoring surfaces sync failures before they become a production problem, instead of failing silently.
› Handled a subtle row-offset bug where the sync tool's row-reference indexing didn't match the spreadsheet's actual row numbers, a bug that would have silently corrupted the wrong row's data if it had shipped uncaught.