Skip to main content

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:

ConceptPurpose
Connector interfaceStandard contract every data source must implement
Canonical schemasUnified Person, Account, and Activity shapes validated by Zod
RegistryRuntime discovery and lifecycle management for all connectors
Temporal orchestrationCron-scheduled syncs, retries, and writeback workflows

The Connector Interface

Every connector implements four methods defined in src/connectors/interface.ts:

MethodSignaturePurpose
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:

  1. Fetch -- the connector pulls raw records from the external API (SOQL, REST, webhooks).
  2. Transform -- raw records are mapped to canonical schemas using configurable field mappings.
  3. Quality -- the quality pipeline deduplicates, validates required fields, and coerces types.
  4. 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

FieldTypeRequiredDescription
externalIdstringYesSource system ID
emailstringNoEmail address
firstNamestringNoFirst name
lastNamestringNoLast name
titlestringNoJob title
phonestringNoPhone number
accountExternalIdstringNoLink to parent account

CanonicalAccount

FieldTypeRequiredDescription
externalIdstringYesSource system ID
namestringYesCompany name
domainstringNoWebsite domain
industrystringNoIndustry vertical
employeeCountnumberNoHeadcount
annualRevenuenumberNoRevenue figure

CanonicalActivity

FieldTypeRequiredDescription
externalIdstringYesSource system ID
typestringYesActivity type (e.g., email_open, intent_signal)
channelstringNoSignal channel (e.g., email, intent, meetings)
occurredAtDateYesWhen the activity happened
personExternalIdstringNoLink to person
accountExternalIdstringNoLink to account
propertiesRecord<string, unknown>NoArbitrary metadata

SyncResult

Every sync() call returns a SyncResult containing:

FieldTypeDescription
recordsArrayCanonical entities produced in this batch
cursorstring | nullOpaque cursor for incremental sync
hasMorebooleanWhether more pages remain
syncedAtDateTimestamp 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());
Duplicate registration

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:

TransformEffect
nonePass through unchanged
lowercaseConvert to lowercase
uppercaseConvert to uppercase
trimStrip 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/:

ComponentPurpose
workflows.tsWorkflow definitions for sync and writeback
scheduler.tsCron-based scheduling for incremental syncs
worker.tsTemporal worker setup and configuration
activities.tsBase activity implementations
salesforce-activities.tsSalesforce-specific activities
Sync scheduling

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_KEY environment 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_log table via tRPC middleware.

Available Connectors

ConnectorAuth MethodData TypesStatus
SalesforceOAuth 2.0Contacts, Accounts, OpportunitiesProduction
Customer.ioAPI Key + WebhookEmail engagement eventsProduction
Delivr.aiAPI KeyIntent signals, member engagementProduction
Microsoft 365MSAL OAuthCalendar events, email metadataProduction
Fathom.videoAPI KeyMeeting transcripts, participantsIngest-only
Building your own

See the Building a Connector guide to add a new data source.