Skip to main content
This guide is for data administrators who know their warehouse but are new to Conversion. It walks through planning and building a sync that loads activity rows, such as logins, feature usage, or purchases, into Conversion as custom events on contacts. For schedules and incremental syncing in general, see the Data Warehouse Sync overview. The short version: write one SELECT that returns one row per event, with columns named user_id or email, event_name, timestamp, and event_id. Every other column becomes a property of the event.

How warehouse rows map to Conversion

A custom event records that a contact did something at a point in time. Unlike contacts and custom objects, events are never updated: each row adds one event to one contact. Once synced, marketers can use the events in:

Column reference

Conversion reads the column names your query returns, ignoring case. It doesn’t use field keys or a mapping screen for events.

Set up a custom event warehouse sync

Before you begin

  • A connected warehouse with a read-only user that can see the tables you plan to sync
  • Admin access in Conversion to create syncs
  • Your contacts already in Conversion, from a contacts sync or another source. An event sync only creates contacts if you turn on Create contacts for unmatched emails.

1. Agree on the events with your marketers

Before you write any SQL, agree with your marketers on which events to send and what they need from each one. Triggers, filters, and scoring rules match events by name and filter on their properties, so the names and properties you choose are what marketers will build with. Write down each event name and its properties before you start. Keep names stable: renaming an event later means updating every trigger and filter that uses it.

2. Write the query

Return one row per event. Name the columns as in the column reference, and convert the timestamp to Unix seconds as text. This example uses Snowflake syntax:
To sync several event types from one table, select the type column as event_name instead of a fixed string:
Filter to the event types your marketers agreed on. Every row becomes an event, so an unfiltered events table can send millions of events nobody uses. The timestamp and last_sync_time syntax differ by warehouse. See Timestamps and your warehouse’s guide: BigQuery, Snowflake, ClickHouse, or Databricks.

3. Create the sync

  1. Go to Settings → CRM and syncing → Syncs.
  2. Click Add sync.
  3. Enter a Name and Description, and pick the Connection.
  4. For Object, choose Custom events.
  5. Set the Frequency. Events always use the Upsert mode.
  6. Turn on Create contacts for unmatched emails if events should create contacts that don’t exist yet. This requires an email column.
  7. Turn on Subscribe new contacts if the contacts this sync creates should be subscribed to email.
  8. Click Edit query, paste your query, and click Preview to check the first 100 rows.
  9. Click Create sync.
  10. Open the sync and click Activate.
See Data Warehouse Connections and Syncs for more on each setting.

4. Check the results

  1. Open the sync’s Logs tab and check the rows processed and any skipped or failed rows. See Common mistakes for what each reason means.
  2. Open a contact you expect to have events, go to its activity log, and filter to Custom events. Confirm the event name, time, and properties.
  3. Share the result with your marketers and confirm they can build the triggers, filters, and scoring rules you agreed on.
Onboarding with Conversion? Let your Deployment Strategist know when this is done and we’ll check it on our end.

How contacts are matched

Each row must identify one contact with user_id, email, or both.
  1. If the row has a user_id, Conversion looks for the contact with that user ID.
  2. If none is found and the row has an email, Conversion looks for the contact with that email.
  3. If the email matches a contact that has a different user ID, the row is skipped as Conflicting identifier.
  4. If no contact matches, the row is skipped as Contact not found, unless Create contacts for unmatched emails is on. Then Conversion creates the contact from the row’s email (and user ID, if present). Rows without a valid email are still skipped.
Use the same identifier your contacts sync uses. Prefer user_id when you have it, since it doesn’t change when a user updates their email.

Timestamps

Name the column timestamp. Conversion stores it in UTC.
  • Send it as text. A numeric value fails with Invalid timestamp, so cast it to a string in your query.
  • Unix seconds (1730073600) are the safest format. Unix milliseconds and ISO 8601 strings (2026-10-28T00:00:00Z) also parse.
  • If the column is missing or the value is null, the event is stamped with the time it was synced.
{{last_sync_time}} filters which rows a run reads. The timestamp column sets when each event happened. They can come from different columns: if events can land in your warehouse late, filter on the time the row was loaded and send the time the action happened as timestamp.

Duplicates and backfills

With an event_id, Conversion keeps one event per ID. If a later run sends the same event_id again, for example because of a time buffer or a re-run, no second event is created. Repeats within three days show in the sync’s logs as Duplicate event. Without an event_id, every row creates a new event, so overlapping runs create duplicates. An event_id must be unique across every event sync in your workspace, and across events sent through the Events API. Conversion doesn’t scope it by sync or event name, so if two tables both have a row with ID 123, one event silently replaces the other. Prefix the table’s primary key with the table or event name, such as 'order:' || o.id. If the table has no single key, build one that is stable across runs, such as the user ID, event name, and timestamp joined together. On the first run, {{last_sync_time}} is 0, so the query returns all history. To start from a recent date instead, add a fixed lower bound to the WHERE clause.
Before the first run of a sync with a lot of history, check with your marketers which workflows and lead scoring rules use these event names, so a large backfill doesn’t surprise them.

Common mistakes