Tracking Flow¶
Cookie strategy¶
The plugin maintains a UTM history of 2 touch points: the first visit and the most recent visit before checkout. These are stored as two parallel sets of cookies:
| Cookie prefix | Purpose | Example key |
|---|---|---|
| (none) | First UTM touch — never overwritten | utm_source |
current_ | Most recent UTM touch — updated each visit | current_utm_source |
Cookie reference¶
| Cookie | Required | Set on initial visit | Set on revisit |
|---|---|---|---|
utm_source | Yes | Yes | No |
utm_medium | Yes | Yes | No |
utm_campaign | Yes | Yes | No |
utm_term | No | Yes | No |
utm_content | No | Yes | No |
utm_entry_date | No | Yes | No |
current_utm_source | No | No | Yes |
current_utm_medium | No | No | Yes |
current_utm_campaign | No | No | Yes |
current_utm_term | No | No | Yes |
current_utm_content | No | No | Yes |
current_utm_entry_date | No | No | Yes |
Lifetime: All cookies expire 14 days from when they were last written. On a revisit with new UTM data the initial cookies' lifetimes are extended by another 14 days, but their values are never changed.
Flush: All UTM cookies are deleted when the customer completes checkout.
Required parameters: utm_source, utm_medium, and utm_campaign must all be present in the URL or no cookies are written.
Cookie lifecycle¶
flowchart TD
A[Visitor arrives] --> B{utm_source + utm_medium
+ utm_campaign in URL?}
B -- No --> Z[No action]
B -- Yes --> C{User already has
UTM cookies?}
C -- No --> D[Set initial UTM cookies
Set utm_entry_date]
C -- Yes --> E{Same UTM data
as existing cookies?}
E -- Same --> Z
E -- Different --> F[Flush current_ cookies
Set new current_ UTM cookies
Extend initial cookie lifetime] Checkout: traditional WooCommerce¶
Fires on woocommerce_checkout_order_processed at priority 200 (intentionally after WooCommerce Subscriptions at priority 100, which is needed to resolve the subscription ID).
sequenceDiagram
participant WC as WooCommerce
participant CO as Checkout
participant CK as Cookies
participant DB as utm_tracking
WC->>CO: woocommerce_checkout_order_processed
CO->>CK: read initial UTM cookies
CO->>CK: read current_ UTM cookies
alt current_ cookies are set
CO->>DB: INSERT current UTM row (conversion = true)
CO->>DB: INSERT initial UTM row (conversion = false)
else only initial cookies
CO->>DB: INSERT initial UTM row (conversion = true)
end
CO->>CK: flush all UTM cookies Checkout: GraphQL (WPGraphQL + WooCommerce)¶
Fires on graphql_woocommerce_after_checkout. The headless client passes UTM data as a campaignData field on CheckoutInput instead of relying on server-side cookies.
The same conversion logic applies: if current is present it gets conversion = true and initial gets conversion = false.
GraphQL types registered¶
input UTMDataInput {
utm_source: String
utm_medium: String
utm_campaign: String
utm_term: String
utm_content: String
utm_entry_date: String
}
input CampaignDataInput {
initial: UTMDataInput
current: UTMDataInput
}
# Added to the existing CheckoutInput type:
extend input CheckoutInput {
campaignData: CampaignDataInput
}
Example mutation¶
{
"input": {
"campaignData": {
"initial": {
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "spring-sale",
"utm_entry_date": "2026-01-15 09:00:00"
},
"current": {
"utm_source": "email",
"utm_medium": "newsletter",
"utm_campaign": "spring-sale",
"utm_entry_date": "2026-06-10 14:30:00"
}
}
}
}
If only initial is provided (first-touch conversion), omit the current key entirely.