Skip to main content
The Conversion Fivetran connector loads your contacts and email engagement data into a destination warehouse on a schedule. It’s a Fivetran Connector SDK connector built on top of the Conversion public API export endpoints, so Fivetran handles the scheduling, paging, and loading while the API supplies the data. The connector is open source. You can deploy it as-is, read exactly how it works, or fork it to fit your needs.

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 single eventType from POST /v2/exports/email-events, and the destination table name is the lowercased event type:
TableSourceNotes
contactsPOST /v2/exports/contactsOne 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_sendPOST /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 the X-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 connector state, 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.
Paging is driven by pagination.nextCursor, not by page length. A short page is not end-of-stream. The connector keeps paging as long as the cursor advances and stops only when it’s exhausted (null) or stops advancing.
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:
git clone https://github.com/tapp-ai/fivetran-connector.git
cd fivetran-connector
uv sync --extra dev

2. Configure It

Copy the example configuration and fill in your API key. configuration.json is gitignored, so your key is never committed:
cp configuration.example.json configuration.json
configuration.json
{
  "base_url": "https://pub-api.conversion.ai/api",
  "api_key": "sk_live_<id>_<secret>"
}

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:
uv run fivetran debug --configuration configuration.json

4. Deploy to Fivetran

Deploy the connector to your Fivetran account, targeting your configured destination:
uv run fivetran deploy \
  --api-key <FIVETRAN_DEPLOY_KEY> \
  --destination <DESTINATION> \
  --connection conversion
Once deployed, Fivetran manages the sync schedule and loads each table into your destination warehouse.

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 in connector.py.
  • Select a subset of tables by trimming the EMAIL_STREAMS list or removing the contacts stream.
  • Change the page size via PAGE_LIMIT (the server applies its own hard cap).
  • Tune retries with MAX_RETRIES and BACKOFF_BASE_SECONDS.
See CONTRIBUTING.md in the repository for development guidelines.

Frequently Asked Questions

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.
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.
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.
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.