Connector Overview
GTM Clarity uses a pluggable connector framework to ingest data from external CRM, marketing automation, intent, and communications platforms. Every connector normalizes source data into a canonical schema before it reaches the scoring engine or database, ensuring the rest of the system never deals with vendor-specific field names.
Architecture
The connector framework is built around four concepts:
| Concept | Purpose |
|---|---|
| Connector interface | Standard contract every data source must implement |
| Canonical schemas | Unified Person, Account, and Activity shapes validated by Zod |
| Registry | Runtime discovery and lifecycle management for all connectors |
| Temporal orchestration | Cron-scheduled syncs, retries, and writeback workflows |
The Connector Interface
Every connector implements four methods defined in src/connectors/interface.ts:
| Method | Signature | Purpose |
|---|---|---|
authenticate | (config) => Promise<{ credentials, expiresAt? }> | Exchange stored settings for live credentials |
sync | (credentials, cursor, options?) => Promise<SyncResult> | Fetch records since last cursor; return canonical entities |
getSchema | () => FieldMapping[] | Return default source-to-canonical field mappings |
healthCheck | (credentials) => Promise<ConnectorHealthStatus> | Lightweight liveness probe for the external API |
Each connector also exposes read-only metadata:
readonly id: string; // e.g. "salesforce", "customerio"
readonly name: string; // Human-readable label
readonly version: string; // SemVer
Data Flow
Data moves through a consistent pipeline regardless of the source:
- Fetch -- the connector pulls raw records from the external API (SOQL, REST, webhooks).
- Transform -- raw records are mapped to canonical schemas using configurable field mappings.
- Quality -- the quality pipeline deduplicates, validates required fields, and coerces types.
- Persist -- clean records are upserted into tenant-scoped PostgreSQL tables via Drizzle ORM.
Canonical Schemas
All connectors output one of three Zod-validated canonical types.
CanonicalPerson
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Source system ID |
email | string | No | Email address |
firstName | string | No | First name |
lastName | string | No | Last name |
title | string | No | Job title |
phone | string | No | Phone number |
accountExternalId | string | No | Link to parent account |
CanonicalAccount
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Source system ID |
name | string | Yes | Company name |
domain | string | No | Website domain |
industry | string | No | Industry vertical |
employeeCount | number | No | Headcount |
annualRevenue | number | No | Revenue figure |
CanonicalActivity
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Source system ID |
type | string | Yes | Activity type (e.g., email_open, intent_signal) |
channel | string | No | Signal channel (e.g., email, intent, meetings) |
occurredAt | Date | Yes | When the activity happened |
personExternalId | string | No | Link to person |
accountExternalId | string | No | Link to account |
properties | Record<string, unknown> | No | Arbitrary metadata |
SyncResult
Every sync() call returns a SyncResult containing:
| Field | Type | Description |
|---|---|---|
records | Array | Canonical entities produced in this batch |
cursor | string | null | Opaque cursor for incremental sync |
hasMore | boolean | Whether more pages remain |
syncedAt | Date | Timestamp of this sync run |
stats | { created, updated, errors } | Counters for observability |
Connector Registry
The ConnectorRegistry class in src/connectors/registry.ts provides runtime discovery:
import { connectorRegistry } from "@/connectors/registry";
// List all registered connectors
const all = connectorRegistry.list();
// Look up by ID
const sf = connectorRegistry.get("salesforce");
// Check existence
if (connectorRegistry.has("customerio")) { /* ... */ }
Connectors self-register when their module is imported. Each connector file ends with:
connectorRegistry.register(new MyConnector());
Calling register() with a connector ID that already exists throws an error. Each connector ID must be unique.
Field Mappings
Every connector ships with default field mappings that map source fields to canonical target fields. Mappings support four built-in transforms:
| Transform | Effect |
|---|---|
none | Pass through unchanged |
lowercase | Convert to lowercase |
uppercase | Convert to uppercase |
trim | Strip leading/trailing whitespace |
Admins can override default mappings per-tenant through the dashboard field mapping UI.
Temporal Orchestration
Long-running sync and writeback operations are orchestrated by Temporal.io workflows defined in src/connectors/temporal/:
| Component | Purpose |
|---|---|
workflows.ts | Workflow definitions for sync and writeback |
scheduler.ts | Cron-based scheduling for incremental syncs |
worker.ts | Temporal worker setup and configuration |
activities.ts | Base activity implementations |
salesforce-activities.ts | Salesforce-specific activities |
Incremental syncs run on configurable cron schedules (default: every 15 minutes). Full syncs can be triggered manually or on first connection.
Security Invariants
All connectors enforce these security rules:
- Encryption at rest -- OAuth tokens and API keys are encrypted with AES-256-GCM using the
CONNECTOR_ENCRYPTION_KEYenvironment variable (32-byte hex key). - Tenant isolation -- every operation is scoped to a
tenantId. No cross-tenant data leaks. - No token logging -- raw credentials are never written to logs, even in debug mode.
- Automatic refresh -- OAuth token refresh is transparent to the caller.
- Audit trail -- all mutations are logged to the
audit_logtable via tRPC middleware.
Available Connectors
| Connector | Auth Method | Data Types | Status |
|---|---|---|---|
| Salesforce | OAuth 2.0 | Contacts, Accounts, Opportunities | Production |
| Customer.io | API Key + Webhook | Email engagement events | Production |
| Delivr.ai | API Key | Intent signals, member engagement | Production |
| Microsoft 365 | MSAL OAuth | Calendar events, email metadata | Production |
| Fathom.video | API Key | Meeting transcripts, participants | Ingest-only |
See the Building a Connector guide to add a new data source.