Growth stalls when your data lives in seven places. I spent six weeks last spring leading a revenue-operations rebuild for a national amateur sports league — roughly 2,400 teams, seven systems consolidated, zero rollbacks. One piece of that engagement was a HubSpot CRM data mirror that copies team-application lifecycle events out of the league's proprietary registration system into a CRM I built for them from scratch during the same project.
The mirror is live in production. A real HubSpot company record created by the integration, with all three lifecycle stages — initial submission, NDA signed, final approval — landing correctly over a two-day window, was read back from HubSpot's own property-history interface.
The engagement went live 29 May 2026. This is how the data mirror works, what broke, and what I left in the code so the next person can see what I already knew was unresolved.
Additive-only, reversible by design
The data mirror is a one-way backend integration. It copies events out of the league's registration system — a Python/Django + Celery backend — into HubSpot. I designed and wrote the integration; the registration system's own outside development agency reviewed it, merged it into their codebase, and owns deployment.
Four patch points were specified in the registration system's existing code: three required, plus one conditional legacy route. Each patch point is a small, additive change — 6 to 12 lines — to an existing file: a serializer, an e-signature service, an insert path, and a model file. My estimate, documented in the project evidence, put the total lines added to existing client code at under 80, across all patch points and settings.
I chose explicit hook calls at named locations over a more idiomatic Django signal specifically so the change would not surprise the system's own maintainers, who had never used a signal in that codebase before. The integration was built to leave no trace if switched off.
A feature flag defaults to off. A separate dry-run flag defaults to on, so the sync logs the payload it would send and exits before calling HubSpot. Every write is an idempotent upsert keyed on a stable external application ID. The sync is wrapped in a database transaction commit hook, so a rolled-back save never produces a HubSpot write. Log lines carry only an application ID and an event name — no personal data.
Status word: live.
Audits left in the code
The mirror's shipped code carries three numbered rounds of adversarial audit findings — AUDIT 001, 002, and 003 — left as permanent, dated comments in the source files. Each finding is severity-coded: critical, high, medium, low. At least 3 findings are explicitly marked resolved with a date; 8 remained open as of the last commit I read.
Rather than fixing or hiding the open findings, I left them dated in the code itself, so a reviewer can see what was already known to be unresolved.
A separate, dedicated field-coverage audit caught a data-integrity gap that no functional test had surfaced. A form field submitted by the client's front end as web_site did not match the name the receiving code expected: website. Because the field was optional, the mismatch was treated as simply absent rather than an error, and the value was never stored anywhere — not just never forwarded to HubSpot. It was found two months after the initial merge, through the field-coverage audit rather than a bug report.
What this means for your seven systems
The league had data in seven places when I started. The mirror connects one of those systems to HubSpot without replacing it, without requiring a full migration, and without breaking what already works. The registration system's own maintainers can still deploy, patch, and extend their platform exactly as they did before the integration. The only difference is that lifecycle events now land in HubSpot as well.
This approach works when you need your systems to talk to each other but cannot afford to roll everything into one platform or rewrite what you have.
The integration shipped on time, in six weeks, with zero rollbacks. The additive design, the feature flags, and the dry-run mode gave the client a reversible path at every step. The audits left in the code gave the next maintainer a map of what I already knew was unresolved, so nothing was hidden.
If you are competing against bigger money and your data lives in seven places, this is one way to connect them without breaking what already works.
DMs are open.