Skip to content
Meta Conversions API

Offline Conversions from Google Sheets to Meta: Columns, Hashing and Deduplication

How to send sales and qualified leads from a Google Sheet to Meta via the Conversions API: required columns, date and phone formats, hashing and the 7-day limit.

Convs teamUpdated: 7 min read
Connections in the panel: a Google Sheets source and a Meta destination
On this page
  1. What are offline conversions in Meta?
  2. Which columns does the sheet need?
  3. How is data from the sheet hashed?
  4. How do you map statuses to Meta events?
  5. How does deduplication work with a sheet?
  6. How do you check that events arrive?
  7. Common errors and their causes
  8. When is a spreadsheet the wrong source?
  9. Next step

Plenty of businesses run their pipeline in a Google Sheet: a sales rep adds a row, marks it "qualified", later "won". Meta never hears about it, because the Pixel sees your website, not your spreadsheet. The Conversions API lets you pass those changes to Meta as offline conversions, meaning events that happened away from your site. This guide shows how to lay out the columns, how the data is hashed, how double counting is avoided, and where this approach stops working.

What are offline conversions in Meta?

They're events your business knows about and the browser doesn't: a phone sale, a signed contract, a lead qualified after a call. Meta now takes them through the same Conversions API as website events. It describes its older, separate offline conversions interface as legacy and recommends the Conversions API for new integrations.

An event from a sheet uses action_source = system_generated, i.e. raised by a business system. The key rules from Meta's server event parameters:

  • event_time is when the event really happened, at most 7 days before sending
  • one event that's too old makes Meta reject the whole request
  • each event needs at least one customer identifier

Which columns does the sheet need?

Column names are up to you, because you map them in the panel. What matters is the content.

Column (example)What it holdsWatch out for
idA stable record ID, e.g. your CRM numberNot the row number, which changes when you sort. No emails or names
statusBusiness status, e.g. QUALIFIED, CONVERTEDCase must match the flow
status_changed_atWhen the record entered that statusA date cell or ISO 8601 with time zone. Not the sync time
emailCustomer emailHashed before sending
phonePhone with country code, e.g. +14155550123Text column, so the sheet keeps the plus sign
meta_lead_idLead ID from a Meta Instant FormText only, or the sheet may round the long number
value, currencyAmount and ISO 4217 code, e.g. EURRequired for Purchase
consentTRUE when you have a lawful basis to share the dataDon't fill it in wholesale without checking your process

The minimum is record ID, status, time, consent and at least one person identifier: email, phone or Meta lead ID.

Why does the status change time matter so much?

Because Meta ties a conversion to the moment it happened. If a tool stamped events with the sync time, a Friday sale would look like a Monday sale. So Convs never substitutes the read time for a missing date: a row without one is rejected, and the message points to the first failing row number without showing any customer data.

Date cells are read in the sheet's own time zone. Text dates must include a zone, e.g. 2026-04-14T10:30:00-05:00.

How is data from the sheet hashed?

Meta requires email and phone to be normalised and SHA-256 hashed, while the lead ID is sent as is. Following Meta's customer information parameters:

  1. Email: trim spaces, lowercase, then SHA-256. Jane.Doe@Example.com and jane.doe@example.com produce the same hash.
  2. Phone: strip spaces, dashes and brackets; the country code is mandatory; hash the digits only. +1 (415) 555-0123 becomes the hash of 14155550123.
  3. Meta lead ID: not hashed, because Meta knows it in that form.

Convs does this on the server. A phone number without a country code is rejected rather than guessed, because a wrong prefix produces a hash that matches nobody. Hashes are still personal data under laws like the GDPR; see GDPR and hashing in the Conversions API (not legal advice).

How do you map statuses to Meta events?

A flow defines which source status becomes which Meta event. For example:

Status in the sheetMeta event
QUALIFIEDQualifiedLead
CONVERTEDPurchase (with value and currency)
LOSTnot mapped, so nothing is sent

Pick event names that match what you'll later use in a campaign or custom conversion. One source can feed several destinations (two Pixels, say), and each destination has its own delivery status. For designing stages Meta can learn from, read CRM lead stages for Meta.

