Skip to content

Prepare a CSV for offline conversions

Offline conversions reach your advertising platforms as a CSV file that Signals reads from a location you control. This article covers what the file has to contain, how each value has to be formatted, and what to check before you send the first one.

One file serves nine destinations: Meta, Google, Snapchat, TikTok, LinkedIn, X, Reddit, Pinterest and OpenAI. Which columns each one requires differs, and that is covered below. Google Store Sales is the exception and uses its own template.

  • A source instance in Studio with Offline Events selected as the Integration Type, and a delivery location connected. Signals reads from AWS S3, Google Cloud Storage, FTP or SFTP.
  • The sample file for Offline Events, which carries the exact column names and two example rows. Build your export against that rather than against a list you have typed out.
  • Agreement on what your conversion event is called, since that name is what the destination reports against.
  • A way to apply SHA-256 hashing in your export process, since personal data has to be hashed before it leaves your systems. Apply SHA-256 hashing to PII has snippets for Google Sheets, SQL and the common languages.
  • Use a consistent pattern that carries the date on which the file is uploaded, for example sample_offline_conversion_2026_08_06.csv.
  • If you send more than one file for the same date, add a numeric suffix to the second and later files.
  • Keep the naming identical every time. An inconsistent name is the most common reason a file sits in the folder and nothing happens.

Three columns are required by every destination, and a further five are required by some of them or on some rows.

ColumnWhat goes in it
event_timeThe time of the conversion, as a UNIX timestamp. Use the moment the purchase happened, not the moment the record was created.
event_nameThe name of the action, for example Purchase. This is what the destination reports against.
last_modified_dateWhen the record last changed, as a date and time.
email_1_hashedHashed email address. Required by every destination except OpenAI.
One phone columnAt least one of the six phone columns. Required by Meta, Snapchat and TikTok.
currencyThree-letter ISO currency code. Required on Purchase rows only.
valueThe amount, as a decimal number. Required on Purchase rows only.
action_sourceWhere the conversion happened. Required by Meta, Snapchat and TikTok. One of phone_call, chat, physical_store, system_generated or other.

These are optional. Send the ones your destination reports on.

ColumnNotes
product_typeproduct or product_group.
product_name, product_category, product_ids, product_brand, product_price, product_quantityThe product side of the conversion. product_ids takes an array.
product_group_idIdentifier for a group of product variants.
transaction_idYour reference for the order. This is what stops the same purchase being counted twice if a file is read again.
number_items, item_numberThe item count, and a unique reference to distinguish events inside one order.
delivery_methodin_store, curbside or delivery.
customer_statusnew, returning or reactivated.
predicted_ltvPredicted lifetime value for the conversion.
subscription_id, shop_id, store_code, store_page_id, brand_page_idSubscription and store references, where the destination uses them.
custom_parameter_1, custom_value_1One custom key and value. Meta passes them through as sent and Google accepts one. Snapchat, TikTok and X do not accept custom parameters.

A row with no identifier cannot be matched to anyone, so this is where match rates are won. Send as many as your transaction record holds.

ColumnNotes
email_1_hashed, email_2_hashed, email_3_hashedUp to three addresses, one per column. The first is required, the others optional.
phone_1 to phone_3, in format_a and format_bSix columns. Send E.164 in the format_b column.
first_name_hashed, last_name_hashedRecommended by Meta and Snapchat.
city_hashed, state_hashed, country_hashed, zipcode_hashedHashed location. Recommended by Meta and Snapchat.
state, zipcode, countryThe same location fields in plain text, for the destinations that want them unhashed.
gender_hashed, date of birth fieldsFour date of birth columns: year, month, day and a single combined column.
external_idYour own identifier for the person, such as a loyalty number or an external cookie ID. Sent in plain text.
fbclidThe Meta click ID for the conversion.
fbcThe Meta browser cookie value, in the form fb followed by the subdomain index, the creation time and the click ID.
fb_login_idThe numeric Facebook user ID, where you have it.
gclid, gbraid, wbraidGoogle click identifiers, all optional. Set at most one of the three on a row.
snapchat_click_idSnapchat click identifier.
client_user_agent, client_ip_addressSent in plain text. Used by Google, Snapchat, Reddit and Pinterest.
first_name, last_name, title, company_namePlain text, used by LinkedIn.

