Published Oct 11, 2026

GA4 Cross-Domain Tracking: Keep One Session Across Landing Pages, Apps, Booking and Payment

A practical GA4 cross-domain implementation and testing guide for landing pages, web apps, YCLIENTS, payment processors, redirects, and server-side purchases.

Category: Analytics & Conversion Tracking · By Mikalai Sasau

This implementation guide explains how to keep Google Analytics 4 user and session continuity when a journey moves from a campaign landing page to a main website, a web application, a hosted booking service, a payment page, and an owned confirmation page. It separates GA4 cross-domain linking from referral filtering and server-side purchase reconciliation, then provides an end-to-end test plan for every handoff.

Practical default: use one GA4 web data stream and the same G- measurement ID on every domain you can legitimately tag in the same customer journey. Use the GA4 linker only between those taggable domains, use unwanted-referral rules for untaggable providers that send the browser back, and preserve a business order or booking key plus GA4 identifiers on your server before the visitor leaves your controlled environment.

Contents

Executive summary

GA4 cross-domain measurement solves a narrow but important problem: it lets two or more tagged web domains in the same web data stream keep the same browser-level Analytics identity when a user navigates between them. The source page adds a short-lived Google linker parameter named _gl to an eligible link or form. The destination tag validates that parameter and writes equivalent first-party measurement state on the destination domain. When the handoff succeeds, the client_id and the active session_id remain consistent instead of GA4 creating a second user and a second session.

This does not mean every external step belongs in cross-domain configuration. A payment processor, booking platform, identity provider, bank challenge page, or another vendor that does not run your intended GA4 tag cannot consume your linker as part of your stream. In that route, adding the vendor to GA4 cross-domain settings does not make the vendor visible. The normal pattern is to measure the outbound handoff, preserve your own order or booking reference, prevent the vendor from becoming a misleading referral on return, and confirm the commercial outcome from your backend or the vendor webhook.

Likewise, an unwanted-referral rule does not transport an identifier, recover a lost cookie, extend a timed-out session, or reconstruct activity on an external domain. It tells GA4 not to treat a matching referrer as the reported traffic source. It is often needed for payment returns, but it is not a substitute for the linker or server-side reconciliation.

The distinction between a subdomain and a separate registrable domain is also practical, not cosmetic. With normal GA4 cookie configuration, www.example.com and app.example.com can usually read measurement cookies scoped to the shared parent domain. example.com and example-booking.com cannot. A subdomain route should therefore be tested first, not automatically decorated with _gl; a separate-domain route normally needs the linker when both sides are tagged.

GA4 Cross-Domain Tracking for Booking and Payment

For revenue and booking integrity, browser continuity should be only one layer. Capture the GA4 client_id and session_id, consent state, and an opaque business key such as order_id, booking_id, or journey_id before redirecting away. Then use the payment or booking webhook as the outcome source of truth and send a deduplicated GA4 purchase or lead event from one controlled path. This protects measurement when the visitor never reaches the thank-you page, closes the tab, changes device, is delayed by a bank challenge, or returns after the original session has expired.

Journey Primary GA4 method Additional control What proves success
Owned site → owned separate domain Same web stream plus GA4 cross-domain linker Preserve _gl through every redirect Same client_id and session_id on both domains
www.example.com → app.example.com Normally shared parent-domain GA4 cookies Align tag, cookie, and consent configuration Same identifiers without an unnecessary second session
Site → hosted booking provider Linker only if the provider can run the same G- destination Vendor metadata, booking key, callback, API, or webhook Booking record reconciled to the original controlled handoff
Site → payment provider → site Usually no GA4 visibility on the payment domain Unwanted referral plus server-side order correlation Payment webhook and one deduplicated purchase
Promo landing → main shop Same stream and linker when domains differ Do not add internal UTM tags Original acquisition plus one user/session
Several active mirror domains Same stream and explicit linking only when every mirror is intentionally active Prefer a canonical redirect architecture where possible Redirects preserve query parameters and no duplicate page views fire

The mechanisms people confuse

GA4 cross-domain linker

The GA4 linker is an identity-transport mechanism. It decorates an eligible navigation with _gl, and the destination tag uses the encoded, validated payload to establish compatible first-party Analytics state on the destination. Google documents this for related domains that use the same Google tag destination and recommends configuring GA4 domains in the web stream’s tag settings.

The linker answers one question: can this browser remain the same GA4 client and active session after it crosses a cookie boundary? It does not answer whether a booking was completed, whether payment settled, whether a CRM lead was qualified, or whether a different device belongs to the same person.

Unwanted referrals

An unwanted-referral condition changes source classification. GA4 adds ignore_referrer=true to matching events so the matching domain is not reported as the referral source. The common use case is a return from a third-party payment processor. The browser may still have the original first-party cookie on your own domain; suppressing the payment referrer prevents the processor from replacing useful acquisition context in reporting.

It does not create or transfer a client_id. It does not preserve a session_id if the original session has timed out. It does not measure the provider’s pages. It is also not retroactive: historical sessions do not change after you edit the list.

