Commerce setup
Beaconry binds your store's real action hooks and fires the shop funnel server-side: product views, cart changes, checkout, purchase and refund, each carrying the full GA4 items[] array and the Meta content_* catalog block so Dynamic Product Ads match without extra config. Three platforms ship today, WooCommerce, Easy Digital Downloads and SureCart. Activate the plugin, flip the toggle on the Commerce tab, done. There is no JavaScript snippet to paste into a template.
The three platforms at a glance
Each platform gets a dedicated adapter that only registers when its host plugin is loaded and you have turned its toggle on. An install with just WooCommerce never carries EDD or SureCart code paths. How much of the funnel a platform exposes depends entirely on which hooks that platform fires server-side, and the three differ a lot here.
| Platform | Server-side funnel coverage | How the funnel is sourced |
|---|---|---|
| WooCommerce | Full 10-event funnel | Native WooCommerce action hooks, classic and block checkout both covered. |
| Easy Digital Downloads | 8 events (no search, no add_payment_info) | Native EDD action hooks, render-path-agnostic via template_redirect and EDD conditional tags. |
| SureCart | purchase and refund server-side, the rest of the funnel via the browser bridge | SureCart's cart UI is a JavaScript web component that emits no PHP action, so the cart funnel is captured in the browser and routed back through the same server endpoint. |
The shape of every event is identical across all three: a GA4 items[] array (item_id, item_name, item_brand, item_category, item_variant, price, quantity) and a Meta CAPI content block (content_ids[], content_type, content_name, content_category, num_items). That single canonical shape is what lets one purchase fan out to ten channels and still match each platform's product catalog. The currency comes straight from the store (WooCommerce store currency, EDD store currency, the currency on each SureCart order), and Multi-currency normalises it automatically when you turn that on.
Turning commerce tracking on
The Commerce tab only appears in the Beaconry menu when a supported shop plugin is active, so you never stare at an empty tab. Open wp-admin, Beaconry, Commerce. Each detected platform has its own card with a single master toggle and an expandable "Which events get tracked?" list. Flip the toggle, save, and that platform starts firing immediately. Saving on this tab only writes the three commerce toggles, your Forms-tab settings are never touched.
Service pills at the top of the tab show each active platform as on or off at a glance and scroll you to its card. That is the whole setup. The customer-matching fields (email, phone, name, city, ZIP) are pulled from the order automatically and SHA-256-hashed per channel before anything leaves your server, so you get good match quality with no extra work.
WooCommerce: the full 10-event funnel
WooCommerce is the deepest integration because it fires the richest set of server-side hooks. All ten standard commerce events are covered:
view_item: a visitor opens a product page. The cornerstone event for Meta Dynamic Product Ads catalog matching.view_item_list: a visitor opens the shop, a category or a tag archive. Capped at 24 products so a large archive view stays a bounded payload.view_cart: a visitor opens the cart page.add_to_cart: a visitor adds a product. Covers all three add paths: classic page-reload, AJAX inline buttons on archives, and the block / Store-API add-to-cart.remove_from_cart: a visitor removes a line item.search: a visitor searches the shop, including the standalone product-search result URL that is not a shop or category page.begin_checkout: a visitor enters checkout. Fires on both the classic shortcode checkout and the Checkout block.add_payment_info: the order is placed and a payment method is committed. Fires for both classic and Store-API (block) order processing.purchase: the order is received, with all line items, shipping, tax and coupon codes.refund: the order is refunded. Sent as a negative-value event so reports net correctly (see "Refunds" below).
Classic and block checkout
This is the part most server-side setups get wrong, so it is worth being explicit. The block-based Checkout has no server-side "checkout form rendered" hook, so a naive integration silently never fires begin_checkout on block checkouts. Beaconry handles checkout render-path-agnostically: it keys on is_checkout() at template_redirect (a page-ID match, not a URL or render path) so the classic shortcode, the Gutenberg Checkout block and page-builder checkouts all fire one begin_checkout. The classic woocommerce_before_checkout_form and the official block woocommerce_blocks_checkout_enqueue_data hooks stay wired as belt-and-suspenders, and a per-request guard collapses whichever fires first to a single dispatch. The same dual coverage applies to add_payment_info, which listens on both woocommerce_checkout_order_processed (classic) and woocommerce_store_api_checkout_order_processed (block).
One consequence of block and AJAX contexts: those hooks fire inside a REST or admin-ajax request, where the request URL is the endpoint, not the page the customer was on. Beaconry detects async context via WordPress API flags (REST_REQUEST, wp_doing_ajax()) and resolves the customer-facing page URL from the referer or the configured checkout-page permalink. It never parses or slug-matches URLs, so a renamed cart or checkout page cannot break tracking.
The per-order event_id
Purchase events use a deterministic event_id derived from the order, bcnr_purchase_<order_id>. A thank-you-page refresh re-fires the WooCommerce woocommerce_thankyou hook, but the stable id plus an order-meta marker mean the purchase is only ever counted once. That same id is also what makes Hybrid Mode deduplication work: if you also run a browser pixel, Beaconry hands the identical event_id to the browser-side Purchase event so the vendor deduplicates the browser and server copies on its side. View events that have no natural unique key (view_item_list, view_cart, begin_checkout) use a windowed deterministic id so a prefetch or redirect double-request collapses to one conversion, while a genuine repeat view minutes later still counts.
Catalog match: items[] and content_*
Every WooCommerce event carries both representations of the products involved. item_id and content_ids[] prefer the product SKU and fall back to the WooCommerce product ID, so they line up with your Google Merchant Center and Meta Catalog feeds. item_brand is read from the pa_brand attribute or a product_brand taxonomy, item_category from the first assigned product category, and item_variant from the variation attributes for variable products. The Meta block sets content_type to product_group when more than one product is present and product otherwise. None of this needs configuration, it is derived from your existing product data.
Easy Digital Downloads: 8 events
EDD is the standard for digital goods (plugins, themes, eBooks, music) and many Beaconry customers use it instead of WooCommerce because it is leaner for pure digital sales. EDD fires server-side hooks for most of the funnel, so Beaconry tracks eight events:
view_item: a visitor opens a download (product) page.view_item_list: a visitor opens the shop archive, a download category or tag. Caught on the native CPT archive, the[downloads]shortcode, and theedd/downloadsGutenberg block.view_cart: a visitor opens the checkout with items in the cart.add_to_cart: a visitor adds a download to the cart.remove_from_cart: a visitor removes a download.begin_checkout: a visitor enters the EDD checkout.purchase: a payment completes, with all downloads, tax and totals.refund: a payment is refunded, sent as a negative-value event.
Two events from the WooCommerce list are intentionally absent because EDD does not expose them server-side. There is no search event (EDD has no shop-search hook Beaconry can bind to) and no add_payment_info event (EDD's payment-method step is theme-rendered and not surfaced as an action). The eight events above are everything EDD ships out of the box.
As with WooCommerce, the view and checkout events are render-path-agnostic. They key on is_singular('download') and edd_is_checkout() at template_redirect rather than on the old the_content-based template hooks, so they fire correctly even when a page builder (Elementor Theme Builder, Divi, Bricks) renders the single-download or checkout template and bypasses the_content. Purchase events dedup on a stable per-payment id, bcnr_edd_purchase_<payment_id>, so a thank-you-page refresh never double-counts.
SureCart: server purchase plus the browser bridge
SureCart is the newer, SaaS-style commerce player, strong on subscriptions and increasingly popular with the performance-marketing-minded shops that are Beaconry's core audience. Its architecture needs a different approach. SureCart's cart and checkout UI is a JavaScript web component (sc-line-item-list and friends) that emits no PHP action for cart events, so there is nothing for a server-side hook to bind to for add_to_cart, view_cart, begin_checkout and the rest.
Beaconry splits the coverage to match this reality:
- Server-side, authoritative: the PHP adapter fires
purchaseon thesurecart/checkout_confirmedaction andrefundon SureCart's refund model event. These are the conversions that must never be missed, and binding them in PHP means they are counted even for orders that never touch the browser cart UI, like email or manual-charge orders. The purchase deduplicates on a stable per-order id,bcnr_sc_purchase_<order_id>. - Browser bridge, for the cart funnel: the upper-funnel JavaScript events SureCart dispatches in the browser (product view, list view, add-to-cart, remove, cart view, begin checkout, payment and shipping info, search) are captured by the SureCart bridge inside Beaconry's browser engine and routed back through the exact same same-origin REST endpoint and the same server-side forwarder. So those events are still tracked, and still server-side from the vendor's point of view, they just enter the pipeline from the browser instead of from a PHP hook.
The practical upshot: SureCart gets the full funnel, but the two events that decide revenue, purchase and refund, have a hard server-side guarantee independent of any browser JavaScript, while the cart-funnel events ride the bridge. Amounts are handled correctly for zero-decimal currencies (JPY, KRW and the rest), where the SureCart amount is already in major units rather than cents.
Refunds: GA4 only, by design
Refund routing is the one place commerce tracking has to be opinionated, and Beaconry is. When an order is refunded on any of the three platforms, the refund is sent to GA4 only. Every ad channel is deliberately skipped. The reason is simple and worth understanding:
- GA4 has a real
refundevent that nets the refunded revenue against the original purchase, so your GA4 revenue reporting stays accurate without a spreadsheet. Beaconry sends the refund there as a negative-value event keyed to the same transaction id. - No ad channel has a true refund event. Meta CAPI, TikTok, Pinterest, Snapchat, Reddit and the rest either have no refund event at all or only a custom event that is a dead counter, invisible to Smart Bidding and never netted against the conversion. Worse, on some channels (Snapchat, for example) a forced custom refund event would collide with the slot reserved for lead conversions, polluting your conversion reporting. Sending a fake refund to an ad platform adds noise and zero optimisation value, so Beaconry does not.
This split is enforced centrally in the forwarder, not per platform, so all three commerce adapters get identical refund behaviour and a future channel inherits the rule automatically. If you need refunds reflected in an ad platform's reporting, that is a manual export against the platform's own order-management or offline-conversion-adjustment API, a different surface from the Conversions API Beaconry drives.
Per-channel event coverage
Not every funnel event maps cleanly onto every ad channel's standard event vocabulary. Beaconry only sends an event to a channel when that channel has a meaningful standard event for it; otherwise it is skipped at the forwarder so you do not get duplicate counts or junk custom events bloating a channel's conversion manager. GA4 receives the complete funnel. The table below shows where each WooCommerce or EDD event lands (SureCart's server-side events are purchase and refund; its bridged cart events follow the same per-channel rules as the rows below).
| Funnel event | GA4 | Meta CAPI | TikTok | Snapchat | ||
|---|---|---|---|---|---|---|
view_item | Yes | ViewContent | ViewContent | view_content | VIEW_CONTENT | VIEW_CONTENT |
view_item_list | Yes | Skipped | Skipped | view_category | Skipped | Skipped |
view_cart | Yes | Custom (ViewCart) | Skipped | Skipped | Skipped | Skipped |
add_to_cart | Yes | AddToCart | AddToCart | add_to_cart | ADD_CART | ADD_TO_CART |
remove_from_cart | Yes | Custom (RemoveFromCart) | Skipped | Skipped | Skipped | Skipped |
search | Yes | Search | Search | search | SEARCH | SEARCH |
begin_checkout | Yes | InitiateCheckout | InitiateCheckout | initiate_checkout | START_CHECKOUT | Custom |
add_payment_info | Yes | AddPaymentInfo | AddPaymentInfo | add_payment_info | ADD_BILLING | Custom |
purchase | Yes | Purchase | Purchase | checkout | PURCHASE | PURCHASE |
refund | Yes | Skipped | Skipped | Skipped | Skipped | Skipped |
Why "Skipped" appears so often on the upper-funnel and refund rows: most ad channels map view_item_list and view_cart onto the same ViewContent-style event as view_item, so sending all three would double or triple count a single page view in that channel's reporting without giving Smart Bidding any new signal. Those channels optimise on product-detail and conversion signals (ViewContent, AddToCart, Purchase), not on list or cart views, so Beaconry sends the meaningful one and skips the duplicates. remove_from_cart and refund have no standard slot on those channels at all. Pinterest is the exception that keeps view_item_list, because it has a distinct view_category event. Reddit's standard vocabulary has no checkout-stage event, so begin_checkout and add_payment_info land there as custom events rather than standard conversions, where Snapchat has native START_CHECKOUT and ADD_BILLING slots.
The four conversion-slot channels (Google Ads, Microsoft Ads, LinkedIn, X Ads) are not shown as columns because they work differently: each only fires the events you have explicitly mapped to a conversion action, goal, rule or slot in that channel's setup. Map purchase to a Google Ads conversion action and a purchase fires there; leave a funnel event unmapped and it simply does not dispatch to that channel. They receive the full funnel feed, your slot configuration decides which events become conversions. See the per-channel guides under Channel setup for the slot mapping on each.
Consent and admin traffic
Commerce events go through the same central consent gate as every other Beaconry event: without visitor consent stored in the nl_pref cookie, nothing is dispatched. Because a purchase or refund is a completed server-side action, consent is enforced once in the dispatcher rather than re-checked at every hook. Logged-in admins are excluded from front-end tracking by default, with one deliberate exception: when an admin marks a SureCart order paid or processes a refund from the dashboard, that conversion belongs to the customer who bought, not the admin clicking the button, so those specific server events bypass the admin gate and are still counted.
Troubleshooting
- "begin_checkout never fires on my block checkout": confirm the WooCommerce toggle is on under Beaconry, Commerce, and that you are not logged in as an admin (front-end tracking skips admins by default). The block checkout is covered by
is_checkout()attemplate_redirect, so a renamed checkout page is not the cause. Watch the event arrive in the GA4 real-time view or the Live Dashboard. - "My SureCart add-to-cart events are missing": those ride the browser bridge, not a PHP hook, so they only fire after the visitor accepts consent (the bridge is consent-gated like all browser events).
purchaseandrefundare server-side and independent of the bridge, so if purchases arrive but cart events do not, check the consent banner is being accepted. - "EDD shows no search or payment-info events": expected. EDD does not expose server-side hooks for either, so those two events are not part of the EDD funnel. The other eight are.
- "Refunds are not showing up in Meta / Google Ads": also expected, and by design. Refunds are routed to GA4 only because no ad channel has a real refund event. Check the GA4
refundevent, where the negative value nets against the purchase. - "A thank-you-page refresh counted the purchase twice": it should not, the per-order event_id plus an order-meta marker prevent it. If you see a genuine duplicate, verify you are not also firing a second purchase from a separate browser pixel without handing it Beaconry's event_id (Hybrid Mode wires that for you).