Normalize the value first, then hash it. A hash only matches if both sides normalized the value the same way, so formatting is not cosmetic here.

FieldFormat
event_timeUNIX timestamp.
EmailTrim leading and trailing whitespace and convert everything to lowercase. One address per cell.
PhoneE.164. A leading plus sign, then the country code and the number, up to fifteen digits, with whitespace, brackets and hyphens removed. For example +919876543210. Put this in the format_b column.
Names and cityLetters a to z only. Trim whitespace, lowercase, no punctuation or special characters.
State, hashedFor the United States, the two-character code in lowercase. Elsewhere, the full region name in lowercase with no punctuation, special characters or whitespace.
State, plainFor the United States, the two-letter code. Elsewhere, the full region name.
PostcodeLowercase, no whitespace. United States, first five digits only. United Kingdom, the Area, District and Sector format.
CountryTwo-letter ISO 3166-1 alpha-2 code. Lowercase in the hashed column.
Genderm for male, f for female.
Date of birthDD/MM/YYYY in the combined column, or YYYY, MM and DD in the three separate columns.
  • Every column whose name ends in _hashed carries the SHA-256 hash of the normalized value, not the value itself.
  • Everything else stays in plain text. That includes the whole event side of the file, external_id, the plain state, zipcode and country columns, the click identifiers, the user agent and the IP address.
  • Never hash an empty cell. Hashing a blank produces a valid-looking 64-character value that matches nothing, and it is a common export bug. Leave the cell empty instead.
  • A hashed email address is required by every destination except OpenAI.
  • Meta, Snapchat and TikTok require a phone number and an action source. The others do not.
  • Meta and Snapchat recommend the name and location fields. Pinterest accepts them as optional. Google, TikTok, X, LinkedIn and Reddit largely do not use them.
  • Click identifiers are per platform: fbclid and fbc for Meta, gclid, gbraid or wbraid for Google and all three optional, snapchat_click_id for Snapchat.
  • LinkedIn is the only destination that uses the plain first name, last name, title and company name columns.
  • Custom parameters go through to Meta as sent, Google accepts one, and Snapchat, TikTok and X accept none.

Store Sales has its own 22-column template rather than extra columns on this one. It keeps the same event and identifier basics, adds language_code, third_party_user_id and a custom key and value pair, and takes the location fields in plain text rather than hashed. Use the Store Sales sample file, not this one.

  • A comma-separated .csv file.
  • A header row naming the columns. You only need the columns you are actually sending, so a file with fewer columns than the template is fine.
  • There is no limit on the number of rows or on the size of the file.

A header mismatch fails quietly. A column whose header does not match the template is skipped rather than reported, and the file processes normally without it, so a renamed or misspelled header does not produce an error. It produces events with that field missing. If a match rate is lower than you expected on a file that processed cleanly, compare the header row against the template first, including case and underscores.

Rows older than the destination’s attribution window are dropped without an error, so filter them out in the export rather than sending them and wondering where they went.

  • Meta accepts conversions up to 90 days old.
  • Google Store Sales accepts conversions up to 60 days old.

Daily is the recommendation. Three files a week is the practical minimum. Sending less often than that gives the destination less to learn from, and shows up as weaker optimization rather than as an error.

  • Column names match the sample file exactly, including case and underscores.
  • The file name carries today’s date, and a second file for the same date has a suffix.
  • event_time is a UNIX value and last_modified_date is populated.
  • Purchase rows carry both currency and value.
  • Phone numbers are in E.164, in the format_b column, and carry a country code.
  • Every _hashed column contains a hash, and no blank cell has been hashed.
  • No row is older than the destination’s attribution window.
  • The same date has not already been sent in an earlier file.