Server-side business correlation

Server-side correlation joins marketing context to a commercial record. The website creates or receives an opaque key such as journey_id, cart_id, booking_id, or order_id; the backend stores the relevant Analytics context and passes the business key through an integration surface the provider supports. A webhook, API response, export, or return URL then brings the provider’s outcome back to the controlled backend.

This method answers a different question: which real booking, order, or lead resulted from the controlled handoff? It remains useful even when GA4 browser continuity is perfect, because a browser page is not an authoritative payment ledger. The broader identifier and vendor-integration architecture is covered in metricfixer’s guide to preserving UTM parameters, click IDs, and user identifiers across external systems.

Google Ads Conversion Linker preserves Google advertising and Floodlight click information used by conversion tags. GA4 cross-domain measurement and Google advertising linking share parts of the Google tag linker infrastructure and may contribute values to _gl, but they are not interchangeable. A route can preserve GA4 client_id and session_id while still mishandling an advertising click identifier, or preserve ad-click state while sending the two domains to different GA4 streams.

The implementation should therefore be tested by identifier family. Do not treat the visual presence of a long _gl value as proof that every Google product is configured correctly.

Mechanism What it changes What it does not do
GA4 cross-domain linker Transports supported Analytics browser and session state between compatible tagged domains Does not make an untagged vendor observable or confirm a business outcome
Unwanted referrals Prevents selected referrers from becoming reported traffic sources Does not transport IDs or reconnect a timed-out session
Google Ads Conversion Linker Supports Google Ads and Floodlight click and conversion measurement Does not replace GA4 identity or CRM/order matching
Server correlation Joins a booking, order, or lead to stored measurement context Does not recreate unseen browser events on an external platform

A reference architecture for the full journey

Reference path: ad or organic visit → promo.example → www.example.com → app.example.com → hosted booking or YCLIENTS → payment provider or bank challenge → owned thank-you page → payment or booking webhook → one reconciled GA4 outcome.

The route contains three different boundaries. The first two are under the business’s control and can normally share one web stream. The booking and payment layers may be only partly controllable. The backend outcome exists independently of whether the browser returns. The design below makes those boundaries explicit.

Interactive journey: open each handoff

Each step contains the expected identifiers, the request to inspect, the pass condition, and the fallback when the destination is a black box.

  1. 1. Campaign landing → main store on a separate domain

    Expected: both domains load the same GA4 web stream. A real click from the landing page adds a fresh _gl parameter to the destination URL. The store accepts it before its first GA4 page event is sent.

    Inspect on the source: retrieve the current identifiers after the Google tag has initialized and after the applicable analytics consent state is available.

    const measurementId = 'G-XXXXXXXXXX';
    const getGaField = (field) =>
      new Promise((resolve) => gtag('get', measurementId, field, resolve));
    
    Promise.all([
      getGaField('client_id'),
      getGaField('session_id'),
      getGaField('session_number')
    ]).then(([clientId, sessionId, sessionNumber]) => {
      console.table({
        hostname: location.hostname,
        clientId,
        sessionId,
        sessionNumber
      });
    });

    Pass: the store reports the same client_id and active session_id. The store’s first page_view belongs to the same DebugView device and the same logical session.

    Fail: _gl never appears, a redirect strips it, the destination fires into another G- destination, or the destination writes a new cookie before consuming the incoming linker.

  2. 2. Main store → app.example.com

    Expected: normal GA4 cookie settings usually scope measurement cookies to the shared parent domain, so a separate linker parameter may not be needed. The web application still has to load the same intended stream, use compatible cookie settings, and apply the same consent decision.

    Inspect on both hosts: run the identifier snapshot above and compare the values. Then inspect the first GA4 collection request from the app.

    https://www.google-analytics.com/g/collect?...&tid=G-XXXXXXXXXX&cid=1234567890.1730000000&sid=1730000100&dl=https%3A%2F%2Fapp.example.com%2Fdashboard

    Pass: the tid, cid, and sid values match the intended stream, client, and active session. A changed hostname by itself is not a failure.

    Fail: the app uses a host-only or custom-prefixed cookie, a second stream, a different consent default, or an authentication redirect that removes the original state.

  3. 3. Owned page → hosted booking page or YCLIENTS

    Expected when the provider is taggable: the provider runs the same intended G- measurement destination and accepts the incoming linker. This must be a documented or verified capability, not an assumption based on the provider showing a browser page.

    Expected when the provider is not taggable: record an outbound handoff and pass an opaque provider-supported booking or journey reference. Do not claim that the vendor’s internal steps are part of your GA4 session.

    Test the redirect chain: use a fresh real _gl value because linker values expire quickly. The additional mf_probe parameter tests whether each redirect preserves ordinary query parameters.

    curl -sS -D - -o /dev/null -L --max-redirs 10 \
      'https://booking.example/start?mf_probe=1&_gl=PASTE_FRESH_VALUE'

    Pass: every relevant Location header preserves the intended allowlisted parameters, or the provider returns the opaque business key through a webhook or API.

    Fail: the provider offers no same-stream tag access, no supported parameter, no metadata field, no callback, and no webhook. In that case, only the outbound handoff and aggregate provider outcomes are deterministically measurable.

  4. 4. Booking or cart → payment provider → owned return page

    Expected: the payment provider is commonly outside your GA4 stream. Your own first-party GA4 cookie should still be available when the browser returns to your domain. Add the actual provider hostnames observed in the return referrer to unwanted referrals, but do not mistake that setting for session preservation.

    Inspect the return request: compare cid and sid with the values captured before payment, and inspect dr to confirm which payment hostname referred the browser.

    https://www.google-analytics.com/g/collect?...&tid=G-XXXXXXXXXX&cid=1234567890.1730000000&sid=1730000100&dl=https%3A%2F%2Fwww.example.com%2Fthank-you&dr=https%3A%2F%2Fsecure.payment.example%2F

    Pass: the return event has the original client ID; the session ID remains the same only when the session is still active; the payment domain does not replace the useful acquisition source; and the backend has an independent payment confirmation.

    Fail: the team treats the thank-you page as the only purchase trigger. Users who close the page or never return are then missing, while refreshes and retries may create duplicates.

  5. 5. Backend confirms the order or booking

    Expected: a webhook or authoritative backend transition confirms the business outcome. The server uses the stored client_id, session_id, transaction data, consent and policy state, and a stable transaction_id to send one controlled event.

    Validate before production: send a representative event to the Measurement Protocol validation endpoint. Validation requests do not enter reports.

    curl -X POST \
      'https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXXXX&api_secret=SERVER_ONLY_SECRET' \
      -H 'Content-Type: application/json' \
      -d '{
        "client_id": "1234567890.1730000000",
        "validation_behavior": "ENFORCE_RECOMMENDATIONS",
        "events": [{
          "name": "purchase",
          "params": {
            "session_id": 1730000100,
            "engagement_time_msec": 1,
            "transaction_id": "ORDER-10482",
            "currency": "USD",
            "value": 129.00,
            "items": [{
              "item_id": "SKU-42",
              "item_name": "Service package",
              "price": 129.00,
              "quantity": 1
            }]
          }
        }]
      }'

    Pass: the validation response contains no relevant messages, the production endpoint receives exactly one event, and the transaction reconciles to the order system. A 2xx response alone is not proof that the event was semantically valid or reported as intended.

