{"id":450,"date":"2026-09-08T10:10:00","date_gmt":"2026-09-08T08:10:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/?p=450"},"modified":"2026-10-24T02:25:45","modified_gmt":"2026-10-24T00:25:45","slug":"automated-ga4-meta-capi-event-validation-in-bigquery","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/","title":{"rendered":"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery"},"content":{"rendered":"<p>Reconciling GA4 with Meta CAPI shows which expected business events are evidenced in each source. A difference does not establish its cause: consent, different event definitions, processing delays and technical failures can produce the same pattern. GA4 is not necessarily a browser-only stream either.<\/p>\n<h2>1. Define what should match first<\/h2>\n<p>The starting point is a reliable list of business events, such as confirmed orders. Comparing only two tracking tables cannot reveal an event missing from both. The reference list also needs its own completeness checks.<\/p>\n<p>A stable key connects the sources. UUIDv4 is an option, not a requirement or encryption method. Retries retain the same identifier for the same event; distinct events receive distinct identifiers. An additional scope prevents collisions between shops or data sources.<\/p>\n<p>Event names need an explicit mapping, for example GA4 <code>purchase<\/code> and Meta <code>Purchase<\/code> to the common type <code>purchase<\/code>. GA4&#8217;s <code>transaction_id<\/code>, a custom reconciliation parameter and Meta&#8217;s <code>event_id<\/code> are separate fields. GA4 purchase deduplication and Meta Pixel\/CAPI deduplication have their own rules. A BigQuery match proves neither advertising attribution nor successful deduplication at Meta.<\/p>\n<figure class=\"lw-diagram\"><img decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/dia-450-reconciliation-20261024-en.png\" alt=\"An independent reference list exposes missing evidence on both sides. Delivery attempts are aggregated before comparison.\" width=\"1120\" height=\"580\" loading=\"lazy\"><figcaption>An independent reference list exposes missing evidence on both sides. Delivery attempts are aggregated before comparison.<\/figcaption><\/figure>\n<h2>2. Prepare three tables before comparing<\/h2>\n<p>The example below requires custom tables. Linking GA4 does not create these tables automatically; ingestion and data-quality checks must be implemented first.<\/p>\n<ul>\n<li><code>expected_events<\/code>: exactly one row per <code>scope_id<\/code>, <code>event_kind<\/code> and <code>event_id<\/code>, plus <code>business_time<\/code> for the actual business event and BOOL fields <code>expected_ga4<\/code> and <code>expected_meta<\/code>. TRUE means identifiable transmission to that service is intended and permitted; FALSE means not expected; NULL means unresolved.<\/li>\n<li><code>ga4_observations<\/code>: one row per actually exported event, with the same composite key and ingestion timestamp <code>loaded_at<\/code>. Re-ingesting the same export row must not create another observation. Events actually recorded more than once remain visible.<\/li>\n<li><code>capi_attempts<\/code>: one row per delivery attempt and associated event, with the key, <code>loaded_at<\/code> and <code>receipt_state<\/code>. Allowed states are <code>received<\/code>, <code>rejected<\/code> and <code>unknown<\/code>. Only evidence attributable to the particular event qualifies as <code>received<\/code>.<\/li>\n<\/ul>\n<p>HTTP 200 alone is not proof of attribution. A response containing a received-event count also needs a valid association with the request. An ambiguous or partial batch response must not mark every event as received. Failed and uncertain attempts stay in the log; subsequent successful retries add further records. Access tokens and full customer payloads do not belong in this monitoring log.<\/p>\n<p>Blank identifiers, repeated parameter keys, unknown event names and observations without a reference event require separate error lists. Invalid records must not disappear silently. <code>scope_id<\/code> defines the fixed relationship between shop, GA4 property or stream and Meta data source.<\/p>\n<h2>3. Evaluate the business day and aggregate retries<\/h2>\n<p>The query uses a UTC business day four days in the past as an example. This allows time for ordinary delays but does not guarantee completeness. The export, reference list and CAPI log must be sufficiently current before evaluation. A missing table is an ingestion failure, not evidence of zero events.<\/p>\n<p>GA4 daily tables follow the property&#8217;s time zone. The documentation describes <code>event_timestamp<\/code> as receipt time; transmission and the business event can differ. The evaluated day therefore comes from <code>business_time<\/code>. Prepared observation tables must include relevant later arrivals. Reading only the GA4 daily table named for the business day is not generally sufficient.<\/p>\n<pre class=\"wp-block-code-beautifier-block-code\"><code>-- GoogleSQL. Prepared tables are defined in the article.\n-- @run_time is supplied by BigQuery Scheduled Queries.\nDECLARE target_day DATE DEFAULT DATE_SUB(DATE(@run_time, &#x27;UTC&#x27;), INTERVAL 4 DAY);\nDECLARE observed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP();\n\nCREATE TEMP TABLE expected AS\nSELECT scope_id, event_kind, event_id, expected_ga4, expected_meta\nFROM `project_id.monitoring.expected_events`\nWHERE business_time &gt;= TIMESTAMP(target_day, &#x27;UTC&#x27;)\n  AND business_time &lt; TIMESTAMP(DATE_ADD(target_day, INTERVAL 1 DAY), &#x27;UTC&#x27;);\n\nASSERT (\n  SELECT COUNT(*) = 0 FROM expected\n  WHERE scope_id IS NULL OR TRIM(scope_id) = &#x27;&#x27;\n     OR event_kind IS NULL OR TRIM(event_kind) = &#x27;&#x27;\n     OR event_id IS NULL OR TRIM(event_id) = &#x27;&#x27;\n) AS &#x27;Invalid key in expected events&#x27;;\n\nASSERT NOT EXISTS (\n  SELECT 1 FROM expected\n  GROUP BY scope_id, event_kind, event_id\n  HAVING COUNT(*) &gt; 1\n) AS &#x27;Duplicate business-event key&#x27;;\n\nCREATE TEMP TABLE result AS\nWITH ga4 AS (\n  SELECT g.scope_id, g.event_kind, g.event_id, COUNT(*) AS ga4_rows\n  FROM `project_id.monitoring.ga4_observations` g\n  JOIN expected e USING (scope_id, event_kind, event_id)\n  WHERE g.loaded_at &lt;= observed_at\n  GROUP BY g.scope_id, g.event_kind, g.event_id\n),\ncapi AS (\n  SELECT c.scope_id, c.event_kind, c.event_id,\n         COUNT(*) AS capi_attempts,\n         COUNTIF(c.receipt_state = &#x27;received&#x27;) AS capi_receipts,\n         COUNTIF(c.receipt_state = &#x27;unknown&#x27;) AS capi_unknown\n  FROM `project_id.monitoring.capi_attempts` c\n  JOIN expected e USING (scope_id, event_kind, event_id)\n  WHERE c.loaded_at &lt;= observed_at\n  GROUP BY c.scope_id, c.event_kind, c.event_id\n)\nSELECT target_day AS business_day, observed_at AS checked_at, e.*,\n       COALESCE(g.ga4_rows, 0) AS ga4_rows,\n       COALESCE(c.capi_attempts, 0) AS capi_attempts,\n       COALESCE(c.capi_receipts, 0) AS capi_receipts,\n       COALESCE(c.capi_unknown, 0) AS capi_unknown\nFROM expected e\nLEFT JOIN ga4 g USING (scope_id, event_kind, event_id)\nLEFT JOIN capi c USING (scope_id, event_kind, event_id);\n\nSELECT *,\n  CASE\n    WHEN expected_ga4 IS NULL OR expected_meta IS NULL THEN &#x27;SCOPE_UNKNOWN&#x27;\n    WHEN NOT expected_ga4 OR NOT expected_meta THEN &#x27;OUTSIDE_JOINT_SCOPE&#x27;\n    WHEN ga4_rows &gt; 0 AND capi_receipts &gt; 0 THEN &#x27;OBSERVED_BOTH&#x27;\n    WHEN ga4_rows = 0 AND capi_receipts = 0 THEN &#x27;UNCONFIRMED_BOTH&#x27;\n    WHEN ga4_rows = 0 THEN &#x27;UNCONFIRMED_GA4&#x27;\n    ELSE &#x27;UNCONFIRMED_CAPI&#x27;\n  END AS reconciliation_status,\n  ga4_rows &gt; 1 AS repeated_ga4_rows,\n  (expected_ga4 = FALSE AND ga4_rows &gt; 0)\n    OR (expected_meta = FALSE AND capi_attempts &gt; 0) AS unexpected_transmission\nFROM result;<\/code><\/pre>\n<p>Aggregation prevents two GA4 rows and three CAPI attempts from becoming six apparent business events. Repetition remains visible through counts. <code>UNCONFIRMED<\/code> means missing evidence in the available data, not necessarily a broken tag. <code>unexpected_transmission<\/code> separately flags transmissions not intended according to the reference.<\/p>\n<p>This is not a complete ingestion pipeline. Table access needs suitable partitioning or limits, permissions and a cost check. Store business day and evaluation time with results. Re-evaluation should deliberately replace older daily snapshots instead of appending duplicates that change the rate.<\/p>\n<h2>4. Alert on findings rather than successful query execution<\/h2>\n<p>A downstream <code>latest_reconciliation<\/code> view contains exactly the latest successful snapshot per business day and key. The separate query below returns only days exceeding an example threshold. The minimum of 100 and five-percent threshold need justification; neither is a universal quality standard.<\/p>\n<pre class=\"wp-block-code-beautifier-block-code\"><code>-- A separate scheduled SELECT over the latest successful snapshot.\nWITH counts AS (\n  SELECT business_day,\n    COUNTIF(expected_ga4 AND expected_meta) AS comparable,\n    COUNTIF(expected_ga4 AND expected_meta\n            AND (ga4_rows = 0 OR capi_receipts = 0)) AS unconfirmed\n  FROM `project_id.monitoring.latest_reconciliation`\n  GROUP BY business_day\n),\nrates AS (\n  SELECT *, 100 * SAFE_DIVIDE(unconfirmed, comparable) AS rate_pct\n  FROM counts\n)\nSELECT *\nFROM rates\nWHERE comparable &gt;= 100 AND rate_pct &gt; 5;<\/code><\/pre>\n<p>With no comparable events, <code>SAFE_DIVIDE<\/code> leaves the rate unknown. Events outside the joint scope and unresolved expectations stay outside this denominator. Separate checks must monitor those cases, unexpected transmissions, invalid IDs, repeated records and incomplete data feeds.<\/p>\n<p>The Scheduled Query metric <code>last_execution_job_rows<\/code> counts result rows, not the value of <code>rate_pct<\/code>. A Monitoring rule filtered to this violations-only query can alert when its row count exceeds zero. The previous value can persist after failed or disabled runs. Execution failures and freshness therefore need separate monitoring; an old retained alert is not a new daily finding.<\/p>\n<div class=\"lw-faq\">\n<h2>Questions and answers<\/h2>\n<h3>Does the reconciliation ID require a GA4 custom dimension?<\/h3>\n<p>Not to access a parameter actually exported in <code>event_params<\/code>. Registration does not guarantee transmission or export, however. Missing or repeated keys need detection during ingestion.<\/p>\n<h3>Are 48 hours and a 06:00 UTC run enough?<\/h3>\n<p>No. GA4 can update daily tables over several calendar days and occasionally reprocess historical data. CAPI delivery and the local log have separate delays. Waiting periods, freshness checks and re-evaluation of older days belong together; a fixed time does not guarantee export completion.<\/p>\n<h3>Can ads_storage substitute for Meta consent checks?<\/h3>\n<p>No. <code>Yes<\/code>, <code>No<\/code>, <code>Unset<\/code> and an absent value do not automatically establish permission for a different service. Expectations must reflect the actual documented service configuration and decision. An intentionally unsent event is not a technical fault; an unknown state is not consent.<\/p>\n<\/div>\n<div class=\"lw-quellen\">\n<h2>Sources<\/h2>\n<ul>\n<li><a href=\"https:\/\/support.google.com\/analytics\/answer\/7029846?hl=en\" target=\"_blank\" rel=\"noopener noreferrer\">GA4 BigQuery export schema<\/a><\/li>\n<li><a href=\"https:\/\/developers.facebook.com\/documentation\/ads-commerce\/conversions-api\/using-the-api\/\" target=\"_blank\" rel=\"noopener noreferrer\">Meta Conversions API: using the API<\/a><\/li>\n<li><a href=\"https:\/\/docs.cloud.google.com\/bigquery\/docs\/create-alert-scheduled-query\" target=\"_blank\" rel=\"noopener noreferrer\">BigQuery: alerts with scheduled queries<\/a><\/li>\n<\/ul>\n<\/div>\n<p><!-- lw-analytics-reconcile-guide:start --><\/p>\n<h2>Practical check: reconcile orders with the shop export<\/h2>\n<p>The <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/shop-ga4-revenue-reconciliation\/\">Shop vs. GA4 Revenue Reconciliation tool<\/a> compares two CSV files locally. The order ID is the join key. Names, email addresses and postal addresses are not needed: an order ID, amount and currency are enough. This does not automatically make an order ID anonymous.<\/p>\n<ol>\n<li>Prepare both exports for the same period and timezone, with the same treatment of tax, shipping and refunds. Aggregate line items and refunds to one agreed amount per order before comparison.<\/li>\n<li>Select each file&#8217;s separator and decimal mark, then load it. Map the order ID, amount and currency columns. Identifiers with leading zeros remain text.<\/li>\n<li>Select the decimal precision and an explicit per-order tolerance. Currency conversion is not performed.<\/li>\n<li>Review the separate groups: missing on either side, duplicate ID, different currency and amount outside tolerance.<\/li>\n<li>Export the result and investigate individual orders in the source systems.<\/li>\n<\/ol>\n<p>Duplicate IDs are flagged rather than added together: a repeated purchase event, an item-level export and a refund row require different interpretations. Negative amounts are accepted only as numbers; the business definition still has to agree between files. A missing Analytics order can reflect consent, timing, filtering or collection failures and is not by itself proof of a faulty tag. A zero difference also does not establish that all orders were exported. The tool never combines totals across currencies.<\/p>\n<p>Definitions should follow the chosen export and the <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/ga4\/ecommerce\" target=\"_blank\" rel=\"noopener noreferrer\">GA4 ecommerce documentation<\/a>. The <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/reporting\/data\/v1\/api-schema\" target=\"_blank\" rel=\"noopener noreferrer\">Data API schema<\/a> distinguishes transaction IDs and revenue metrics; a generic total-revenue column is not automatically equivalent to a shop&#8217;s order amount.<\/p>\n<p><!-- lw-analytics-reconcile-guide:end --><\/p>\n<p><!-- lw-revenue-export-guide:start --><\/p>\n<h2>Preparing the Analytics CSV from BigQuery<\/h2>\n<p>The <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/shop-ga4-revenue-reconciliation\/#aw-export-helper\">export helper in the revenue reconciliation tool<\/a> generates a purchase query for an existing daily GA4 export. Project, dataset, dates and rounding precision are entered locally. The page does not run the SQL or request BigQuery access.<\/p>\n<p>The resulting columns <code>transaction_id<\/code>, <code>amount<\/code> and <code>currency<\/code> match the comparison fields. Each recorded purchase remains a row. An additional purchase-event count helps identify repeated transaction IDs; these are not silently summed or discarded. A missing amount is not replaced with zero.<\/p>\n<ol>\n<li>Agree on the purchase-amount definition and reporting-day boundaries before creating the shop file. This query excludes refunds and does not add tax or shipping separately.<\/li>\n<li>Generate SQL, validate it in BigQuery and inspect the cost estimate. Set a maximum bytes billed before execution.<\/li>\n<li>Save the complete results as a local CSV. Load the file with comma-separated fields and decimal dot, using the same precision as the query.<\/li>\n<li>Review repeated IDs and missing fields before interpreting amount differences. The comparison accepts up to 20,000 data rows per file. Keep identifiers as text when using spreadsheet software.<\/li>\n<\/ol>\n<p>Two downloadable invented CSV files provide a reproducible first test, with one case in each of the six result groups. They are independent of the SQL and use two decimals with zero tolerance.<\/p>\n<p>References: <a href=\"https:\/\/support.google.com\/analytics\/answer\/7029846?hl=en\">GA4 export schema<\/a>, <a href=\"https:\/\/docs.cloud.google.com\/bigquery\/docs\/running-queries\">query validation and cost controls<\/a>, and <a href=\"https:\/\/docs.cloud.google.com\/bigquery\/docs\/export-file\">saving query results as CSV<\/a>.<\/p>\n<p><!-- lw-revenue-export-guide:end --><br \/>\n<!-- lw-items-practice:start --><\/p>\n<h2>Practical check: inspect the items array before sending<\/h2>\n<p>An e-commerce event can arrive successfully while its product data is wrong. The <a href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/toolbox\/ga4-ecommerce-item-array-validator\/\">GA4 E-Commerce Item Array Validator<\/a> checks the JSON array locally and points to the affected field, for example <code>items[0].price<\/code>. A quoted price such as <code>\"12.50\"<\/code> is reported as a type error instead of silently converted.<\/p>\n<p>The check distinguishes errors from review notes. Repeated product IDs can represent variants; they do not automatically prove duplicate purchases. Missing prices prevent a complete calculated value. Zero quantities remain zero, while omitted quantities default to one. The sum uses the already discounted price and excludes tax and shipping. Event-level currency and transaction identifiers still need a separate check.<\/p>\n<p>A useful workflow is to check the array, inspect the actual network request and then reconcile the recorded orders. Passing this structural check alone does not establish correct collection or reporting. Rules and limits follow <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/ga4\/ecommerce\">Google\u2019s e-commerce guide<\/a> and the <a href=\"https:\/\/developers.google.com\/analytics\/devguides\/collection\/ga4\/reference\/events#purchase\">purchase reference<\/a>.<\/p>\n<p><!-- lw-items-practice:end --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A technical step-by-step tutorial on building an automated event reconciliation and validation pipeline between GA4 and Meta Conversions API in BigQuery.<\/p>\n","protected":false},"author":1,"featured_media":14010,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[113],"tags":[91333,91261,91297,91258,91057,91516,91219],"class_list":["post-450","post","type-post","status-publish","format-standard","hentry","category-tutorials","tag-bigquery","tag-conversion-tracking","tag-data-quality","tag-google-analytics-4","tag-meta-conversions-api","tag-tracking","tag-tutorial"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Automated GA4 &amp; Meta CAPI Event Validation in BigQuery | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Reconcile GA4 and Meta CAPI events in BigQuery: SQL checks, alerts and comparisons with shop orders and analytics CSV exports.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Reconcile GA4 and Meta CAPI events in BigQuery: SQL checks, alerts and comparisons with shop orders and analytics CSV exports.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-08T08:10:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-24T00:25:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-450-automated-ga4-meta-capi-event-valida-g.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery\",\"datePublished\":\"2026-09-08T08:10:00+00:00\",\"dateModified\":\"2026-10-24T00:25:45+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/\"},\"wordCount\":1539,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-450-automated-ga4-meta-capi-event-valida-g.png\",\"keywords\":[\"BigQuery\",\"Conversion Tracking\",\"Data Quality\",\"Google Analytics 4\",\"Meta Conversions API\",\"Tracking\",\"Tutorial\"],\"articleSection\":[\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/\",\"name\":\"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-450-automated-ga4-meta-capi-event-valida-g.png\",\"datePublished\":\"2026-09-08T08:10:00+00:00\",\"dateModified\":\"2026-10-24T00:25:45+00:00\",\"description\":\"Reconcile GA4 and Meta CAPI events in BigQuery: SQL checks, alerts and comparisons with shop orders and analytics CSV exports.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-450-automated-ga4-meta-capi-event-valida-g.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/hero-450-automated-ga4-meta-capi-event-valida-g.png\",\"width\":1200,\"height\":630,\"caption\":\"Automated GA4 & Meta CAPI Event Validation in BigQuery\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/en\\\/digital-analytics\\\/tutorials\\\/automated-ga4-meta-capi-event-validation-in-bigquery\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery | Lukas Wojcik","description":"Reconcile GA4 and Meta CAPI events in BigQuery: SQL checks, alerts and comparisons with shop orders and analytics CSV exports.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/","og_locale":"en_US","og_type":"article","og_title":"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery | Lukas Wojcik","og_description":"Reconcile GA4 and Meta CAPI events in BigQuery: SQL checks, alerts and comparisons with shop orders and analytics CSV exports.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-09-08T08:10:00+00:00","article_modified_time":"2026-10-24T00:25:45+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-450-automated-ga4-meta-capi-event-valida-g.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery","datePublished":"2026-09-08T08:10:00+00:00","dateModified":"2026-10-24T00:25:45+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/"},"wordCount":1539,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-450-automated-ga4-meta-capi-event-valida-g.png","keywords":["BigQuery","Conversion Tracking","Data Quality","Google Analytics 4","Meta Conversions API","Tracking","Tutorial"],"articleSection":["Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/","url":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/","name":"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-450-automated-ga4-meta-capi-event-valida-g.png","datePublished":"2026-09-08T08:10:00+00:00","dateModified":"2026-10-24T00:25:45+00:00","description":"Reconcile GA4 and Meta CAPI events in BigQuery: SQL checks, alerts and comparisons with shop orders and analytics CSV exports.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-450-automated-ga4-meta-capi-event-valida-g.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/hero-450-automated-ga4-meta-capi-event-valida-g.png","width":1200,"height":630,"caption":"Automated GA4 & Meta CAPI Event Validation in BigQuery"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/en\/digital-analytics\/tutorials\/automated-ga4-meta-capi-event-validation-in-bigquery\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Automated GA4 &amp; Meta CAPI Event Validation in BigQuery"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/450","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=450"}],"version-history":[{"count":9,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/450\/revisions"}],"predecessor-version":[{"id":23166,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/450\/revisions\/23166"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/14010"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=450"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=450"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=450"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}