Skip to content

Tracking Flow

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 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.

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

mutation Checkout($input: CheckoutInput!) {
  checkout(input: $input) {
    order { databaseId }
  }
}
{
  "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.