How _gl, client_id, and session_id behave

The three values are related but have different jobs and lifetimes. Most cross-domain debugging becomes easier once they are inspected separately.

Value Meaning Expected lifetime Correct use Common misuse
_gl A short-lived, encoded Google linker transport parameter Google documents linker parameters as expiring after about two minutes; they are normally added at click time Move supported Google measurement state through an immediate navigation Parsing it, storing it as a durable ID, copying it into reusable links, or using it as an order key
client_id A pseudonymous browser/client instance identifier used by GA4 web measurement Persists in first-party Analytics storage subject to consent, cookie settings, browser limits, and deletion Confirm that the same browser remains the same GA4 client across the route; join eligible server events Treating it as an authenticated person, CRM identity, or reliable cross-device key
session_id The timestamp in seconds associated with the start of the GA4 session Active until session timeout; the default web inactivity timeout is 30 minutes unless changed Confirm active-session continuity and associate eligible Measurement Protocol events Assuming it is globally unique without pairing it with client_id, user_pseudo_id, or user_id
transaction_id A stable business transaction key used by the GA4 purchase event Should remain stable for the same transaction Reconcile reporting and prevent duplicate collection of the same purchase Generating a new value on each retry or reusing one value for different orders
journey_id or booking key Your own opaque reference for the handoff and backend join Defined by the business’s justified retention policy Connect external booking/payment outcomes to stored attribution and Analytics context Putting sequential internal IDs, email addresses, phone numbers, or other sensitive data into public URLs

Google’s supported gtag('get', ...) API can retrieve client_id, session_id, and session_number. Google Tag Manager custom templates can use readAnalyticsStorage, which returns the client ID and current session records. These supported interfaces are safer than parsing the internal structure of _ga and _ga_* cookies, whose serialization is an implementation detail and can change.

A session ID must be interpreted with a user-level identifier. Google explicitly recommends joining session_id with user_id or user_pseudo_id when analyzing sessions outside the GA4 interface. Two unrelated users can begin sessions in the same second, so session_id alone is not a unique database key.

The visible _gl string should be treated as opaque. Its presence proves that some linker decoration occurred; it does not prove that the destination accepted the value, wrote the expected cookies, used the same stream, or sent the first event with the inherited IDs. The pass condition is identifier continuity in the destination request, not the parameter’s visual length.

GA4 Cross-Domain Tracking for Booking and Payment

One web stream or several

