← All posts
Engineering1 min read

Building the unified data model

A technical look at how we made a contact in your funnel literally the same row as the one in your CRM.

The premise sounds obvious: one contact table for everything. But the second you start, the questions get hard fast.

The naive approach

You could just slap a foreign key on every table. Funnel submissions point to contacts, course enrollments point to contacts, community memberships point to contacts. Done.

The problem is that identity is harder than identity. A visitor opts in with one email, buys a course with another, comments in your community as a third. Are these one person or three?

What we did

We introduced an identity layer above the contact table. Identities can have many emails, many sessions, many devices — but they always resolve to a single contact under the hood. When two identities turn out to be the same person, we merge them.

The merge is the hard part. We had to think about:

  • Conflicting field values (which firstName wins?)
  • Course progress (do we union? take the most-progressed?)
  • Subscription state (definitely take the active one)
  • Workflow state (pause both, resume one)

Every merge is reversible for 30 days. Nothing in software should be permanent on the first day.

What's next

We're working on probabilistic identity resolution — same browser, same IP block, same device fingerprint — for cases where customers don't give us enough explicit identifiers. That's a long road; it'll ship next quarter.