← Back to Dimensions

Customer Value Tier

Also known as: Customer Value Segment, LTV Tier, CLV Tier, Customer Value Band

RetentiondimensionE-commerce: Customer Retention, Loyalty & LTVltvE-commerce

Assigns each customer to a High, Medium or Low tier by percentile rank of historical lifetime revenue: top 20%, next 30%, bottom 50%. Pure value tiering, unlike RFM Segment or Customer Loyalty Tier.

Business Use Case

1️⃣ CRM team: when a customer enters High tier, move them to the VIP journey and suppress discount-led offers.,2️⃣ Paid media team: refresh High-tier seed audiences monthly for lookalikes; exclude Low tier from costly retargeting.,3️⃣ Customer Service: route High-tier contacts to the priority queue and review their SLA breaches weekly.,4️⃣ Finance: review revenue share and customer count by tier each quarter to set retention budget per tier.

Business Criticality

High

Journey / Navigation Area

Monetization

Owning Function

Marketing

Scope

User

Calculation Notes

1️⃣ LTV input is net historical revenue per customer: value of orders not cancelled, minus refunds, excluding tax and shipping, all orders to date.,2️⃣ Rank purchasers by LTV with PERCENT_RANK: ≥ 0.80 is High, 0.50 to < 0.80 is Medium, < 0.50 is Low.,3️⃣ Customers with no completed purchase get a null tier, never Low; guest orders count only once stitched to a user_id.,4️⃣ Cut-offs are recomputed monthly on the full purchasing base and held for the month, so tier moves reflect customers, not threshold drift.,5️⃣ Exclude every cancelled order (fraud included), plus test and employee orders, before ranking; customers tied at a cut-off take the higher tier.,6️⃣ Convert order values to the reporting currency at the order-date rate before summing.

Technical Details

Data Typestring
Lookback WindowMonthly, Quarterly

Events

PlatformEvent
Google Analytics 4login
Adobelogin

Data Mappings

W3 Data Layer
{
  "user": [
    {
      "profile": [
        {
          "profileInfo": {
            "profileID": "usr_48213",
            "loginMethod": "email"
          },
          "attributes": {
            "customerValueTier": "High"
          }
        }
      ]
    }
  ],
  "event": [
    {
      "eventInfo": {
        "eventName": "login"
      }
    }
  ]
}
GA4 Data Layer
{
  "event": "login",
  "method": "email",
  "user_id": "usr_48213",
  "user_properties": {
    "customer_value_tier": "High"
  }
}
Adobe Client Data Layer
{
  "event": "login",
  "user": {
    "id": "usr_48213",
    "loginMethod": "email",
    "customerValueTier": "High"
  }
}
XDM Mapping
{
  "identityMap": {
    "userID": [
      {
        "id": "usr_48213",
        "primary": true
      }
    ]
  },
  "_openkpis": {
    "customerValueTier": "High"
  }
}

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Monthly snapshot: tiers held for the current month, ranked on all orders placed before it began
DECLARE end_date DATE DEFAULT DATE_TRUNC(CURRENT_DATE(), MONTH);
DECLARE start_date DATE DEFAULT DATE_SUB(end_date, INTERVAL 20 YEAR);

WITH
-- Purchase and refund events with order parameters pivoted; net value excludes tax and shipping
commerce_events AS (
  SELECT
    event_name,
    PARSE_DATE('%Y%m%d', event_date) AS event_day,
    user_id,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') AS transaction_id,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'currency') AS currency,
    (SELECT COALESCE(value.double_value, value.int_value) FROM UNNEST(event_params) WHERE key = 'value')
      - IFNULL((SELECT COALESCE(value.double_value, value.int_value) FROM UNNEST(event_params) WHERE key = 'tax'), 0)
      - IFNULL((SELECT COALESCE(value.double_value, value.int_value) FROM UNNEST(event_params) WHERE key = 'shipping'), 0) AS net_value
  FROM `your-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', start_date) AND FORMAT_DATE('%Y%m%d', DATE_SUB(end_date, INTERVAL 1 DAY))
    AND event_name IN ('purchase', 'refund')
),
-- Orders resolved to a customer through the order's stitched customer id (guest orders count once stitched), never cancelled for any reason (fraud included), excluding test and employee orders; one row per order
orders AS (
  SELECT
    e.transaction_id,
    MAX(co.customer_id) AS user_id,
    MIN(e.event_day) AS order_day,
    MAX(e.currency) AS currency,
    MAX(e.net_value) AS net_value
  FROM commerce_events AS e
  JOIN `your-project.commerce.orders` AS co ON co.order_id = e.transaction_id
  WHERE e.event_name = 'purchase'
    AND co.customer_id IS NOT NULL
    AND co.cancelled_at IS NULL
    AND NOT co.is_test
    AND NOT co.is_employee
  GROUP BY e.transaction_id
),
-- Refunded value per order, excluding tax and shipping
refunds AS (
  SELECT transaction_id, SUM(net_value) AS refund_value
  FROM commerce_events
  WHERE event_name = 'refund'
  GROUP BY transaction_id
),
-- Net lifetime value per customer in the reporting currency at the order-date rate
customer_ltv AS (
  SELECT
    o.user_id,
    SUM((o.net_value - IFNULL(r.refund_value, 0)) * fx.rate_to_reporting) AS net_ltv
  FROM orders AS o
  LEFT JOIN refunds AS r USING (transaction_id)
  JOIN `your-project.finance.fx_rates` AS fx ON fx.rate_date = o.order_day AND fx.currency = o.currency
  GROUP BY o.user_id
),
-- Rank from the top over the full purchasing base; ties share the top position of their group, so they take the higher tier
ranked AS (
  SELECT user_id, PERCENT_RANK() OVER (ORDER BY net_ltv DESC) AS rank_from_top
  FROM customer_ltv
),
-- Every identified customer plus every stitched purchaser, so non-purchasers carry a null tier
customers AS (
  SELECT user_id
  FROM `your-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', start_date) AND FORMAT_DATE('%Y%m%d', DATE_SUB(end_date, INTERVAL 1 DAY))
    AND user_id IS NOT NULL
  UNION DISTINCT
  SELECT user_id FROM customer_ltv
)
-- Tier per customer: percentile >= 0.80 High, 0.50 to < 0.80 Medium, < 0.50 Low (percentile = 1 - rank_from_top)
SELECT
  c.user_id,
  CASE
    WHEN r.rank_from_top <= 0.20 THEN 'High'
    WHEN r.rank_from_top <= 0.50 THEN 'Medium'
    WHEN r.rank_from_top IS NOT NULL THEN 'Low'
  END AS customer_value_tier