For standard GA4 cross-domain continuity, use the same GA4 web data stream and measurement ID on all participating web domains. Google’s own setup instructions require the same tag ID from the same web stream on every domain. A shared property with two different web streams is not the same thing: each stream has its own G- destination, and GA4 cross-domain measurement is not designed to turn two streams into one continuous session.

The Google Tag Manager container ID does not have to be the same. GTM-AAAA on the landing site and GTM-BBBB in the application can both send to G-XXXXXXXXXX. What matters is the final Analytics destination and compatible configuration. Separate containers may be operationally sensible when different engineering teams own the domains, but they increase the need for a shared measurement specification, version control, and regression tests.

Do not create separate streams merely to report by hostname. A single stream already records page location and hostname, so domains can be separated in Explorations, custom reports, BigQuery, or downstream models. One stream better represents one connected web journey; reporting boundaries should normally be implemented as dimensions and governance, not as identity-breaking collection boundaries.

There are legitimate reasons for different streams: legally or operationally separate businesses, unrelated products, materially different data governance, or environments that should never share measurement identity. In those cases, accept that ordinary cross-domain session continuity is not the primary design. A deliberate shared roll-up stream can be added alongside local streams, but dual tagging increases duplicate-event risk, cookie conflicts, consent complexity, and maintenance. Simo Ahava has documented the risk that a destination’s local GA4 configuration can overwrite shared roll-up state; cookie prefixes and exact tag sequencing then require careful design.

For multiple domains routed through one server-side Google Tag Manager endpoint, also review metricfixer’s guide to server-side GTM for multiple domains. A first-party collection endpoint can improve transport control, but it does not remove the browser’s separate-domain cookie boundary or eliminate the need to carry identity correctly.

Architecture Recommended collection Continuity expectation
One customer journey across owned web domains One GA4 web stream, same G- ID Full linker-based continuity when navigation and consent permit
One parent domain with several web subdomains One stream and compatible parent-domain cookie configuration Usually continuous without _gl; verify rather than assume
Independent brands or data controllers Separate properties or streams according to governance No automatic session continuity; use approved aggregate or business-level joins
Native mobile application plus website Web stream plus app stream/Firebase under a considered property design Web cross-domain linking does not bridge native app identity; use User-ID or approved deep-link and backend architecture

Scenario playbook

Website → another owned domain → thank-you page

When all domains are controlled and can run the same GA4 web stream, configure every participating domain in the stream’s cross-domain settings. Test both directions if users can move both ways. The source needs to decorate links or forms; the destination needs to accept incoming linker values before sending its first event; and every intermediate redirect must preserve _gl.

A common failure occurs when the first destination immediately redirects from a short campaign URL, language selector, login endpoint, or trailing-slash normalizer. The first request contains _gl, but the next Location header reconstructs the URL without the query string. The browser arrives at the final page with no linker, and the destination creates a new client. Test the full redirect chain, not only the HTML on the final page.

Do not send a second purchase merely because the user reaches an owned thank-you page after the backend has already sent the transaction. Decide which path owns the outcome or use the same stable transaction_id with a verified deduplication strategy.

Main website → app.site.com

For a web application on a subdomain, the normal GA4 cookie configuration is designed to make first-party Analytics cookies available at the highest usable domain level. That usually allows www.site.com and app.site.com to share the same GA4 client and session without URL decoration.

Still test it. Subdomain continuity breaks when one application sets a host-only cookie, changes cookie_domain, uses a different cookie_prefix, loads a different G- destination, delays consent differently, or sends an event before the common tag configuration is ready. Login and SSO redirects can also introduce external hosts that affect the route.

If the product called “app” is a native iOS or Android application rather than a web application at app.site.com, the problem is not web cross-domain tracking. A native app uses an app stream and app-instance identity. Cross-platform analysis normally depends on a consistent non-PII User-ID after authentication, approved deep-link context, and backend records—not on carrying the web _gl parameter into an app.

Website → YCLIENTS

YCLIENTS should be treated as a vendor integration, not automatically as a second owned GA4 domain. Its current public documentation describes analytics options for online booking, but it does not document a universal field where every customer can place an arbitrary GA4 measurement ID and obtain same-stream cross-domain behavior. Available integrations, scripts, widgets, callbacks, and marketplace applications can vary, so the exact account and booking mode must be verified.

There are two materially different routes:

  • Direct booking link: a top-level navigation leaves the owned site for a YCLIENTS host. Standard GA4 cross-domain continuity is possible only if that booking environment actually runs and accepts the same intended G- destination. If it does not, record the outbound handoff and rely on a supported booking reference, integration, API, export, or webhook.
  • Embedded booking widget: the parent site can measure the page and the action that opens the widget, but it cannot freely read a cross-origin iframe’s DOM, storage, or completion state. Completion needs a documented callback, cooperative postMessage contract, vendor event integration, or server-side booking record. DOM scraping is brittle and may violate the provider’s expected integration boundary.

YCLIENTS announced a move from yclients.com to yclients.ru in September 2026 and says old links redirect after the transition. The practical measurement consequence is immediate: update externally placed booking links to the direct .ru host, update widget code and API hosts where applicable, and test any remaining .com redirect chain. A redirect that works for the visitor can still remove _gl, an opaque journey token, or another allowed query parameter.

