Data layer
The exact payload Zappush reads from window.dataLayer, with a copy-paste example for every ecommerce and lead event.
Once the tracker loads it wraps window.dataLayer.push and reads every push. One
push reaches both GA4 and Zappush. There is no extra call and no GTM tag to add.
Zappush follows the GA4 recommended events
schema. If you already send GA4 ecommerce, you are already sending Zappush
ecommerce. If you do not use a data layer, call
zappush.track() instead, which takes the same fields
flattened and the Zappush event name rather than the GA4 one.
Push shape
window.dataLayer.push({
event: "purchase", // required, must be a string
user_data: { ... }, // who the shopper is
ecommerce: { ... }, // what it was worth, and which products
});| Key | What it carries |
|---|---|
event | The event name. A push without a string event is ignored. |
user_data | Contact details, used to match the conversion to a person. |
ecommerce | value, currency, transaction_id, coupon, items. |
| Anything else | Passed through as event metadata. |
Zappush flattens all three into one object. If the same key appears twice, the
later one wins in this order: user_data, then top-level keys, then ecommerce.
Pushes made before the tracker finishes loading are replayed, so you never need
a readiness check. On a visitor's first page load that replay happens once their
consent state resolves, which takes a moment longer than the script loading.
page_view is ignored, because the tracker fires its own page view. Anything
starting with gtm. is ignored.
Contact fields
This is the part that decides whether a conversion matches a real person in Meta. Email and phone do most of the work. Send them on every event that has them.
Inside user_data, exactly these keys are read. This is the GA4 Enhanced
Conversions shape.
| Key | Example | Notes |
|---|---|---|
email | alex@example.com | |
phone_number | +61400000000 | E.164, including country code |
address.first_name | Alex | |
address.last_name | Smith | |
address.city | Sydney | |
address.region | NSW | State or province |
address.postal_code | 2000 | |
address.country | AU | ISO 3166-1 alpha-2 |
address is a single object, never an array. Keys inside user_data that are
not on this list are dropped. To send anything else, put it at the top level of
the push, where these names and aliases are read:
| Field | Also accepted as |
|---|---|
email | |
phone | phone_number |
first_name | firstName |
last_name | lastName |
city | |
state | region |
zip | postal_code, zipcode |
country | country_code |
date_of_birth | dob |
gender | |
external_id | external_customer_id |
Send whatever you have, all fields are optional. Zappush persists them for the session and attributes later events in that session to the same person, so you only need to send them once.
Anything pushed to window.dataLayer is readable by every other script on the
page. To keep contact details out of the shared data layer, call
zappush.track() directly instead.
Items
| Field | Required | What it does |
|---|---|---|
item_id | Yes | Identifies the product. Sent to Meta as content_ids. |
item_name | No | Product name in your dashboard. Used as the identity if item_id is missing. |
quantity | No | Summed into Meta's num_items. Defaults to 1. |
price | No | Unit price in your dashboard and on order lines. |
item_variant | No | Variant name in your dashboard. |
item_category | No | Product type in your dashboard. |
Other GA4 item fields (item_brand, coupon, discount, index,
affiliation, item_category2 and up) are accepted and stored on the event, but
Zappush does not read them and does not forward them to Meta.
Put value and currency on ecommerce, not on items. The conversion value
Meta receives is ecommerce.value exactly as you send it. It is never summed
from item prices, so an event without a value reports a conversion worth
nothing.
Ecommerce events
Fire these in order as the shopper moves through your funnel.
Product viewed
window.dataLayer.push({
event: "view_item",
ecommerce: {
currency: "USD",
value: 79.0,
items: [
{
item_id: "SKU-001",
item_name: "Running Shoes",
item_variant: "Blue / 10",
item_category: "Footwear",
price: 79.0,
quantity: 1,
},
],
},
});Added to cart
window.dataLayer.push({
event: "add_to_cart",
ecommerce: {
currency: "USD",
value: 79.0,
items: [
{ item_id: "SKU-001", item_name: "Running Shoes", price: 79.0, quantity: 1 },
],
},
});Checkout started
window.dataLayer.push({
event: "begin_checkout",
ecommerce: {
currency: "USD",
value: 158.0,
coupon: "SUMMER10",
items: [
{ item_id: "SKU-001", item_name: "Running Shoes", price: 79.0, quantity: 2 },
],
},
});Shipping info submitted
window.dataLayer.push({
event: "add_shipping_info",
user_data: { email: "alex@example.com", phone_number: "+61400000000" },
ecommerce: {
currency: "USD",
value: 158.0,
shipping_tier: "Ground",
items: [
{ item_id: "SKU-001", item_name: "Running Shoes", price: 79.0, quantity: 2 },
],
},
});Payment info submitted
window.dataLayer.push({
event: "add_payment_info",
user_data: { email: "alex@example.com", phone_number: "+61400000000" },
ecommerce: {
currency: "USD",
value: 158.0,
payment_type: "Credit Card",
items: [
{ item_id: "SKU-001", item_name: "Running Shoes", price: 79.0, quantity: 2 },
],
},
});Purchase
Fire on the order confirmation page, after payment succeeds.
window.dataLayer.push({
event: "purchase",
user_data: {
email: "alex@example.com",
phone_number: "+61400000000",
address: {
first_name: "Alex",
last_name: "Smith",
city: "Sydney",
region: "NSW",
postal_code: "2000",
country: "AU",
},
},
ecommerce: {
transaction_id: "T-1234",
currency: "USD",
value: 158.0,
coupon: "SUMMER10",
items: [
{
item_id: "SKU-001",
item_name: "Running Shoes",
item_variant: "Blue / 10",
item_category: "Footwear",
price: 79.0,
quantity: 2,
},
],
},
});Two fields carry more weight here than anywhere else:
valuemust be a number greater than zero. Without it the purchase does not become an order in your dashboard and reports no revenue.transaction_ididentifies the order. It is what stops a refreshed confirmation page from counting twice, and it is sent to Meta asorder_id.
Always send contact details on purchase. Without them Zappush cannot match the order to a shopper, and the conversion will not attribute in Meta.
Refund
window.dataLayer.push({
event: "refund",
ecommerce: {
transaction_id: "T-1234",
currency: "USD",
value: 79.0,
items: [{ item_id: "SKU-001", quantity: 1 }],
},
});Lead events
generate_lead is a GA4 event, so push it to the data layer. schedule and
verify_lead are Zappush events with no GA4 name, so GTM will never emit them.
Fire those two with zappush.track(), which takes contact
fields flat rather than under user_data.
Lead captured
Fire when you collect contact details: a form, a quiz result, a lead magnet, an email opt-in.
window.dataLayer.push({
event: "generate_lead",
lead_source: "Quiz",
user_data: {
email: "alex@example.com",
phone_number: "+61400000000",
address: { first_name: "Alex", last_name: "Smith" },
},
});Add value and currency if the lead has a known worth. Both or neither.
Appointment booked
Fire schedule when someone books a call or an appointment. It is a stronger
signal than a form submit, and it maps to Meta's standard Schedule event.
window.zappush.track("schedule", {
email: "alex@example.com",
phone: "+61400000000",
first_name: "Alex",
last_name: "Smith",
});Lead verified
Fire verify_lead when a lead you already captured is confirmed or qualified,
for example an email or phone confirmation. The initial capture is
generate_lead.
window.zappush.track("verify_lead", {
email: "alex@example.com",
phone: "+61400000000",
});Send contact fields only on lead events. No value, currency, or product data unless the lead has a real monetary worth.
Event name reference
Use either the GA4 name or the Zappush name. Both resolve to the same event.
| GA4 event | Zappush event | Meta CAPI event |
|---|---|---|
generate_lead | lead_generated | SubmitApplication |
view_item | product_viewed | ViewContent |
add_to_cart | product_added_to_cart | AddToCart |
remove_from_cart | product_removed_from_cart | custom |
view_cart | cart_viewed | custom |
begin_checkout | checkout_started | InitiateCheckout |
add_shipping_info | checkout_shipping_info_submitted | AddShippingInfo |
add_payment_info | payment_info_submitted | AddPaymentInfo |
purchase | checkout_completed | Purchase |
add_to_wishlist | product_added_to_wishlist | AddToWishlist |
search | search_performed | Search |
login | user_logged_in | custom |
sign_up | user_signed_up | CompleteRegistration |
view_item_list | item_list_viewed | custom |
view_promotion | promotion_viewed | custom |
select_promotion | promotion_selected | custom |
select_item | item_selected | custom |
refund | order_refunded | custom |
select_content | content_selected | custom |
share | content_shared | custom |
view_search_results | search_results_viewed | custom |
Two Zappush events have no GA4 name: schedule maps to Meta's Schedule, and
verify_lead maps to Meta's Lead.
Any name not on this list passes through as a custom event under the name you used, and reaches Meta as a custom event. Custom names appear in your dashboard once received, and you can give them a funnel stage on the analytics configure page.
What reaches Meta
On the seven product events (view_item, add_to_cart, add_to_wishlist,
begin_checkout, add_shipping_info, add_payment_info, purchase), Zappush
sends a fixed set and drops the rest: value, currency, content_ids from
your item_ids, num_items from your quantities, content_type, order_id
from transaction_id, and coupon.
On every other event, all metadata is forwarded except contact fields, which Zappush forwards separately as Meta's user data.
To confirm a push arrived, open Analytics → Events in the dashboard. Your event shows up within a couple of minutes.