Reference implementation
github.com/tapp-ai/fivetran-connector: the full connector source, licensed under Apache-2.0.
Before You Begin
Make sure you have the following before starting:- A Conversion API key: Generate one from Settings → API Keys in the Conversion dashboard. The connector authenticates with this key; see Authentication.
- A Fivetran account and a configured destination: The warehouse Fivetran will load into (BigQuery, Snowflake, Redshift, Databricks, etc.).
- A Fivetran deploy key: Used to deploy the connector to your Fivetran account.
- uv installed locally: Used to run and deploy the connector.
Tables
The connector can export eleven tables, all upserted by primary key so re-emitted rows update in place. Which ones sync is controlled by thetables configuration value (see Choosing Tables); the Group column is the name you list there. Each email table requests a single eventType from POST /v2/exports/email-events, and the destination table name is the lowercased event type:
Core contact columns (
id, email, subscription_status, created_at, updated_at, and the Salesforce IDs) are declared up front. Contact field columns are left undeclared so Fivetran infers them automatically. That’s what lets every field surface as its own column without hardcoding the set as it grows.Custom Event Columns
Choosing Tables
tables is an optional, comma-separated configuration value listing the groups and/or table names to sync. A table that isn’t listed is neither declared in the schema nor synced, so it never appears in your destination.
Unknown names fail the sync at startup. Fivetran’s own schema tab still works as a per-table opt-out on top of this. Changing
tables after the first sync only adds or removes tables going forward; trigger a resync if you need history for a newly added table.
How It Works
Authentication
Each request carries your API key in theX-API-Key header (sk_live_<id>_<secret>). Because the API scopes every response to the business that owns the key, the connector never sends a business ID.
Incremental Sync
Each table keeps its own opaque cursor in connectorstate, keyed by table (contacts_cursor, email_send_cursor, custom_events_cursor, …). The connector never interprets the cursor. It stores whatever the API last returned and sends it back on the next request.
Every request posts {"limit": 1000, "cursor": <saved cursor>} (the email tables also send eventType). custom_events is a windowed export: its very first request sends {"occurredAt": {"start": <custom_events_start>}} instead of a cursor, and every later request sends only the cursor. No end is sent, so the server re-resolves “now” on each request and the stored cursor keeps advancing into new events on every sync. The connector reads rows from the response’s data and the next cursor from pagination.nextCursor.
The connector checkpoints state after every page, so progress is durable and the next sync resumes from the stored cursor. Rows are upserted by primary key (id / event_id), so any row the API re-emits updates in place.
Resilience
Transient failures (network errors,5xx, and 429 rate limits) are retried with exponential backoff. The connector fails fast on 4xx responses and on any structured API error returned in the response envelope.
Setup
1. Get the Connector
Clone the reference implementation:2. Configure It
Copy the example configuration and fill in your API key.configuration.json is gitignored, so your key is never committed:
configuration.json
tables is optional and picks which tables sync (see Choosing Tables). Leave it out for contacts and email, or set it to custom_events to sync only custom events.
custom_events_start is optional and sets the earliest occurred_at the first custom_events sync backfills, as an RFC-3339 UTC timestamp. It defaults to 2020-01-01T00:00:00Z. It is only read before the first sync; changing it on an existing connection has no effect until you resync.
All values must be strings; a Fivetran Connector SDK requirement. After deploying, the same values are editable in the connection’s setup form in the Fivetran dashboard.
3. Run It Locally
Debug against the API before deploying. This runs the sync against a local DuckDB warehouse so you can inspect the tables and confirm cursors advance:4. Deploy to Fivetran
Deploy the connector to your Fivetran account, targeting your configured destination:Customizing the Connector
Because the connector is open source and licensed under Apache-2.0, you’re free to fork it and adapt it. Common customizations:- Add or rename columns by editing
schema()and the row mappers inconnector.py. - Select a subset of tables with the
tablesconfiguration value; no code change needed (see Choosing Tables). - Change the page size via
PAGE_LIMIT(the server applies its own hard cap). - Tune retries with
MAX_RETRIESandBACKOFF_BASE_SECONDS.
CONTRIBUTING.md in the repository for development guidelines.
Frequently Asked Questions
Does Conversion host the connector for me?
Does Conversion host the connector for me?
No. The connector is open source and runs in your own Fivetran account, loading into your own destination. You stay in full control of your data and credentials. Conversion provides the API the connector reads from.
How are timestamps formatted?
How are timestamps formatted?
The API emits RFC-3339 timestamps with nanosecond precision. The connector truncates the fractional seconds to microseconds so they parse cleanly into Fivetran’s
UTC_DATETIME type. Values without a sub-second fraction pass through unchanged.Why do new contact fields appear automatically as columns?
Why do new contact fields appear automatically as columns?
Contact fields are left undeclared in the connector’s schema, so Fivetran infers them from the data it loads. As you define new fields in Conversion, they surface as new columns on the
contacts table on the next sync. No connector change required.I only want custom events. Do I have to sync contacts and email too?
I only want custom events. Do I have to sync contacts and email too?
No. Set
"tables": "custom_events" in configuration.json (or in the connection’s setup form in Fivetran) and the connector declares and syncs only the custom_events table. Nothing else is created in your destination.Can I build my own pipeline instead?
Can I build my own pipeline instead?
Yes. The connector is just a client of the public API export endpoints. If you’d rather use a different ETL tool or write your own loader, you can call the same endpoints directly.