The pass condition should be a reconciled booking, not merely a widget-open event. Store an opaque journey or booking key on the server, and connect it to the confirmed booking record through the strongest supported interface. When no deterministic return key exists, report the outbound handoff and provider totals separately rather than presenting a time-window match as exact attribution.

Website → payment system → website

Most hosted payment pages are intentionally outside the merchant’s GA4 implementation. Do not add a payment hostname to GA4 cross-domain settings unless the processor explicitly supports running the same intended stream and consuming the linker. Passing _gl through an untagged payment domain does not make its pages measurable.

Add the payment hosts that actually appear as return referrers to the web stream’s unwanted-referrals configuration. Use observed hosts from DevTools, GA4 diagnostics, and real test payments: a payment brand may use different domains for checkout, 3-D Secure, local acquiring, wallets, or regional processing. Match narrowly enough to avoid suppressing legitimate referrals.

On return, your original first-party GA4 cookie may still be present. If the user returns before the configured inactivity timeout and the browser state remains available, the session_id may still match. If payment takes longer than the timeout, or the route changes browser/app context, a new session is valid. Referral suppression should not be used to pretend that an expired session remained active.

The authoritative purchase trigger should normally be the successful backend payment transition or provider webhook. A thank-you page is useful for UX and as a diagnostic signal, but it is vulnerable to tab closure, network loss, back-button revisits, refreshes, and provider retries. Metricfixer’s analysis of missing and duplicated purchase tracking in Shopify illustrates why browser, platform-native, and server event sources need explicit ownership and reconciliation.

Promotional landing → main store

Use the same stream and GA4 cross-domain linker when the landing page and store use separate registrable domains. Do not add internal UTM parameters to the store link. UTMs describe acquisition into the business; adding a new internal utm_source or utm_campaign at the handoff can overwrite or reclassify the original campaign context.

If the promotional page is a short-lived platform you cannot fully configure, capture the original campaign and click context on the first controlled endpoint and create an opaque server-side journey key. Then forward only the values the store or downstream system actually needs. The linker remains the right mechanism for GA4 identity when both pages can support it; your business key remains the right mechanism for order reconciliation.

Several mirrors of one website

First decide whether these are true mirrors or active, independent entry domains. A true mirror should usually issue a single canonical redirect to the preferred hostname. The redirect must preserve the intended query string, including campaign parameters, supported click IDs, and a fresh _gl when one is present. Ensure the mirror does not fire a page view before redirecting, or the route may create duplicate landing events and confusing hostname paths.

If several domains remain intentionally active and users can navigate among them, place them in the same web stream and configure explicit cross-domain linking. Use the hostname dimension to segment reporting. Also review SEO canonicalization, consent consistency, authentication, cookie policy, and content ownership: GA4 continuity does not solve duplicate-content or security architecture.

A domain migration deserves a temporary test matrix for old-to-new redirects, cached links, campaign URLs, language routes, and deep pages. Preserve query values by allowlist and verify the final GA4 request rather than accepting “the page opens” as the success criterion.

Google’s current documentation says GA4 can pass identifiers through links or forms, while the developer guide exposes a manual decorate_forms option. Some practitioner guides still describe cross-domain behavior as link-only because older implementations, Google Tag Manager interfaces, and custom form patterns have produced inconsistent results. The safe editorial conclusion is not to pick a slogan: distinguish normal HTML navigation from application-specific behavior and test the actual production route.

The automatic linker listens at the document level for click events that bubble from eligible links. A normal <a href="..."> navigation to a configured destination is the best-supported pattern. Decoration can fail when a component calls stopPropagation(), replaces the destination in JavaScript, invokes router navigation without a real anchor, or opens an intermediate URL that is not matched by the linker domain condition.

Forms

A standard form that navigates to another domain can be decorated when the implementation supports form decoration. In a manual Google tag configuration, set decorate_forms: true. Google notes that GA4 users should normally configure domains in the Analytics Admin interface; use manual settings only when the implementation requires them and you understand their precedence.

gtag('set', 'linker', {
  domains: [
    'promo.example',
    'www.example.com',
    'booking.example'
  ],
  accept_incoming: true,
  decorate_forms: true
});

gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');

Apply the linker setting before the relevant config command. A manual linker setting can override or diverge from the domain list configured in GA4 Admin, so include the complete intended domain set and document the source of truth.

A JavaScript form is different. A form that sends fetch() or XHR, waits for an API response, and then calls location.assign() is programmatic navigation. A button that opens a modal, a React/Vue router transition, or a vendor SDK that creates a checkout session may also bypass normal decoration. Those routes require explicit integration or a server-generated destination URL, followed by end-to-end testing.

Keep one GA4 user and session across landing pages, subdomains, booking tools, payment returns, redirects, forms, and server-side purchase events.

Redirects

