Click-Tracking
Some conversions are just a click. A "Book a call" button, an outbound link to your Polar checkout, a "Start free trial" CTA that lives in a third-party app. Add one of five Beaconry CSS classes to that element and the click fires a real server-side conversion to every channel you have connected, with no per-vendor snippet and no JavaScript glue.
What it does
Form submissions and commerce events are caught by dedicated hooks (Beaconry binds the real submission hook of seven form plugins and the real funnel hooks of WooCommerce, EDD and SureCart). But plenty of meaningful actions are neither a form nor a cart event: an anchor that leaves your site for an external checkout, a button that opens a booking widget, a "download the trial" link, a pricing CTA that scrolls to an iframe. There is no server-side hook to bind to, because the action happens entirely in the browser and often navigates away.
Click-Tracking closes that gap with a generic, class-driven capture. You decorate the element with a Beaconry class, optionally annotate it with a few data attributes, and Beaconry treats the click like any other event: it runs through the same same-origin REST endpoint and the same server-side forwarder, so it lands on Meta, Google Ads, TikTok and the rest as a standard conversion. No vendor pixel touches the page.
The five class slots
There are exactly five slots. Each maps to a standard advertising event, deliberately, so vendor channels keep their optimisation signal instead of receiving an opaque custom event they can't bid on. Add the class to any clickable element (<a>, <button>, or anything you delegate a click to) and that slot's event fires.
| CSS class | GA4 event | Meta CAPI | Typical use |
|---|---|---|---|
.beaconry-checkout | begin_checkout | InitiateCheckout | Outbound link to an external checkout (Polar, Stripe, a hosted cart). |
.beaconry-lead | generate_lead | Lead | A "Request a quote" or "Talk to sales" button that opens a widget, not a native form. |
.beaconry-signup | sign_up | CompleteRegistration | A "Create account" / "Start free trial" CTA that hands off to an external app. |
.beaconry-contact | contact | Contact (custom) | A click-to-call link, a mailto, a "Message us on WhatsApp" button. |
.beaconry-cta | select_content | ViewContent | A generic engagement CTA you want to count but that isn't a checkout, lead, signup or contact. |
A note on the GA4 names, because it trips people up: the checkout slot sends GA4 begin_checkout, not initiate_checkout. begin_checkout is GA4's canonical spelling, and the forwarder is what translates it per channel: Meta and TikTok receive InitiateCheckout, Pinterest receives initiate_checkout, Snapchat its own start-checkout event, and so on. You configure one CSS class, every platform gets the name it expects.
The .beaconry-contact and .beaconry-cta slots resolve to events that aren't Meta standard events, so on Meta they go out as custom events (Contact, ViewContent) while staying first-class standard events on GA4. That's intentional: a contact or a generic CTA is real engagement worth recording, but it shouldn't masquerade as a checkout or a purchase.
Per-button overrides with data attributes
The class decides the event. Four optional data-bcnr-* attributes decide the payload, per element, so two buttons sharing a class can still carry different values and catalog ids.
| Attribute | Effect |
|---|---|
data-bcnr-value | Monetary value for this click. Overrides the per-slot default from the Tracking tab. A bare number, in the event's currency. |
data-bcnr-currency | Three-letter ISO currency for this click. Overrides the Tracking-tab default. Uppercased automatically; falls back to EUR if nothing is set anywhere. |
data-bcnr-content-id | Catalog / product id for this click. Populates Meta's content_ids[] and builds a single-item GA4 items[] entry so standard reports can attribute it. |
data-bcnr-content-name | Human-readable name for that item, attached alongside the id (Meta content_name, GA4 item_name). |
Example, a checkout button that hands off to Polar and tells every channel exactly what was bought and for how much:
<a class="beaconry-checkout"
data-bcnr-value="149"
data-bcnr-currency="USD"
data-bcnr-content-id="studio-tier"
data-bcnr-content-name="Studio Tier"
href="https://buy.polar.sh/...">Buy Studio</a>That single click dispatches a begin_checkout to GA4 with a one-item items[] array, an InitiateCheckout to Meta with content_ids: ["studio-tier"] and value 149 USD, and the matching event to every other connected channel. The button's visible text is captured as a short label (truncated to 100 characters) for the Live-Dashboard "Source" column, and if the element is an <a> its href is recorded too, so you can see in the dashboard which outbound link a conversion came from.
All four attributes are optional. Omit them and the click still fires, using the per-slot default value and currency you set on the Tracking tab (or zero value with no catalog data if you never set one). They matter most for the checkout and lead slots where a value is genuinely useful for ROAS bidding; a plain contact or CTA click is often fine with no value at all.
How to enable
Click-Tracking is off by default. Turn it on under wp-admin, Beaconry, Tracking, Click-Tracking card. On that card you set the global defaults that apply when an element has no data-bcnr-* override:
- A default currency for all click events.
- A default value per slot: one each for checkout, lead, signup, contact and CTA. Leave a slot at zero if those clicks have no monetary value to assign.
Save, and from the next page load Beaconry attaches a single delegated click-listener to the document. There is nothing to add to your theme beyond the CSS classes themselves, and nothing to paste into individual buttons unless you want a per-button override.
How it works under the hood
- One delegated listener. When the feature is enabled the browser engine (
nl-data.js) registers a single click listener on the document and resolves the nearest ancestor matching any of the five classes viaclosest(). One listener for the whole page, not one per button, so adding tagged elements later (or rendering them dynamically) just works. - Class to event. The matched class selects the GA4 event name and the default-value slot. Data attributes, if present, override value, currency, content id and content name on top of the Tracking-tab defaults.
- Delivery via sendBeacon. The event is posted with
navigator.sendBeacon()to the same-origin REST endpoint. That matters specifically for click-tracking: most tagged clicks are outbound links that immediately navigate away, and a normalfetchwould be cancelled by the navigation.sendBeaconhands the request to the browser to send in the background, so the conversion survives the page leaving. - Standard server-side fan-out. On the server the event runs through the exact same
BCNR_Forwarderdispatch as everything else, so it inherits PII handling, the consent gate, multi-currency normalisation, the per-channel event-name mapping and the Live-Dashboard recorder. No channel-specific click code exists; a new channel added to Beaconry picks up click events automatically. - Hybrid mirror. If you run Hybrid Mode for a channel (its browser pixel is loaded for audience building), the engine also fires the matching standard event on that pixel with the same
event_idthe server used, so the vendor deduplicates the browser and server copies. LinkedIn's Insight Tag and Google Ads gtag.js have no usable client-side conversion call, so they receive the click server-side only. See Hybrid Mode.
No double counting with auto-events
Beaconry's browser engine already fires generic engagement events for outbound links and CTA-style clicks. When Click-Tracking is on, the engine detects that a clicked element carries a Beaconry class and suppresses its own generic outbound / CTA auto-event for that click, so a tagged button produces exactly one event (the click-tracking conversion) and not two. You can tag a link that the engine would otherwise also have logged as a generic outbound click without worrying about a duplicate.
The kill switch (wp-config)
For power users and shared databases there is a hard override. Define this in wp-config.php:
define( 'BCNR_CLICK_TRACKING_ENABLED', false );When the constant is set it wins over the DB toggle at read time, exactly like Beaconry's other constant overrides. The classic use case is a staging site that shares (or was cloned from) the production database: production has Click-Tracking enabled in the DB, but you do not want staging firing real conversions into your ad accounts, so you set the constant to false in staging's wp-config.php and the whole feature is off there regardless of what the shared option says. Set it to true to force-enable in the same way.
When to use it (and when not to)
Click-Tracking is the right tool when the conversion is a click on something Beaconry can't hook server-side. Reach for it for:
- Outbound links to an external checkout or affiliate, where the purchase completes off your domain.
- Buttons that open a third-party widget (booking, chat, calculator) with no WordPress form behind them.
- Click-to-call, mailto and messaging links you want to count as contacts.
- Any high-intent CTA where the click itself is the signal you want to optimise toward.
It is the wrong tool when a dedicated hook already exists, because the hook is more accurate. Specifically:
- Native forms: use the form adapters. They fire on the real submission (a click on a submit button is not a submission, the user could still fail validation), and they hash recognised PII for match quality, which a click can't do.
- WooCommerce, EDD or SureCart: use the commerce funnel. A real
begin_checkoutfrom the commerce adapter carries the actual cart contents and value; a.beaconry-checkoutclick only carries what you hard-code in data attributes.
Rule of thumb: if there is a server-side hook for the action, prefer it and keep Click-Tracking for the clicks that have nowhere else to be caught. The two layers don't conflict, the commerce begin_checkout and a click begin_checkout are distinct events from distinct sources, but you usually want one or the other for a given button, not both.