Connections in the panel: a Google Sheets source and a Meta destination
Connections in the panel: a Google Sheets source and a Meta destination

How does deduplication work with a sheet?

A sheet read every minute means the same row is seen hundreds of times. If every read sent an event, Meta would get a flood of duplicates. There are two layers of protection:

  • Business event: a record ID plus status from a source is a one-off conversion. Re-entering the same status doesn't create a new one.
  • Delivery: each event has a stable event_id derived from the record ID and status, and the queue won't send the same event to the same Meta dataset in the same mode twice, even from two different connections.

Retries keep the same event_id, so if Meta's response is lost in transit, Meta can still deduplicate the repeat. The Pixel never sends sheet events, so there's nothing to reconcile with the browser. More in Pixel and CAPI event deduplication.

What can't a sync reconstruct?

A sheet shows a row's current state, not its history. If a rep moves a row from QUALIFIED to CONVERTED within a minute, a read might only see the second status. If statuses can change faster than the sheet is read, log events in a separate tab: one row per change, each with its own ID.

How do you check that events arrive?

  1. Create a Meta destination in test mode with the code from Events Manager.
  2. Change the status of one row containing test data.
  3. Check the delivery in the panel: "Accepted by Meta" means a response with events_received: 1.
  4. In Events Manager's test events tab, confirm the event arrived with the right name and parameters.
  5. Only then create a production destination and flow. Test and production keep separate deduplication.
The delivery list showing the result of each attempt to Meta
The delivery list showing the result of each attempt to Meta

Acceptance says nothing about how well Meta matched the event to a person. Events Manager shows that as Event Match Quality; see how to improve Event Match Quality.

Common errors and their causes

SymptomCauseFix
Row rejected: missing timeEmpty date column, or text without a time zoneFill in the date or add the zone in ISO 8601
Row rejected: phoneNumber without country codeStore +<country code>… in a text column
Event too oldStatus changed more than 7 days agoMeta won't take it. Keep the sheet current
Nothing is sentNo active flow, or the status uses different letter caseCheck the flow mapping
Data change rejectedEditing a row already reported with the same statusA new business event needs a new record ID

When is a spreadsheet the wrong source?

  • You update statuses once a week. Some events will pass the 7-day limit and be lost. The sheet has to be kept current.
  • Your leads come from Meta Instant Forms and you want conversion leads optimisation. Meta expects CRM events in a specific format for that goal. It's simpler to connect the form directly and update stages on the person's record. The requirements are in Meta conversion leads optimisation.
  • You have tens of thousands of active rows. At 500 rows per minute a full pass takes a while; a server-side integration suits that volume better.
  • The sheet has no person identifier at all. Without email, phone or lead ID, Meta has nothing to match against.

Next step

Add the columns from the table, connect the sheet in test mode and change one row's status. Read more about delivery on the Meta Conversions API feature page; plans are on the pricing page.

Frequently asked questions

Do I need to install Apps Script in my sheet?

No. You connect the sheet by signing in with Google and picking the file in Google's own file picker. The app only gets access to the file you choose, not your whole Drive, and it only reads data.

How quickly does a change in the sheet reach Meta?

The server reads the sheet every minute, up to 500 rows per pass, so a full pass over a large sheet takes a few minutes. A new status goes into the delivery queue and from there to Meta, usually within minutes of the edit.

What happens if I change the status of a row that was already sent?

A new status is a new event, so it is sent if your flow maps it. The same record ID with the same status is a one-off conversion: re-entering a status doesn't create a second conversion, and changing the data of a pair that was already reported is rejected.

Why does the Meta lead ID change in my sheet?

Lead IDs are long numbers, and a spreadsheet may treat them as numeric and round the last digits. Format the column as plain text, or paste IDs with a leading apostrophe, before you start syncing.

Are sheet events enough for conversion leads optimisation?

Meta's conversion leads goal expects CRM events in a specific format, tied to leads from its native Instant Forms. If your leads come from Instant Forms, connect the form directly and update stages on the person's record instead. A sheet works well for sales and for leads that don't come from Meta forms.

See what Meta sees

Connect your ad account and see which forms are ready and which need fixing. Free up to 100 leads a month, no card required.