An HTTP 301, 302, 303, 307, or 308 is not automatically a problem. The problem is a redirect implementation that reconstructs the destination and drops the query string, truncates long values, changes encoding, moves parameters after a hash, or passes through an allowlist that omits _gl.

Use a browser test to obtain a fresh linker value, then inspect every redirect response. The following command prints headers from the chain and follows up to ten redirects:

curl -sS -D - -o /dev/null -L --max-redirs 10 \
  'https://redirect.example/go?mf_probe=1&_gl=PASTE_FRESH_VALUE'

The mf_probe parameter is a generic preservation test. The fresh _gl tests whether the real linker survives. Do not keep retrying an old copied _gl: Google documents a roughly two-minute lifetime, and an expired value can be preserved perfectly yet rejected by the destination.

Preserve only a reviewed allowlist. Blindly forwarding every query parameter can propagate personal data, debugging secrets, open-redirect payloads, or irrelevant campaign values. Log an internal journey key rather than the complete public URL where possible.

Query string or fragment

The default linker position is the query string. Fragment placement after # exists for applications that deliberately require it, but fragments are not sent to the server in an HTTP request and can be treated differently by forms, redirects, and routers. Use fragment mode only when every participating application is designed and tested for it. For ordinary server-rendered pages, booking links, and hosted checkout routes, the query string is easier to preserve and diagnose.

Server-side purchase after the user returns

Recommended outcome workflow: create order or booking → capture consent-aware GA4 context on the owned page → store context against an opaque order key → redirect to booking/payment → receive authoritative webhook → validate amount, currency, items, and status from the backend → send one deduplicated GA4 outcome → reconcile GA4 with the order system.

Capture the Analytics context before the browser leaves. Waiting until the thank-you page is too late for users who never return. The browser may provide the current client_id and session_id; the backend must supply and validate the order value, currency, items, status, and transaction key. Never trust browser-supplied revenue fields as the commercial source of truth.

const measurementId = 'G-XXXXXXXXXX';
const getGaField = (field) =>
  new Promise((resolve) => gtag('get', measurementId, field, resolve));

async function saveAnalyticsContext(orderId) {
  const [clientId, sessionId, sessionNumber] = await Promise.all([
    getGaField('client_id'),
    getGaField('session_id'),
    getGaField('session_number')
  ]);

  const response = await fetch('/analytics/order-context', {
    method: 'POST',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      order_id: orderId,
      client_id: clientId,
      session_id: Number(sessionId),
      session_number: Number(sessionNumber)
    })
  });

  if (!response.ok) {
    throw new Error('Could not save analytics context');
  }
}

Run this only under the applicable consent and privacy design. The endpoint should authenticate or otherwise bind the request to the server-created order, reject arbitrary order IDs, validate data types, rate-limit abuse, avoid logging secrets, and apply a defined retention policy. The browser code does not send an API secret; Measurement Protocol secrets stay on the server.

Stored field Source Purpose
order_id / booking_id Backend Authoritative business join and idempotency key
client_id Google tag after permitted initialization Associate the server event with the same GA4 web client
session_id Google tag Eligible same-session attribution when timing requirements are met
Session start and capture time GA context plus backend clock Determine whether a later Measurement Protocol event can legitimately be attributed to the session
Consent and policy state Consent platform and backend Prevent later systems from assuming that every captured identifier can be sent for every purpose
Value, currency, items, tax, status Order system or payment/booking webhook Authoritative ecommerce payload
Provider webhook ID Provider Deduplicate webhook retries and preserve an audit trail

Google’s current Measurement Protocol guidance places strict conditions on session attribution: include the original session_id, send the request no later than 24 hours after the session start, and use an event timestamp_micros that falls between the session start and session end. Do not invent an earlier event time to force an association. If payment completes outside that boundary, report the outcome under the appropriate offline/server use case rather than claiming it belonged to the expired web session.

For accurate Realtime and engagement-related behavior, include engagement_time_msec. Validate development requests against /debug/mp/collect with ENFORCE_RECOMMENDATIONS, then send production requests to /mp/collect without strict validation behavior unless your design intentionally accepts rejection. The validation endpoint does not ingest events and does not verify that the API secret itself is correct.

Use one purchase owner wherever possible. If the browser, a platform-native integration, GTM, and the backend can all send the same order, document the precedence and use a stable transaction_id. Google says that repeated ecommerce events with the same transaction ID are ignored after the first collected event, but relying on this alone can hide payload differences, refunds, and race conditions. The backend should also enforce its own idempotency.

For wider CRM and advertising workflows, see metricfixer’s guide to offline conversion tracking and CRM integration.

Testing every transition

A valid test proves the chain at several layers. DebugView is useful, but it can show two events without proving that they share the same client and session, and it does not replace order-system reconciliation. Use a clean browser profile, make a real user navigation, and record the evidence at each boundary.

Transition test matrix

