Integrations
Update

Data Manager uploads offline conversions straight from a query — and became a product you can find

TL;DR

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_events` and `validate_conversion_events` — offline conversions into Google Ads through Data Manager.
  • A query result as the row source. `rows` takes a SELECT's own output, so `bigquery.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`.
Identifiers are hashed before anything leaves. The card says so rather than leaving you to assume it.

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

  • `eventSource` is 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".
None of these were visible in the error message. Data Manager answers every bad request with "There was a problem with the request." and puts the sentence that matters in `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 `email` key silently dropped the row that did have one, because the column list came from `rows[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.
Try it on your own account

Start free — 2,500 credits a month, no credit card. Reads are free, and every write waits for your approval.

Related releases