I’m seeing inconsistent data coming from Shopify Web Pixels using analytics.subscribe("checkout_completed"), and I’m trying to understand whether this is expected behavior or an implementation issue.
Setup:
-
Shopify Web Pixel
-
Subscribed to
checkout_completed -
Sending purchase events to GA4
-
Mapping:
-
transaction_id→event.data.checkout.order.id -
customer_id→event.data.checkout.customer.id
-
Issue:
Both transaction_id and customer_id are inconsistently missing in some events (but always never both).
In GA4 BQ export, I see:
-
Valid purchase events with both fields populated
-
Some events where:
-
transaction_idis null /(not set) -
customer_idis also null -
but other fields (e.g. session or Shopify user identifiers) are still present
-
-
In some cases, for the same session/user:
-
one purchase event is fully populated
-
another purchase event is missing these key fields
-
I have already checked if this is related to consent, but I see it happening when the user is giving full consent but also when no consent is given. It seems to be all over the place.
Questions:
-
Is
checkout_completedguaranteed to always contain a fully populatedcheckout.orderandcheckout.customerobject? -
Are there known cases where this event fires with partial or delayed data?
-
What is the recommended way to ensure consistent availability of
transaction_idandcustomer_id? -
Are there known differences in payload structure depending on checkout flow (e.g. Shop Pay, accelerated checkout, etc.)?
Right now it’s difficult to rely on this event for clean purchase tracking due to inconsistent identifiers.
Any insights or confirmations would be appreciated.
For completeness, I am using exactly this script for this specific event within customer events:
For triggering I am using a trigger group that waits until both checkout_completed and window loaded are available due to having consent available. But this shouldn’t be the reason since I also saw the described issue appearing when directly using the checkout_completed event.
analytics.subscribe('checkout_completed', (event) => {
const productLines = event.data?.checkout?.lineItems || [];
console.log(productLines)
const deliveryOptions = event.data?.checkout?.delivery?.selectedDeliveryOptions?.[0]
const transactionOptions = event.data?.checkout?.transactions?.[0]
const checkoutCompletedPayload = {
...buildBasePayload(event, 'checkout_completed'), //defined earlier in script
ecommerce: {
email_marketing: event.data?.checkout?.buyerAcceptsEmailMarketing,
sms_marketing: event.data?.checkout?.buyerAcceptsSmsMarketing,
checkout_token: event.data?.checkout?.token,
currency: event.data?.checkout?.currencyCode,
discount: event.data?.checkout?.discountsAmount?.amount,
value: event.data?.checkout?.subtotalPrice?.amount,
value_incl: event.data?.checkout?.totalPrice?.amount,
shipping: event.data?.checkout?.shippingLine?.price?.amount,
tax: event.data?.checkout?.totalTax?.amount,
country: event.data?.checkout?.localization?.country?.isoCode,
language: event.data?.checkout?.localization?.language?.isoCode,
items: productLines.map((line, index) => ({
item_id: line?.variant?.product?.id,
item_name: line?.variant?.product?.title,
item_name_en: line?.variant?.product?.untranslatedTitle,
affiliation: line?.variant?.product?.vendor,
item_category: line?.variant?.product?.type,
original_price: line?.variant?.price?.amount,
price: line?.finalLinePrice?.amount ?? line?.variant?.price?.amount,
quantity: line?.quantity,
index: index,
position: index + 1,
item_variant_sku: line?.variant?.sku,
item_variant_id: line?.variant?.id,
item_variant: line?.variant?.title,
item_variant_en: line?.variant?.untranslatedTitle,
})),
transaction_id: event.data?.checkout?.order?.id,
shopify_customer_id: event.data?.checkout?.order?.customer?.id,
first_order: event.data?.checkout?.order?.customer?.isFirstOrder,
customer_type: event.data?.checkout?.order?.customer?.isFirstOrder ? 'new' : 'returning',
delivery_cost: deliveryOptions?.cost?.amount,
delivery_description: deliveryOptions?.description,
delivery_handle: deliveryOptions?.handle,
delivery_title: deliveryOptions?.title,
delivery_type: deliveryOptions?.type,
payment_gateway: transactionOptions?.gateway,
payment_method_name: transactionOptions?.paymentMethod?.name,
payment_method_type: transactionOptions?.paymentMethod?.type,
}
}
pushToDataLayer(checkoutCompletedPayload, 'checkout_completed'); //for debugging
});