Data Manager uploads offline conversions straight from a query — and became a product you can find
Data Manager had half of what it needed: Customer Match audiences, but not conversion events. Both halves now exist, and the interesting part is the row source — a query result is not a file, so forcing it through one would add a writable bucket to a pipeline that otherwise needs none.
What shipped
upload_conversion_eventsandvalidate_conversion_events— offline conversions into Google Ads through Data Manager.- A query result as the row source.
rowstakes a SELECT's own output, sobigquery.run_query→ upload is two steps with nothing in between. No bucket, no export, no file. - Its own integration card, naming both capabilities and honest that it rides on the Google Ads grant as a scope union.
- Timestamps in four shapes — RFC3339, epoch seconds, epoch millis, and BigQuery's own
2026-09-15 10:00:00 UTC.
Rows that can never be attributed are dropped, not sent
An event carrying neither a click id nor a hashed identifier can never be attributed to anything. Google accepts it and reports nothing — the silent-failure shape this whole module exists to prevent — so those rows are counted and dropped rather than uploaded.
A conversion time that cannot be parsed is refused rather than coerced to the epoch, for the same reason: a timestamp silently set to 1970 is worse than a rejected row, because nothing downstream reveals it.
Three things a live test found that reading could not
eventSourceis required, although the v1 discovery doc marks it an ordinary optional field. It is refused rather than defaulted — "where did this conversion happen" is a data-quality decision, and stamping every warehouse batch as WEB would mislabel offline data in a way no downstream report reveals.- A conversion keyed by a hashed identifier is an Enhanced Conversion for Leads and needs the account enabled for it; one keyed by a click id needs nothing. One such row fails the whole batch, so the refusal now counts which rows they are.
- "Resource not found" on a conversion action that plainly exists turned out to be propagation: a newly created action answered that for around 20 minutes, then accepted a byte-identical request. Told as a dead end it reads "wrong id"; told as propagation it reads "wait".
details[].fieldViolations — so three precise answers had been arriving as dead ends.Two corrections worth stating plainly
- The first row is not the schema. An inline batch whose first row had no
emailkey silently dropped the row that did have one, because the column list came fromrows[0]alone. Headers are now the union over a bounded sample. A warehouse SELECT is uniform, so this would have hidden for a long time. - A dry run cannot validate Customer Match terms. Google validates the terms declaration even for a preview, so that tool had been answering 400 since it shipped. The lane now splits honestly: the genuinely local part — how many rows survive normalization and why the rest did not — is computed without asking Google, and says so.
Frequently asked questions
- Can I upload offline conversions from BigQuery without exporting a file?
- Yes. The rows argument takes a SELECT's own output, so a BigQuery query followed by an upload is two steps with nothing in between — no bucket, no CSV, no export step.
- What happens to rows that cannot be attributed?
- They are counted and dropped rather than sent. An event with neither a click id nor a hashed identifier can never be attributed, and Google accepts it while reporting nothing — which is the silent failure this module exists to prevent.
- Why does my upload fail with "Resource not found" on a conversion action that exists?
- Most likely propagation. A newly created conversion action was measured answering that for around 20 minutes before accepting a byte-identical request. It is no longer reported as a permanent failure.
- Do I need anything extra to upload conversions keyed by email?
- Yes — a conversion keyed by a hashed identifier is an Enhanced Conversion for Leads and the account has to be enabled for it. One keyed by a click id needs nothing extra. A single such row fails the whole batch, so the refusal names which rows they are.
- Are customer identifiers hashed before upload?
- Yes, identifiers are hashed before anything leaves. Data Manager also has its own OAuth scope, requested as a union with the Google Ads grant.