Transition Request or evidence Pass condition Frequent false positive
Landing → owned domain Actual click URL plus destination g/collect request Fresh _gl; same cid and sid Seeing _gl but not checking the destination identifiers
Main site → subdomain app gtag('get') on both hosts plus cookie scope Same client and active session; same G- destination Assuming all subdomains share cookies despite custom settings
Owned page → booking provider Redirect headers, provider-supported metadata, booking callback/webhook Same-stream continuity if supported; otherwise deterministic business-key return Counting widget open as a completed booking
Payment provider → return page Return g/collect request, dr, webhook, order state Original client where available; correct referral handling; payment confirmed server-side Interpreting unwanted referral as proof of one session
Backend → GA4 Validation response, idempotency record, Realtime/DebugView, BigQuery/export One valid event with correct transaction, value, currency, items, client, and eligible session Treating HTTP 204 or another 2xx as reporting proof

Browser and network test

  1. Start with a clean browser profile. Record browser, consent choice, timestamp, source URL, and campaign parameters.
  2. Open DevTools before the first navigation. Preserve the Network log and filter for collect, g/collect, google-analytics.com, and any first-party server-side tagging endpoint.
  3. On the source, retrieve client_id, session_id, and session_number with the supported Google tag API.
  4. Use the real link, form, booking button, or checkout action. Do not paste the destination URL into a new tab because that bypasses click-time decoration.
  5. Record the final URL and every redirect. Confirm that _gl is present where expected and survives until the destination tag consumes it.
  6. On the destination, retrieve the identifiers again and inspect the earliest GA4 request. Compare tid, cid, sid, dl, and dr.
  7. Use DebugView or Tag Assistant to confirm event order and parameters, but retain the raw request evidence.
  8. Complete a real sandbox or low-value test booking/payment. Confirm the provider outcome, webhook, backend order state, and exactly one analytics transaction.
  9. After processing, verify hostname paths, acquisition, session continuity, and transaction data in GA4 reports or BigQuery. Standard reports can take longer than Realtime or DebugView.

Network request anatomy

A GA4 web collection request commonly exposes the fields needed for a transition test. The exact request can include many more parameters and may use a first-party server-side endpoint, but the diagnostic logic stays the same:

https://www.google-analytics.com/g/collect
  ?v=2
  &tid=G-XXXXXXXXXX
  &cid=1234567890.1730000000
  &sid=1730000100
  &dl=https%3A%2F%2Fwww.example.com%2Fthank-you
  &dr=https%3A%2F%2Fsecure.payment.example%2F
  • tid identifies the Google Analytics destination.
  • cid carries the web client ID.
  • sid carries the session ID.
  • dl is the document location sent with the event.
  • dr is the document referrer sent with the event.

A first-party tagging endpoint may hide or transform the public Google endpoint while retaining equivalent event data. Inspect the server container preview and outbound GA4 request as well as the browser request.

BigQuery continuity query

When GA4 BigQuery export is available, use user_pseudo_id together with ga_session_id and hostname to verify the event sequence. Replace the project, dataset, date suffix, and client ID with test values.

WITH journey_events AS (
  SELECT
    TIMESTAMP_MICROS(event_timestamp) AS event_time,
    event_name,
    user_pseudo_id,
    (
      SELECT value.int_value
      FROM UNNEST(event_params)
      WHERE key = 'ga_session_id'
    ) AS ga_session_id,
    (
      SELECT value.string_value
      FROM UNNEST(event_params)
      WHERE key = 'page_location'
    ) AS page_location,
    ecommerce.transaction_id
  FROM `PROJECT_ID.analytics_PROPERTY_ID.events_YYYYMMDD`
  WHERE user_pseudo_id = '1234567890.1730000000'
)
SELECT
  event_time,
  event_name,
  user_pseudo_id,
  ga_session_id,
  REGEXP_EXTRACT(page_location, r'^https?://([^/]+)') AS hostname,
  page_location,
  transaction_id
FROM journey_events
ORDER BY event_time;

The expected result is one user_pseudo_id and one active ga_session_id across the tagged web handoffs, unless the documented timeout or another legitimate boundary created a new session. The booking/payment provider will not appear as page activity when it is not part of the stream. The transaction should appear once and match the order system.

Test the unhappy paths

  • Consent granted on one domain but denied or delayed on another.
  • Payment abandoned, declined, retried, or completed after more than 30 minutes of inactivity.
  • User closes the tab before the thank-you page.
  • Bank or wallet opens another application or browser context.
  • Redirect passes through a shortener, login host, locale selector, or old vendor domain.
  • JavaScript button navigation instead of a normal link.
  • Same transaction webhook delivered several times.
  • Thank-you page refreshed or opened from browser history.
  • Ad blocker or content security policy blocks the tag on one host.

Failure modes and fixes

