Conversions and customer lists
This page is for the marketer. It covers two things that you do with your own records, such as a CRM export or a sales sheet:
- Sending conversions: telling a platform which leads and clicks became customers, so it can count them for the ads that brought them.
- Uploading a customer list: giving a platform a list of people to use as an audience, to keep ads away from existing customers or to reach them.
Each exists on Meta, Google Ads and LinkedIn, which makes six uploads.
These are people's contact details
Both send people's details to an advertising platform. Send only a list that you are allowed to use this way. The server hashes what the platform accepts as a hash before it sends, and stores nothing of a row. See What is hashed.
What has to be on first
All six are off on a new installation. An administrator opens each one separately:
| Upload | Page | Switch |
|---|---|---|
| Conversions to Meta | Meta app, under Meta Ads | Allow sending conversions to a pixel |
| Customer list on Meta | Meta app, under Meta Ads | Allow customer lists as audiences |
| Conversions to Google | Safety limits, under Google Ads limits | Allow uploading offline conversions to Google |
| Customer list on Google | Safety limits, under Google Ads limits | Allow Customer Match lists on Google |
| Conversions to LinkedIn | LinkedIn app, under LinkedIn Ads | Conversions: ask for rw_conversions |
| Customer list on LinkedIn | LinkedIn app, under LinkedIn Ads | Matched audiences: ask for rw_dmp_segments |
You also need:
- Write access for your user.
- The permission on your own connection, for Google and LinkedIn. Each of those four switches adds a permission to the sign-in. If you connected before the switch was opened, reconnect on Google connection or LinkedIn connection. The two Meta switches ask for no new permission.
When something is missing, the answer says what, and where it is changed. For the administrator's side, see Contact data.
Three ways to hand over the rows
- A file in a data folder. Your AI client names the path of a CSV file that lies in a folder on the server which the administrator opened for this. The rows never pass through the conversation. This is the best way, and the only way for more than 2,000 rows.
- Values that are already hashed. You hash the emails, phone numbers and names yourself, by the platform's own rules, and say that they are hashed. The server checks that each is a SHA-256 hash (64 hexadecimal characters) and passes it on.
- Rows in the conversation. Up to 2,000 rows. The server hashes them before sending, but your AI client has seen them in plain form.
For conversions there is a better way still, which needs no contact detail at all: see Send conversions.
The file
- A CSV in UTF-8, with at most 500,000 rows.
- The first row names the columns. A file with no header row is refused.
- Commas, semicolons or tabs between values all work.
- It must lie inside a data folder. A path outside one is refused. The file is read where it lies and is never copied.
- A column that the upload does not take is named in the answer and ignored. It is never sent.
- Common spellings of a column name are understood, such as
mobileforphoneandsurnameforlast_name.
The columns for a person are the same in every upload that takes them:
| Column | What goes in it |
|---|---|
email | An email address. |
phone | Meta and Google only. A phone number. Write it with the country code, as +966... or 00966.... For numbers written without one, tell the AI client the country's dialling code. A number that starts with a single zero and has no code cannot be used. |
first_name, last_name | The person's name. |
city, state, zip | Meta only (zip also for Google). |
country | Two letters, such as SA, EG or KW. |
Each upload adds its own columns, below.
Send conversions
Send the won deals in
D:\CRM-exports\won-october.csvto Meta as conversions for the Acme pixel.Upload these closed sales to Google as offline conversions. Everyone in the file agreed to their data being used for advertising.
The preview says how many rows were read, how many would be sent, and how many were left out and why. A row is named by its number, never by what is in it. Nothing is sent until you confirm.
To Meta
Needs: a pixel (dataset) of the ad account.
Columns: event_name and event_time on every row, then either lead_id, or email / phone with the other person columns. Optional: value, currency, event_id, external_id, crm_name.
- Prefer Meta's own lead id. If the lead came from a Meta lead form, its
lead_idis in the export (see Leads). A row with alead_idneeds no email and no phone. Itsevent_nameis the stage in your CRM's own words, such as "Qualified" or "Converted", and you say which system it comes from. - A row without a
lead_idneeds an email or a phone, and one of Meta's standard event names, such asLead,PurchaseorCompleteRegistration. APurchaseneeds avalue. event_timemust be within the last 7 days (62 for a sale in a physical shop). One older row refuses the whole call, naming the row.- The preview is the server's own check. Meta has no way to check events without taking them, so Meta has seen nothing until you confirm.
- A test code from Meta's Events Manager is a real send, not a preview, and Meta counts those events too.
To Google Ads
Needs: a conversion action in the account whose type takes uploads. One can be made from here when the administrator allows changes to conversion actions: see Google Ads.
Columns: conversion_time on every row, then a click id (gclid, or gbraid / wbraid for a click from iOS; one of the three), or email / phone. Optional: first_name, last_name, country, zip, value, currency, order_id.
- Prefer the click id. A row with a click id needs no contact detail. A Google lead's click id is in the export as
click_id. conversion_timemust carry its time zone offset, as2026-10-01T09:30:00+03:00. One without is refused.- Rows with an email or a phone work only when the Google Ads account has accepted Google's customer data terms and has enhanced conversions for leads switched on. The server reads both, and the refusal says where they are switched on.
- A name is used only when the last name, the country and the postal code are all there.
- You state consent: see Consent and terms.
- The preview is checked by Google. The same request is sent in a mode where Google checks every row and records nothing.
To LinkedIn
Needs: a conversion in the ad account whose method is the Conversions API. See LinkedIn Ads.
Columns: happened_at on every row, and email, or both first_name and last_name. Optional: value, currency, event_id, company, title, country.
happened_atmust be within the last 90 days. One older row refuses the whole call, naming the row.- The email and the names are hashed. A company, a job title and a country are sent as they are, because LinkedIn takes no hash of them, and only beside both names. Leave those columns out to send hashes alone.
- The preview is the server's own check. LinkedIn has no way to check events without taking them.
Upload a customer list
Create a customer list called "Customers 2026" on Meta, and fill it from
D:\CRM-exports\customers.csv.Add these people to our Customer Match list on Google. They all agreed to personalised advertising, and I accept Google's Customer Match terms.
A list is made empty first and filled afterwards. Making one spends nothing. A list is an audience: to use it, add it to a campaign's targeting or exclusions.
On Meta
Columns: email or phone (a row needs one of them), and optionally first_name, last_name, city, state, zip, country. All are hashed.
- You accept Meta's Custom Audience terms yourself, for the ad account, at Meta. The server never accepts them for you. If you have not, the refusal carries Meta's link.
- When you make the list, Meta is told where its data came from. The default says that you collected it directly from your own customers. If that is not so, say so.
- Meta says nothing back about who it matched, and shows ads to a list only once it has matched 100 people.
- The preview is the server's own check.
On Google Ads
Columns: email or phone (a row needs one of them), and optionally first_name, last_name, country, zip. A name is used only when the last name, the country and the postal code are all there.
- You state consent and accept Google's Customer Match terms: see Consent and terms.
- A person stays in the list for up to 540 days after their last upload.
- Google serves a list once it has matched 100 active people. A young or small account may only observe or exclude with it. Google decides this.
- The preview is checked by Google.
On LinkedIn
A LinkedIn list is either a list of contacts or a list of companies. You choose when you make it, and the list's own kind then says what a row is.
Columns for contacts: email, or both first_name and last_name. Optional: title, company, country.
Columns for companies: one of company, domain, email_domain or page_url, and optionally country.
- The email is hashed. A name, a job title and a company are sent as plain text, because LinkedIn takes them no other way. Leave them out to send hashed emails alone.
- A company row is business data and goes as it is.
- LinkedIn takes up to 48 hours to match a new list, and serves it only once it has 300 members.
- The preview is the server's own check.
Consent and terms
Some statements are yours alone. The server has no default for them, and your AI client is told to ask you and to pass on exactly what you say.
| Statement | Where it is asked | What it means |
|---|---|---|
| Consent to data use | Google: conversions, and adding to or replacing a list | Whether these people agreed to their data being sent to Google for advertising. Granted or denied, as you state it. |
| Consent to personalisation | The same | Whether they agreed to personalised advertising. |
| Google's Customer Match terms | Google: adding to or replacing a list | That you accept Google's Customer Match terms. It is sent to Google as your acceptance. Without it the upload is refused. |
| Meta's Custom Audience terms | Meta: customer lists | You accept them at Meta, for the ad account. The server only reads whether you have. |
| Where the data came from | Meta: making a list | Your own customers, a partner, or both. |
Removing people from a Google list needs none of these: Google does not ask for them there.
Whether you may lawfully use a list this way is yours to confirm. See the terms.
Sending twice
| Upload | What a second send does | What the server does about it |
|---|---|---|
| Conversions to Meta | May count them twice. Meta does not remove a repeat that is sent this way. | A file that was already sent to that pixel is refused unless you ask to send it again. |
| Conversions to Google | Google can recognise a repeat by its id. | The same guard. A row with no order_id gets an id worked out from the row, the same on every send. |
| Conversions to LinkedIn | May count them twice. | The same guard. Give each row its own event_id where your records have one. |
| Any customer list | Nothing. A person who is already in a list stays in it once. | No guard is needed. |
The guard recognises a file. Rows that were given in the conversation cannot be recognised again, because nothing of them is kept. Do not send those a second time unless you mean to.
When only part goes through
Large uploads go in batches. If a platform refuses one batch for what is in it, the others still go, and the answer says that the upload was partly applied and which rows did not go. Fix those rows and send only them. Nothing is retried by itself.
For conversions to Meta and for both Google uploads, one bad row refuses its whole batch. LinkedIn answers a list upload row by row, so there only the refused rows are left out.
Conversions to LinkedIn are the exception: a refused batch stops the upload. The answer says how many events had already gone. Do not send the whole file again, because LinkedIn may count those twice: fix the rows and send only what did not go.
Removing and replacing
Taking people out of a list can change who sees your running ads, so it asks for a second, explicit confirmation. The preview is free, and on Meta it names the ad sets that use the list.
- Remove takes the people in your rows out of the list. On LinkedIn a row is removed only when it matches what was added, so send the same columns.
- Replace (Meta and Google) puts your rows in and takes out everyone else. If a replace stops partway, the answer says so: run it again whole. On Google the list then still holds the old people as well as the new. LinkedIn has no replace.
- Deleting a whole list (Meta and LinkedIn) is a separate request and also asks twice. A list is not deleted while a running ad set or campaign uses it.
What is hashed and what is stored
Before it sends, the server tidies each value by the rules of the platform it is going to and hashes it with SHA-256, wherever that platform takes a hash. A few things go as they are, because the platform takes them no other way:
- to Google: the country and the postal code;
- to LinkedIn: the country, and with conversions a company and a job title; in a contact list also the name;
- ids that are not a person's details: Meta's lead id, Google's click id, your own order id.
Nothing of a row is stored on the server. The activity log keeps how many rows went in, how many were sent or left out and why, and a file's fingerprint and size. It never keeps a name, an email, a phone number or a hash.
Contact data has the full table.