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 exports ten tables, all upserted by primary key so re-emitted rows update in place. Each email table requests a singleeventType from POST /v2/exports/email-events, and the destination table name is the lowercased event type:
| Table | Source | Notes |
|---|---|---|
contacts | POST /v2/exports/contacts | One row per contact. Every contact field is flattened in as its own column keyed by its common name (e.g. first_name, owner_id). |
email_send | POST /v2/exports/email-events (EMAIL_SEND) | One row per send event. |
email_delivery | … (EMAIL_DELIVERY) | The email was accepted by the recipient’s mail server. |
email_open | … (EMAIL_OPEN) | One row per open event. |
email_click | … (EMAIL_CLICK) | Includes link, the clicked URL. |
email_bounce | … (EMAIL_BOUNCE) | Hard (permanent) bounces. |
email_soft_bounce | … (EMAIL_SOFT_BOUNCE) | Soft (transient) bounces. |
email_complaint | … (EMAIL_COMPLAINT) | The recipient marked the email as spam. |
email_unsubscribe_all | … (EMAIL_UNSUBSCRIBE_ALL) | The recipient unsubscribed from all email. |
email_topic_unsubscribe | … (EMAIL_TOPIC_UNSUBSCRIBE) | The recipient unsubscribed from specific topics. |
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.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, …). 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). 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
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 by trimming the
EMAIL_STREAMSlist or removing the contacts stream. - 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.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.