UTM and Discount Configuration Guide
1. Purpose
When an event URL is shared — on a poster, by a vendor, in a Facebook post, in a newsletter, or in a club WhatsApp group — the entries it produces should be traceable back to that specific placement. This guide explains how to construct those URLs, what conventions to follow, and how to attach a discount code to a partner link.
The mechanism is provided by the event payment plugin (event-payment-plugin-woocommerce), which captures attribution parameters and a discount code when the visitor lands on the WordPress site, carries them across the registration-portal round trip, and writes them onto the resulting WooCommerce order.
Covered in this guide:
-
The parameters the plugin supports and what each one is for
-
The naming convention to apply across all channels
-
Per-channel recipes with worked examples
-
Configuring the WooCommerce coupon behind a discount link
-
The constraints that will silently cost you attribution if ignored
-
How to read the captured data back out
This guide is descriptive — it explains the mechanism and the conventions. For configuring the event and its products in the first place, see Configure an Event. For onboarding a new organisation onto the platform, see Provision a Registration Portal Tenant.
2. Audience & Prerequisites
This guide is for the person publishing event links — marketing, event organiser, or club administrator. You need:
-
The event’s public WordPress landing page URL
-
WooCommerce admin access on the tenant site, if you intend to create discount codes
-
Event payment plugin 2.1.0 or later installed on the tenant site — attribution capture does not exist in earlier versions
To confirm the plugin version is new enough, request any page on the site with a test parameter and check that a cookie comes back:
curl -sI "https://<tenant-site>/?utm_source=test&utm_campaign=test" | grep -i set-cookie
An epa_utm cookie in the response means capture is active. No cookie means the plugin is too old, or the request did not reach the WordPress front end.
3. How Attribution Flows
Understanding the path explains every constraint later in this guide.
-
Capture. The visitor opens a tagged link to any front-end page on the tenant WordPress site. On WordPress
init, the plugin reads theutm_*anddiscountparameters out of the query string and writes them into two cookies —epa_discount_codeandepa_utm— scoped to that host, for 72 hours. -
Round trip. The visitor completes entry in the registration portal, which runs on a different host. The cookies are not visible there, and they do not need to be — they simply wait.
-
Order creation. admin-service creates the WooCommerce order and returns the visitor to the pay-for-order page, which is back on the original WordPress host, so the cookies are in scope again.
-
Application. On the pay page the plugin writes the UTM values onto the order as metadata, and applies the carried discount code to the order using WooCommerce’s own coupon engine. The discounted total renders before the visitor pays.
The consequence worth internalising: attribution is written at the pay page, from a cookie set on the landing page. Anything that breaks the link between those two points loses the attribution.
4. Supported Parameters
| Parameter | Purpose |
|---|---|
|
Who or where the visitor came from — the specific partner, placement, venue or platform. This is the field that varies most. |
|
The channel type — how the link was delivered. Drawn from a small fixed vocabulary. |
|
The event and year. One value per event, never varies within an event. |
|
The specific creative or variant, when one source produces several links (poster A vs poster B, first send vs reminder). |
|
Historically a paid-search keyword. Captured and stored, but there is no established use for it in this system. Leave it off unless you have a specific need. |
|
A WooCommerce coupon code to apply automatically. Both spellings are accepted; if both appear, |
No other parameters are captured. Platform click identifiers such as gclid or fbclid pass through harmlessly but are not stored.
5. Naming Convention
Consistency matters more than cleverness. Fix utm_campaign and utm_medium to closed vocabularies, and let utm_source carry the variation.
Format rules, applied everywhere:
-
Lowercase only
-
Hyphens between words, never spaces or underscores
-
No personal names, e-mail addresses or any other personal information — this data lands on an order record
-
Never abbreviate inconsistently: pick
wcscorwc-schools, and use it forever
5.1. Campaign
One value per event, formed as the event slug plus the year:
utm_campaign=tdw2026 # Tour de Worcester 2026
utm_campaign=rooibos2026 # Rooibos MTB 2026
Decide this once when the event opens and put it in the event’s configuration notes. Every link for that event uses it unchanged, forever. This is what makes "how did the 2026 event perform overall" answerable.
5.2. Medium
A closed vocabulary. Adding new values erodes the ability to compare channels, so extend it deliberately, not casually.
| Value | Use for |
|---|---|
|
A QR code scanned off printed material |
|
A typed or short URL printed on material, where the reader keys it in |
|
Organic social media posts |
|
Newsletters and mailing-list sends |
|
Club and community WhatsApp groups |
|
A partner, vendor or club website linking to the event |
Keep qr and print distinct. If both share one value you cannot tell whether people scan posters or type the URL, which is exactly the question that decides next season’s poster design.
5.3. Source
The specific origin. This is the only field with an open vocabulary, and it should be as specific as you can usefully make it — spar-worcester, not supermarket.
Maintain a list of the sources issued per event, so that the same partner is spelled the same way in every campaign and you can reconcile the totals afterwards.
6. Channel Recipes
All examples land on the event’s public entry page. Substitute your own host, page and campaign.
6.1. Poster QR Codes
Issue a separate source per venue, so that you learn which locations work:
https://tourdeworcester.co.za/entries?utm_source=spar-worcester&utm_medium=qr&utm_campaign=tdw2026&utm_content=a3-poster
Do not encode that full string into the QR code. A long URL produces a dense QR grid that scans poorly on a poster — at distance, at an angle, in direct sun. Instead:
-
Create a short redirect slug on the WordPress site, for example
/go/spar-worcester. -
Point it at the fully tagged URL with a 302 redirect.
-
Encode only the short slug in the QR code.
This also means a printed poster can be retargeted later — change the redirect, not the print run.
6.2. Vendors and Partners
Pair the attribution with a discount code, so the partner has something to offer:
https://tourdeworcester.co.za/entries?utm_source=cyclelab-worcester&utm_medium=referral&utm_campaign=tdw2026&discount=cyclelab10
Give each partner their own coupon code, even when the discount value is identical across partners. The coupon is recorded on the order itself, which makes it a far more durable attribution signal than the cookie — see Treat the coupon as the hard signal.
6.3. Social Media
utm_source is the platform; utm_content is the individual post:
https://tourdeworcester.co.za/entries?utm_source=facebook&utm_medium=social&utm_campaign=tdw2026&utm_content=entries-open-post
This lets you see which post worked without exploding the source list into dozens of one-off values.
6.4. Newsletters
https://tourdeworcester.co.za/entries?utm_source=waitlist&utm_medium=email&utm_campaign=tdw2026&utm_content=send-1
Here utm_source names the audience segment — waitlist, past-entrants, wpca-members — because for a mailing list the segment is the meaningful distinction, not the platform. Use utm_content to separate the sends within one campaign.
Most mailing platforms can append UTM parameters automatically per send. Configure it there rather than hand-editing every link, so you never ship a half-tagged link — see Tagging is last-touch, and overwrites wholesale for why partial tagging is actively harmful.
6.5. Club WhatsApp Groups
https://tourdeworcester.co.za/entries?utm_source=wcsc&utm_medium=whatsapp&utm_campaign=tdw2026
|
WhatsApp links get forwarded well beyond the group they were posted in. Read a WhatsApp source as "reach seeded via this club", not "members of this club". If you need a clean count for a club — for a rebate or a challenge, say — issue that club a discount code and count redemptions instead. |
7. Configuring the Discount Coupon
The discount parameter carries a code; the code must already exist as a WooCommerce coupon on the tenant site, or the plugin silently skips it and the visitor pays full price.
To create one: WooCommerce → Marketing → Coupons → Add coupon.
The settings that matter for event entries:
| Setting | Guidance |
|---|---|
Coupon code |
Lowercase. It appears in a URL that will be shared widely, so treat it as public. Do not use a code you would be unhappy to see posted in an open forum. |
Discount type / amount |
Percentage or fixed. Percentage travels better across events with different entry fees. |
Expiry date |
WooCommerce expiry is exclusive. A coupon with expiry |
Product categories |
Restrict to the entries category. Without this the discount also applies to CSA day licences, which are a pass-through cost and should not be discounted. |
Exclude products |
Belt-and-braces alongside the category restriction — explicitly exclude the CSA day licence product. |
Usage limit per coupon |
Leave at 0 (unlimited) for a broad campaign; set a cap for a limited offer. |
Usage limit per user |
Normally 1. Enforced by billing e-mail for guest checkout. |
Limit usage to X items |
Read this one carefully. Setting it to 1 discounts a single entry line. On an order where one person enters three riders, only one entry is discounted. Leave it blank if the offer is meant to cover every rider on the order. |
Individual use only |
Tick it unless you intend codes to stack. |
7.1. Worked Example
The Tour de Worcester waitlist campaign uses a coupon configured as:
| Setting | Value |
|---|---|
Code |
|
Type |
Percentage, 10% |
Expires |
2026-08-31 (last usable day 30 August) |
Product categories |
Entries |
Excluded products |
CSA Day License |
Usage limit per coupon |
Unlimited |
Usage limit per user |
1 |
Limit usage to X items |
1 |
Individual use only |
Yes |
Paired with this link:
https://tourdeworcester.co.za/?utm_source=waitlist&utm_medium=email&utm_campaign=tdw2026&utm_content=send-1&discount=waitlist
Note the effect of Limit usage to X items = 1: a parent entering two children on one order receives 10% off one entry only. That may well be intended — it is called out here because it is the setting most often configured by accident.
8. Constraints
These are the ways attribution is lost. Each one is a property of the mechanism described in How Attribution Flows, not a bug.
8.1. The link must land on the WordPress site
Capture runs on WordPress. A tagged link pointing straight at the registration portal captures nothing at all — no cookie is ever set, and the entry arrives unattributed. Always link to a page on the tenant’s public WordPress site and let the normal entry flow carry the visitor onward.
Any front-end page works, including the home page, so a link to / is functional. Linking to the event’s entry page is still better, simply because it saves the visitor a click.
8.2. The host must not change
The cookies are host-only. If the poster reads www.example.co.za and the pay page runs on example.co.za, the cookie set on the first host is never sent to the second, and the attribution and the discount are both lost.
Pick one canonical host, and verify that the other redirects to it. If a redirect exists it must happen before WordPress processes the request — a WordPress-level canonical redirect fires after capture has already written the cookie to the wrong host.
8.3. Tagging is last-touch, and overwrites wholesale
Each visit carrying any utm_* parameter replaces the entire stored set. A link carrying only utm_source therefore wipes the campaign and medium recorded by an earlier click.
Always emit the same field set on every link for a campaign. This is the main argument for configuring UTM parameters centrally in the mailing platform rather than assembling them by hand.
A visit carrying no utm_* parameters leaves the existing values untouched, so ordinary browsing between the click and the entry is harmless.
8.4. The window is 72 hours
The cookies live for 72 hours from the click. A visitor who clicks the newsletter on Monday, thinks about it, and enters the following weekend arrives with neither attribution nor discount.
For a campaign where a longer consideration window is expected, the lifetime is adjustable through the epa_discount_cookie_ttl filter — a development change, not a setting in the WordPress admin. Raise it with the development team before the campaign, not after.
8.5. Attribution is written at the pay page
An entry that never reaches the WooCommerce pay-for-order page never gets its metadata written. Free entries and any flow that bypasses payment are not attributed.
8.6. In-app browsers break the chain
A QR code scanned from within Facebook, Instagram or WhatsApp opens in that app’s embedded browser. If the visitor later reopens the link in Safari or Chrome to finish entering, the cookies do not travel with them.
Nothing in the URL can prevent this. It is the strongest practical reason to attach a discount code to any link that genuinely matters.
8.7. Treat the coupon as the hard signal
UTM values are a soft signal — they depend on a cookie surviving a multi-host round trip, a 72-hour window, and the visitor staying in one browser.
The coupon code is a hard signal. It is applied to the order itself and survives cookie loss, device switches and in-app browsers. When a number has to be defensible — a partner rebate, a revenue share, a per-club count — count coupon redemptions and use the UTM data only as supporting colour.
9. Reading the Data Back
The captured values are written onto the order as metadata:
| Meta key | Contents |
|---|---|
|
The captured attribution values |
|
Timestamp at which attribution was stored |
|
Timestamp of the single auto-apply attempt for this order |
|
These keys are underscore-prefixed, which means WooCommerce hides them from the order screen’s Custom Fields panel. There is currently no admin UI and no export column for attribution data. Reporting requires a direct database query, as below. If regular campaign reporting is required, that gap needs a work item. |
On a site with High-Performance Order Storage enabled, attribution lives in wp_wc_orders_meta:
SELECT
MAX(CASE WHEN m.meta_key = '_utm_campaign' THEN m.meta_value END) AS campaign,
MAX(CASE WHEN m.meta_key = '_utm_medium' THEN m.meta_value END) AS medium,
MAX(CASE WHEN m.meta_key = '_utm_source' THEN m.meta_value END) AS source,
COUNT(DISTINCT o.id) AS orders,
SUM(o.total_amount) AS revenue
FROM wp_wc_orders o
JOIN wp_wc_orders_meta m ON m.order_id = o.id
WHERE o.status = 'wc-completed'
GROUP BY o.id
ORDER BY campaign, medium, source;
Coupon redemptions — the hard signal — are counted from the coupon’s own usage count under Marketing → Coupons, or per order from the order’s applied coupon lines.
10. Pre-Publication Checklist
Before a link goes onto a poster, into a send, or out to a partner:
-
utm_campaignmatches the value already in use for this event. -
utm_mediumis drawn from the fixed vocabulary in Medium. -
utm_sourceis specific, and recorded on the per-event source list. -
All links in the campaign carry the same set of fields — see Tagging is last-touch, and overwrites wholesale.
-
The link lands on a page of the tenant WordPress site, on the canonical host.
-
If a
discountcode is present, the coupon exists, is published, has not expired, and its category restrictions and item limits are as intended. -
The link has been opened once in a private browser window and the discount confirmed to appear on the pay page.
That last step is the one that catches everything else. Do it once per campaign.