FROM customers AS c
LEFT JOIN ranked AS r USING (user_id);
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Monthly snapshot: tiers held for the current month, ranked on all orders placed before it began
WITH params AS (
  SELECT TRUNC(CURRENT_DATE, 'MM') AS end_date, ADD_MONTHS(TRUNC(CURRENT_DATE, 'MM'), -240) AS start_date
),
-- Purchase and refund events with order fields; net value excludes tax and shipping
commerce_events AS (
  SELECT
    e.eventType AS event_name,
    TO_DATE(e.timestamp) AS event_day,
    e.identityMap['userID'][0].id AS user_id,
    e.commerce.order.purchaseID AS transaction_id,
    e.commerce.order.currencyCode AS currency,
    e.commerce.order.priceTotal
      - COALESCE(e.commerce.order.taxAmount, 0)
      - COALESCE(e.commerce.shipping.shippingAmount, 0) AS net_value
  FROM web_events_dataset AS e
  CROSS JOIN params AS p
  WHERE e.eventType IN ('purchase', 'refund')
    AND e.timestamp >= p.start_date
    AND e.timestamp < p.end_date
),
-- Orders resolved to a customer through the order's stitched customer id (guest orders count once stitched), never cancelled for any reason (fraud included), excluding test and employee orders; one row per order
orders AS (
  SELECT
    e.transaction_id,
    MAX(co.customer_id) AS user_id,
    MIN(e.event_day) AS order_day,
    MAX(e.currency) AS currency,
    MAX(e.net_value) AS net_value
  FROM commerce_events AS e
  JOIN commerce_orders_dataset AS co ON co.order_id = e.transaction_id
  WHERE e.event_name = 'purchase'
    AND co.customer_id IS NOT NULL
    AND co.cancelled_at IS NULL
    AND NOT co.is_test
    AND NOT co.is_employee
  GROUP BY e.transaction_id
),
-- Refunded value per order, excluding tax and shipping
refunds AS (
  SELECT transaction_id, SUM(net_value) AS refund_value
  FROM commerce_events
  WHERE event_name = 'refund'
  GROUP BY transaction_id
),
-- Net lifetime value per customer in the reporting currency at the order-date rate
customer_ltv AS (
  SELECT
    o.user_id,
    SUM((o.net_value - COALESCE(r.refund_value, 0)) * fx.rate_to_reporting) AS net_ltv
  FROM orders AS o
  LEFT JOIN refunds AS r ON r.transaction_id = o.transaction_id
  JOIN fx_rates_dataset AS fx ON fx.rate_date = o.order_day AND fx.currency = o.currency
  GROUP BY o.user_id
),
-- Rank from the top over the full purchasing base; ties share the top position of their group, so they take the higher tier
ranked AS (
  SELECT user_id, PERCENT_RANK() OVER (ORDER BY net_ltv DESC) AS rank_from_top
  FROM customer_ltv
),
-- Every identified customer plus every stitched purchaser, so non-purchasers carry a null tier
customers AS (
  SELECT e.identityMap['userID'][0].id AS user_id
  FROM web_events_dataset AS e
  CROSS JOIN params AS p
  WHERE e.timestamp >= p.start_date
    AND e.timestamp < p.end_date
    AND e.identityMap['userID'][0].id IS NOT NULL
  UNION
  SELECT user_id FROM customer_ltv
)
-- Tier per customer: percentile >= 0.80 High, 0.50 to < 0.80 Medium, < 0.50 Low (percentile = 1 - rank_from_top)
SELECT
  c.user_id,
  CASE
    WHEN r.rank_from_top <= 0.20 THEN 'High'
    WHEN r.rank_from_top <= 0.50 THEN 'Medium'
    WHEN r.rank_from_top IS NOT NULL THEN 'Low'
  END AS customer_value_tier
FROM customers AS c
LEFT JOIN ranked AS r ON r.user_id = c.user_id;

Source Data

Digital Analytics (GA4) + Commerce Platform Orders (Shopify / Salesforce Commerce Cloud / Adobe Commerce)

Dependencies

Prerequisite: List the dependencies required for this Dimension

Events:

purchaserefund-issued

Report Attributes

Acquisition Cohort Traffic Source Device Category Item Category Customer Status

Segment Eligibility

true

Governance

Data SensitivityInternal

Governance

Created bydevyendarm
Created on9/25/2026
Last modified bydevyendarm
Last modified on9/25/2026
Version1.0.0
Validation stateqa passed
StatusPUBLISHED

GitHub

View related Pull Requestcatalog/dimensions/customer-value-tier.yml

Community Discussion