Observed symptom Likely cause Correct fix
No _gl after clicking Destination does not match the configured condition; navigation is programmatic; click propagation is stopped; source tag is not ready Use a real anchor/form where possible, correct domain matching, remove event interference, or implement an explicit supported handoff
_gl is present but destination gets a new client_id Expired linker, destination not accepting incoming values, destination fired too early, different stream, cookie or consent mismatch Test immediately, align the same G- destination and consent, and ensure linker acceptance precedes the first event
Same client ID but a new session ID Session timeout, session state not transferred, or destination started a session before the inherited state was applied Check timing and event order; do not force one session when the configured timeout legitimately elapsed
Own domain appears as a referral Missing tags, separate streams, custom cookie scope, or broken linker Repair tagging and identity continuity first; use unwanted referrals only as a final attribution safeguard, not as the primary repair
Payment provider appears as source Return hostname not in unwanted referrals, alternate provider host, or historical data Add observed provider hosts with narrow matching, retest new sessions, and retain server order correlation
YCLIENTS or another booking platform disappears from the journey Provider does not run the same stream; widget is cross-origin; old-domain redirect strips data Measure outbound handoff, use supported vendor integration or server key, update direct URLs, and test redirects
Purchase appears twice Browser thank-you tag plus native vendor integration plus server/webhook event, or repeated webhook processing Assign one source of truth, use stable transaction_id, and enforce backend idempotency
Purchase is missing when payment succeeded Only thank-you page triggers purchase; browser never returned; consent/tag blocked; webhook path absent Use authoritative backend confirmation and a policy-compliant server event; reconcile against orders
DebugView looks correct but acquisition is wrong Two clients/sessions displayed close together, internal UTMs, late referral configuration, or report processing differences Compare raw cid/sid, remove internal UTMs, inspect BigQuery and processed reports, and test a new clean session
Different domains show correct events but no continuous user path They send to different web streams or properties Use the same intended web stream for one journey, or accept a business-level/aggregate join when governance requires separation

Deployment checklist

  • [ ] Inventory every hostname, subdomain, redirector, widget, login host, booking domain, payment host, return URL, and webhook in the real route.
  • [ ] Classify each hop as owned and taggable, vendor-controlled but integrable, or a black box.
  • [ ] Use the same intended GA4 web stream and G- measurement ID on every taggable web domain that belongs to one journey.
  • [ ] Verify parent-domain cookie behavior for subdomains instead of adding unnecessary linker decoration by habit.
  • [ ] Configure GA4 cross-domain conditions for separate taggable domains and test every direction users can navigate.
  • [ ] Confirm that real links and forms receive a fresh _gl; explicitly test custom buttons, routers, popups, and SDK-generated checkout URLs.
  • [ ] Preserve _gl and other reviewed parameters through every immediate redirect, including old domains, locale routes, login endpoints, and URL shorteners.
  • [ ] Add only the payment and vendor hostnames that actually need referral suppression to the unwanted-referrals list.
  • [ ] Remove internal UTMs between owned pages and domains.
  • [ ] Align consent defaults, updates, tag firing, and storage rules across all controlled domains.
  • [ ] Capture client_id, session_id, session timing, consent state, and an opaque order or booking key before the external handoff.
  • [ ] Keep Measurement Protocol API secrets on the server and validate events with /debug/mp/collect before production.
  • [ ] Assign one owner for each purchase, lead, or booking event and enforce stable transaction IDs plus backend idempotency.
  • [ ] Test success, abandonment, decline, retry, timeout, tab closure, app switch, refresh, and duplicate-webhook paths.
  • [ ] Compare GA4 transactions, revenue, cancellations, and refunds with the booking/payment/order system on a recurring schedule.
  • [ ] Re-run the transition test after vendor domain migrations, checkout redesigns, consent-platform changes, GTM releases, CDN changes, or redirect updates.

The final acceptance criterion is not “events appear.” It is: the same intended GA4 client and active session survive every taggable handoff; untaggable steps do not overwrite attribution; and each commercial outcome is reconciled once to an authoritative order or booking record.

A practical implementation and test plan for _gl, client and session IDs, booking platforms, payment gateways, redirects, and Measurement Protocol purchases.

Methodology and sources

This article was prepared from Google’s current GA4, Google tag, Google Tag Manager, Measurement Protocol, ecommerce, session, cookie, debugging, and BigQuery documentation available on 10 October 2026. Official documentation was treated as the primary source for platform behavior. Practitioner articles and implementation case studies were used to identify recurring production failures—redirect stripping, programmatic navigation, payment referrals, duplicate purchases, booking-engine blind spots, and dual-stream conflicts—not to override current Google documentation.

The YCLIENTS section was checked against the provider’s current online-booking analytics documentation and its September 2026 domain-migration notice. Public documentation does not establish that every YCLIENTS account or booking mode can run an arbitrary GA4 stream, so the article deliberately requires verification of the exact integration rather than presenting a universal recipe.

This article is for technical and operational information only. It is not legal, privacy, accounting, or platform-compliance advice. Consent requirements, browser storage behavior, GA4 processing, Measurement Protocol limits, vendor capabilities, redirect hosts, and user interfaces may change after publication. metricfixer is not affiliated with Google, Google Analytics, Google Tag Manager, YCLIENTS, payment providers, or the independent publishers cited. Test the exact production route under your own consent, security, data-governance, and contractual requirements before relying on the results.