Behind the Build: CRM Integration Adapter Base Layer (v1.0.39)
Behind the Build: CRM Integration Adapter Base Layer
v1.0.39 — Engineering deep-dive
NurtureHub is built to connect natively to agentOS and via open API to every major proptech CRM in the UK market — Reapit, Alto, Street, Loop, and beyond. Shipping reliable, maintainable integrations at that scale without accumulating an unmanageable web of bespoke glue code requires a disciplined architectural foundation. That's exactly what v1.0.39 delivers.
The Problem with Ad-Hoc Integrations
Without a shared contract, each CRM connector tends to grow its own conventions: different credential handling, different sync strategies, different ways of mapping contacts, different error handling. The result is a codebase where adding a fifth integration is just as hard as adding the first — and where a bug fix in one connector doesn't benefit the others.
We wanted the opposite: a model where each new CRM integration costs less than the last, and where correctness guarantees in the shared layer propagate to every connector automatically.
Ports and Adapters (Hexagonal Architecture)
v1.0.39 implements the Ports and Adapters pattern (also known as Hexagonal Architecture) for all CRM connectivity.
The key idea is a strict separation between:
- The port — an abstract interface defining what the platform needs from any CRM: sync contacts, push activity, ingest webhooks, verify a connection.
- The adapters — concrete implementations of that interface, one per CRM provider, each responsible for translating between the CRM's specific API and NurtureHub's canonical data models.
Core platform logic — nurture sequencing, intent scoring, lead alerting — talks exclusively to the port. It has no knowledge of any individual CRM. Adding Reapit support means writing a Reapit adapter. Adding Alto support means writing an Alto adapter. Neither touches the core.
What Ships in This Release
The Abstract Adapter Interface
The interface defines four methods that every CRM adapter must implement:
| Method | Purpose |
|---|---|
syncContacts | Pull contacts from the CRM and reconcile them into NurtureHub |
pushActivity | Write email engagement and nurture events back to the CRM |
ingestWebhook | Receive and process real-time event payloads pushed by the CRM |
verifyConnection | Validate stored credentials and confirm connectivity |
Any class implementing this interface is a valid CRM adapter. The platform treats them all identically.
CRM Provider Registry
A provider registry table enumerates every CRM NurtureHub knows about. When a new adapter is written and merged, its provider is registered here. Routing logic that resolves which adapter to instantiate for a given tenant reads from this registry — meaning no core routing code changes when a new CRM is added.
Per-Tenant Connection Records
Each agency that connects a CRM gets a dedicated connection record containing:
- Encrypted credentials — API keys, OAuth tokens, or whatever the CRM requires, stored encrypted at rest
- Sync cursor — a position marker (timestamp, sequence ID, or similar) that tells the adapter where incremental sync should resume, preventing duplicate processing across runs
- Provider reference — links the connection to the correct adapter via the registry
Canonical Data Models
Rather than letting each adapter define its own output shape, all adapters map their CRM-specific objects into two shared canonical schemas:
Contact — a normalised contact record covering the fields NurtureHub needs regardless of which CRM the contact originated from: identity, contact details, assigned category, and provenance metadata.
Activity — a normalised event record covering email sends, opens, clicks, and CRM-side activities such as calls or viewings, with a consistent timestamp and source attribution.
Because all adapters produce the same shapes, the rest of the platform can process contacts and activities without branching on CRM type.
Connection Health Monitoring
Every connection record now carries health tracking fields:
lastSyncedAt— timestamp of the most recent successful sync runlastErrorAt/lastErrorMessage— timestamp and message of the most recent failure
This data is the foundation for the connection health UI and for future alerting when a sync has been failing for an extended period.
Extending the Platform
The intended workflow for adding a new CRM integration is:
- Write a new adapter class implementing the abstract interface
- Register the new provider in the CRM provider registry
- Map the CRM's contact and activity objects to the canonical schemas within the adapter
- Deploy — no changes required anywhere else in the platform
This is the extension point for all CRM integrations going forward.
What's Next
With the adapter base layer in place, concrete CRM adapters — starting with agentOS and Reapit — can be built on top of it. Each one will implement the four-method interface, produce canonical contacts and activities, and slot into the platform's sync and health-monitoring infrastructure automatically.