and runs during HTML parse, but hubspotutk is set by js.hs-analytics.net, measured loading at ~834ms. On a first visit the cookie does not exist yet, so a single read returns nothing and the visitor gets no external ID at all — and a first visit is exactly what an ad click is. So poll briefly until HubSpot sets it. The SDK loads lazily (~4.5s) and replays a queue, so an init queued a second or two in still lands before any events are transmitted. */ (function attachExternalId() { var attempts = 0; var MAX_ATTEMPTS = 40; /* 40 x 200ms = 8s, then give up quietly */ var timer = null; function tryAttach() { var raw = getExternalId(); if (!raw) { if (++attempts >= MAX_ATTEMPTS && timer) clearInterval(timer); return; } if (timer) clearInterval(timer); sha256Hex(raw).then(function (hash) { oaiq("init", { user: { external_id_sha256: hash } }); }).catch(function () {}); } tryAttach(); /* returning visitor: immediate */ if (!getExternalId()) timer = setInterval(tryAttach, 200); })(); /* --------------------------------------------------------------------------- 2. Load the SDK off the critical path Whichever comes first: the main thread going idle after load, or the first sign of real user intent. No preconnect on purpose — a TLS handshake during LCP for a script we deliberately deferred is a net loss. Measured on clickpost.ai: load event 3614ms, SDK fetched 4508ms. ------------------------------------------------------------------------ */ var loaded = false; var INTENT = ["pointerdown", "keydown", "scroll"]; function loadSdk() { if (loaded) return; loaded = true; INTENT.forEach(function (t) { removeEventListener(t, loadSdk, { passive: true }); }); var s = document.createElement("script"); s.src = SDK; s.async = true; s.fetchPriority = "low"; document.head.appendChild(s); } INTENT.forEach(function (t) { addEventListener(t, loadSdk, { passive: true, once: true }); }); addEventListener("load", function () { window.requestIdleCallback ? requestIdleCallback(loadSdk, { timeout: 2500 }) : setTimeout(loadSdk, 1500); }, { once: true }); /* --------------------------------------------------------------------------- 3. Conversion id sha256(lowercase(email) + "|" + formId + "|" + ISO-hour), first 20 hex, "lead_" prefix. - email lowercased/trimmed so casing does not fork the id - formId keeps two different forms in the same hour from colliding - the hour bucket is only the server's fallback; the id computed here is sent to the relay verbatim, so the two paths cannot disagree even when a submit straddles an hour boundary Ads Manager's own sample omits event_id. We send it so the pixel event and the relay event collapse into one conversion instead of counting twice. ------------------------------------------------------------------------ */ function buildEventId(email, formId) { var seed = [ String(email || "").trim().toLowerCase(), String(formId || ""), new Date().toISOString().slice(0, 13) ].join("|"); return crypto.subtle.digest("SHA-256", new TextEncoder().encode(seed)).then(function (buf) { var hex = Array.prototype.map.call(new Uint8Array(buf), function (b) { return b.toString(16).padStart(2, "0"); }).join(""); return "lead_" + hex.slice(0, 20); }); } /* Guards a re-fire from the history.pushState that book-a-demo-core.js runs after submit, and from back-navigation onto the same state. */ function firedOnce(id) { try { if (sessionStorage.getItem("oaiq_" + id)) return true; sessionStorage.setItem("oaiq_" + id, "1"); return false; } catch (e) { return false; /* private mode / blocked storage: fire rather than lose it */ } } /* Path 2. keepalive lets the request finish even if the page navigates away. Failures are swallowed deliberately: a reporting call must never surface an error to someone who just submitted a form. The relay hashes the email server-side into user.emails_sha256 and adds the caller's IP and user agent, so this event carries the same match signals the pixel gets for free. */ function sendToRelay(email, formId, eventType, eventId) { try { var payload = { token: RELAY_TOKEN, email: email, form_id: formId || "", event_type: eventType, event_id: eventId, /* verbatim, so both paths always match */ source_url: location.href }; var oppref = getOppref(); if (oppref) payload.oppref = oppref; var externalId = getExternalId(); if (externalId) payload.external_id = externalId; fetch(RELAY_URL, { method: "POST", keepalive: true, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }).catch(function () {}); } catch (e) {} } /* Capture the email while the form is still on the page. onFormSubmitted does not reliably carry submissionValues, and by then the form may be removed. */ var lastEmail = ""; document.addEventListener("submit", function (e) { var f = e.target; if (!f || !f.querySelector) return; var i = f.querySelector('input[name="email"], input[type="email"]'); if (i && i.value) lastEmail = i.value; }, true); /* Every HubSpot form on the site reports through this one message event, so no module or theme JS needs editing to add coverage. */ window.addEventListener("message", function (e) { var d = e.data; if (!d || d.type !== "hsFormCallback" || d.eventName !== "onFormSubmitted") return; var email = (d.data && d.data.submissionValues && d.data.submissionValues.email) || lastEmail; if (!email) return; /* without an email there is no stable id to dedupe on */ var eventType = EVENT_BY_FORM[d.id] || EVENT_DEFAULT; buildEventId(email, d.id).then(function (id) { if (firedOnce(id)) return; oaiq("measure", eventType, { type: DATA_TYPE }, { event_id: id }); /* path 1 */ sendToRelay(email, d.id, eventType, id); /* path 2 */ }); }); })();

91ÊÓÆµ

Assess Your Post-Purchase Brand Readiness Start Assessment

Home > Blog >

6 Post-Purchase Metrics Every DTC Brand Owners Should Track

6 Post-Purchase Metrics Every DTC Brand Owners Should Track

Manjusha Pal
By Manjusha Pal
Sathish Loganathan
Reviewed by This article has been thoroughly reviewed, fact-checked, and compiled using comprehensive, up-to-date information provided by ClickPost — a trusted authority in logistics and eCommerce shipping solutions. Our editorial process ensures accuracy, relevance, and reliability for our readers. Sathish Loganathan

Ready to delight customers after checkout?

Trusted by 600+ brands

In this blog

    TL;DR Summary

    DTC margins have compressed from 35% to 22% since 2021, making post-checkout customer behavior a make-or-break factor for profitability. Six measurable metrics now separate brands that retain buyers from those funding growth purely through paid acquisition.

    • Repeat Purchase Rate – reveals if new buyers ever return at all

    • Time to Second Purchase – aligns email flows to peak repurchase intent

    • LTV:CAC Ratio – flags when acquisition math stops working

    • Post-Purchase NPS – isolates fulfillment problems from brand problems

    • Cohort vs. Blended Averages – exposes hidden gaps in customer journey timing

    • Retention vs. RPR Distinction – prevents misreading loyal base as new growth

    Post-purchase metrics look harmless until the numbers stop adding up. Revenue may still be growing, yet repeat orders have slowed, returns have crept up, and support is fielding more delivery questions.

    Paid acquisition has made that gap harder to absorb. Median DTC contribution margin fell from around 35 percent in 2021 to roughly 22 percent by 2025. Far less margin now remains after the first order.

    But once checkout is done, what happens next can still be a blind spot. Founders have often had to rely on blended benchmarks, Shopify Analytics reports that show averages instead of medians, and return data that ends at the refund without showing whether the customer came back.

    That’s why this guide follows six metrics from that first order onward: what they mean, when they should worry you, and which ones matter as the business grows.

    Why DTC Brand Owners Misread Post-Purchase Performance

    The problem is rarely a missing metric. More often, one of three reporting habits has bent the story.

    First, retention rate and repeat purchase rate have been treated as the same thing. Retention follows churn across active customers; RPR asks whether first-time buyers returned. If retention held while RPR stayed flat, fix the new-customer journey before the loyalty program.

    Second, blended averages can hide the repurchase window. The report may show, even when the median sits between 15 and 35. Build flows around that mean, and most repeat buyers could pass their highest-intent period before the next message lands.

    Third, satisfaction has often been read without revenue beside it. An NPS above 60 sounds strong. If post-return repurchase has fallen to 8 percent, though, the score has missed what customers did next.

    Retention Rate vs. Repeat Purchase Rate: Why the Difference Matters

    A brand might retain 90 percent of active customers and still convert only 22 percent of first-time buyers into a second order. One number says the loyal base stayed; the other asks whether anyone new joined it.

    The 6 Post-Purchase Metrics Every DTC Brand Owners Should Track

    For each metric, look at how it is calculated, what a healthy range looks like, when the number needs attention, and what you can do about it.

    1. Repeat Purchase Rate (RPR)

    Formula: (Customers who bought more than once ÷ Total customers) × 100.

    Benchmark: According to, the DTC average sits at 25 to 30 percent. Consumables reach 40 to 55 percent, beauty ranges from 30 to 40 percent, pet supplies average 30 to 35 percent, and luxury falls between 9 and 11 percent.

    Diagnostic trigger: If RPR has stayed below 20 percent despite healthy product reviews, check post-purchase email timing before changing the loyalty program.

    What the window tells you: Customers who came back within 60 days were roughly 3x more likely to become long-term customers. Build the early journey around that window.

    2. Time to Second Purchase (T2SP)

    Formula: Median number of days between Order 1 and Order 2, measured at cohort level, not blended average.

    Benchmark: According to, apparel and beauty customers typically place their second order within 15 to 27 days, while consumables take 27 to 68 days.

    Diagnostic trigger: Suppose your Klaviyo sequence fires on Day 7 and Day 30, while median T2SP lands on Day 18. That leaves a 10-day gap during the highest-intent window. Move the flow closer to the median.

    Tool note: Pull the number from Shopify Analytics under Customers > Returning customers. Cross-check it in Triple Whale’s cohort view if available. Shopify shows the mean; Triple Whale exposes the median.

    3. Customer Lifetime Value (CLV) and LTV: CAC Ratio

    Formula: CLV = Average Order Value × Purchase Frequency × Average Customer Lifespan. LTV: CAC Ratio = CLV ÷ Customer Acquisition Cost.

    Benchmark: Healthy , the business is marginally profitable. Below 1.5:1, the acquisition model does not work.

    Diagnostic trigger: If LTV: CAC has fallen below 3:1 while RPR remains above 25 percent, AOV may be holding it down. Review upsell performance before blaming loyalty.

    iOS 14 context: Since rolled out, platform-reported ROAS has overstated the true return by up to 2.3x (LayerFive, 2026). Read the blended MER beside LTV: CAC for a clearer view.

    4. Post-Purchase NPS

    Formula: % Promoters (9 to 10 score) minus % Detractors (0 to 6 score).

    Benchmark: A score of 30 to 50 is healthy for DTC. . Beauty and supplements have often scored 10 to 15 points above apparel because returns are lower.

    Diagnostic trigger: Post-purchase NPS focuses on delivery and unboxing. If it has been running 15 points below brand NPS, start with fulfillment and packaging.

    Timing: Send the survey 5 to 7 days after delivery. Brand NPS usually goes out after 90 days, so the scores capture different moments.

    5. Return Rate and Return Experience Quality

    Formula: (Number of orders returned ÷ Total orders placed) × 100.

    Benchmark by vertical: Apparel and footwear 30 to 40 percent. Beauty 10 to 15 percent. Supplements 5 to 8 percent. Home goods 15 to 25 percent. Electronics 8 to 15 percent.

    Read both sides: A return has two readings. Return rate shows the cost; experience quality shows whether it also cost the next purchase. An apparel brand at 35 percent with self-service may retain more customers than one at 20 percent with a poor process.

    Diagnostic trigger: Pair return rate with post-return repurchase rate. If fewer than 15 percent have bought again within 60 days, the return process is pulling down LTV.

    6. WISMO Rate (Where Is My Order?)

    Formula: (WISMO support tickets ÷ Total orders shipped) × 100.

    Benchmark: WISMO tickets have typically made up 20 to 40 percent of post-purchase support volume. With proactive tracking notifications, that share can fall below 10 percent.

    Revenue impact: By the time a customer asks where the order is, the brand has taken two hits. The ticket costs $3 to $5, while that customer is roughly 2x more likely to leave a negative review and 30 percent less likely to repurchase.

    Advanced calculation: Revenue per support ticket = Total revenue from first-time buyers who contacted support for WISMO ÷ Number of WISMO tickets. The result shows the revenue behind each unresolved inquiry.

    Which Post-Purchase Metrics to Track at Your Revenue Stage

    Your dashboard does not need to arrive fully built. Start with what you can act on, then add depth once order volume makes the data dependable.

    $0 to $1M: Start With Two Numbers

    At this stage, you only need RPR and post-purchase NPS. One shows whether first-time buyers came back; the other shows where the order experience may have stopped them. Shopify reports and a simple Klaviyo survey should be enough.

    $1M to $5M: Add Unit Economics

    Once order volume has grown, add LTV:CAC, category return rate, and T2SP. Cohort patterns will be more dependable. Use with Shopify Analytics. An attribution platform may help, but it is not urgent yet.

    $5M and Above: Build the Full Dashboard

    By this stage, add WISMO rate, survey attribution, revenue per support ticket, subscription conversion, and contribution margin by cohort. Triple Whale, , or can deepen the view. Missing the signal now costs more than the tooling.

    Post-Purchase Metrics Benchmarks by DTC Vertical

    Start with the row that matches your vertical. A blended DTC average may make a healthy number look weak, or let a real problem pass unnoticed.

    Metric Apparel Beauty Supplements/
    CPG
    Pet
    Supplies
    Home
    Goods
    Repeat Purchase
    Rate
    20 to 30% 30 to 40% 40 to 55% 30 to 35% 15 to 25%
    Return Rate 30 to 40% 10 to 15% 5 to 8% 5 to 10% 15 to 25%
    Post-Purchase
    NPS
    35 to 50 45 to 60 50 to 65 55 to 70 30 to 45
    LTV:CAC
    Target
    ≥3:1 ≥3.5:1 ≥4:1 ≥3:1 ≥2.5:1
    WISMO Rate
    (target)
    <15% <10% <8% <10% <15%
     
    Benchmarks are useful, but they rarely tell the whole story. AOV, fulfillment speed, and replenishment cycle can make the same repeat purchase rate mean very different things across brands.
     
    An 11 percent RPR may work well for a luxury brand with a $2,000 AOV, while a supplements brand with a 45 percent RPR and a $30 AOV may still have weaker unit economics. For a broader view across DTC categories, refer to.

    Your Post-Purchase Metrics Starter Checklist

    Before the next monthly review, work through this list so every metric has been measured the same way.

    • Pull repeat purchase rate from Shopify Analytics for the last 90 days.

    • Calculate median time to second purchase by cohort, not as a blended average.

    • Send post-purchase NPS 5 to 7 days after confirmed delivery.

    • Calculate LTV:CAC and compare it with the benchmark for your vertical.

    • Measure return rate by product category.

    • Track WISMO tickets as a percentage of orders shipped.

    • Add “How did you hear about us?â€� to the post-purchase survey.

    • Review all six metrics monthly and assign one owner.

    Where ClickPost Fits Into the Metrics Stack

    Once a metric has shown you the problem, the workflow has to do the repair.

    • Order Editing can let customers fix address or size errors before fulfillment, preventing avoidable returns and protecting post-purchase NPS.

    • Order Tracking and on-time notifications can send updates before an order-status question becomes a ticket, lowering WISMO and its support cost.

    • Post-Purchase Upsells raise AOV at order confirmation, which feeds into CLV and LTV: CAC.

    • Returns Protection and Exchanges give customers a clearer next step once a return starts and can retain revenue through exchanges. Together, these workflows cover the operational side of all six metrics through one integration layer.

    What Post-Purchase Metrics Say About Growth

    Taken together, these metrics show how much value survives after the first order. Repeat purchases shorten CAC payback, while delivery issues, returns, and support friction eat into contribution margin. Shopify recommends reviewing repeat purchase rate, LTV, payback period, and contribution margin together, while Bain has long connected stronger retention with higher profitability.

    You can see that impact in individual orders. Preventing a WISMO ticket lowers the cost of serving the customer. Converting a return into an exchange keeps revenue that may otherwise have been refunded.

    A relevant upsell adds more value without another acquisition cost. Shopify similarly notes that exchanges can retain and potentially increase revenue. After all, the first order creates growth. What happens after it determines how much of that growth compounds.

    Post-Purchase Metrics: Frequently Asked Questions

    What are post-purchase metrics?

    Post-purchase metrics tell you what happened after an order was placed. The core set covers RPR, T2SP, CLV, post-purchase NPS, return rate, and WISMO rate.

    What is a good repeat purchase rate for DTC brands?

    For most DTC brands, 25 to 30 percent is healthy. Consumables may reach 40 to 55 percent, apparel often lands at 20 to 30 percent, and luxury at 9 to 11 percent. Use your vertical.

    How do you measure post-purchase customer experience?

    Use post-purchase NPS 5 to 7 days after delivery, WISMO as a share of shipped orders, and post-return repurchase rate. Together, they cover delivery, support, and returns.

    What is time to second purchase and why does it matter?

    Time to second purchase is the median gap between a customer’s first and second order. Buyers who came back within 60 days were roughly 3x more likely to become long-term customers.

    How do you calculate customer lifetime value for ecommerce?

    Start with CLV = Average Order Value × Purchase Frequency × Average Customer Lifespan. Then divide by CAC. A healthy DTC LTV:CAC ratio should be at least 3:1.

    What is post-purchase NPS and how is it different from brand NPS?

    Post-purchase NPS usually goes out 5 to 7 days after delivery and captures fulfillment and unboxing. Brand NPS comes later and reflects broader sentiment.

    What metrics should a DTC brand track after iOS 14?

    Add post-purchase survey attribution, including “How did you hear about us?� Since iOS 14, platform ROAS can overstate return, so read blended MER and zero-party data beside it.

    How do you use cohort analysis to measure post-purchase retention?

    Group customers by first purchase date, then compare 30, 60, and 90-day RPR across acquisition periods. You will see whether newer cohorts improved or weakened.

    The Post-Purchase Experience Platform

    Momentum Leader - G2 Users Most Likely To Recommend - G2 Best Results Mid-Market - G2
    . window.disableHubSpotCookieBanner = true; function killNative() { var n = document.getElementById("hs-eu-cookie-confirmation"); if (n && n.parentNode) { n.parentNode.removeChild(n); return true; } return false; } killNative(); if (window.MutationObserver) { var mo = new MutationObserver(function () { killNative(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); setTimeout(function () { mo.disconnect(); }, 15000); } var COOKIE = "cp_cookie_consent"; var EU = ["at","be","bg","hr","cy","cz","dk","ee","fi","fr","de","gr","hu","ie","it","lv","lt","lu","mt","nl","pl","pt","ro","sk","si","es","se","gb","uk","is","li","no","ch"]; function readCookie(nm) { var m = document.cookie.match(new RegExp("(?:^|; )" + nm + "=([^;]*)")); return m ? decodeURIComponent(m[1]) : null; } function writeCookie(nm, val, days) { var d = new Date(); d.setTime(d.getTime() + (days * 864e5)); document.cookie = nm + "=" + encodeURIComponent(val) + ";expires=" + d.toUTCString() + ";path=/;SameSite=Lax" + (location.protocol === "https:" ? ";Secure" : ""); } function stored() { var raw = readCookie(COOKIE); if (!raw) return null; try { var o = JSON.parse(raw); if (typeof o.analytics === "boolean") return o; } catch (e) {} return null; } function apply(state, revoke) { window._hsp = window._hsp || []; window._hsp.push(["setHubSpotConsent", { analytics: !!state.analytics, advertisement: !!state.advertisement, functionality: !!state.functionality }]); if (revoke && !state.analytics && !state.advertisement && !state.functionality) { window._hsp.push(["revokeCookieConsent"]); } window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "cp_cookie_consent", cp_consent_analytics: !!state.analytics, cp_consent_advertisement: !!state.advertisement, cp_consent_functionality: !!state.functionality }); document.documentElement.setAttribute("data-cp-consent", "set"); } function inScope(banner) { if (banner.getAttribute("data-scope") !== "eu_only") return true; var geo = (document.body.getAttribute("data-geo_location") || "").toLowerCase(); if (!geo) return true; return EU.indexOf(geo) !== -1; } function init(banner) { var days = parseInt(banner.getAttribute("data-days"), 10) || 180; var prefs = banner.querySelector("[data-cpcb-prefs]"); var compact = banner.querySelector('[data-cpcb-actions="compact"]'); var expanded = banner.querySelector('[data-cpcb-actions="expanded"]'); var toggles = banner.querySelectorAll("[data-cpcb-toggle]"); function show() { banner.hidden = false; requestAnimationFrame(function () { banner.classList.add("is-visible"); }); } function hide() { banner.classList.remove("is-visible"); setTimeout(function () { banner.hidden = true; }, 300); } function collapse() { if (!prefs) return; prefs.hidden = true; if (expanded) expanded.hidden = true; if (compact) compact.hidden = false; } function expand() { if (!prefs) return; prefs.hidden = false; if (compact) compact.hidden = true; if (expanded) expanded.hidden = false; } function paint(state) { Array.prototype.forEach.call(toggles, function (t) { var key = t.getAttribute("data-cpcb-toggle"); if (key === "necessary") { t.setAttribute("aria-checked", "true"); return; } t.setAttribute("aria-checked", state[key] ? "true" : "false"); }); } function fromToggles() { var state = { analytics: false, advertisement: false, functionality: false }; Array.prototype.forEach.call(toggles, function (t) { var key = t.getAttribute("data-cpcb-toggle"); if (key in state) state[key] = t.getAttribute("aria-checked") === "true"; }); return state; } function commit(state, revoke) { writeCookie(COOKIE, JSON.stringify({ analytics: !!state.analytics, advertisement: !!state.advertisement, functionality: !!state.functionality, v: 1, ts: Date.now() }), days); apply(state, revoke); hide(); } Array.prototype.forEach.call(toggles, function (t) { if (t.disabled) return; t.addEventListener("click", function () { t.setAttribute("aria-checked", t.getAttribute("aria-checked") === "true" ? "false" : "true"); }); }); banner.addEventListener("click", function (e) { if (!e.target || !e.target.closest) return; var btn = e.target.closest("[data-cpcb-action]"); if (!btn) return; var action = btn.getAttribute("data-cpcb-action"); if (action === "customise") { expand(); return; } if (action === "accept") { commit({ analytics: true, advertisement: true, functionality: true }, false); return; } if (action === "reject") { commit({ analytics: false, advertisement: false, functionality: false }, true); return; } if (action === "save") { commit(fromToggles(), true); return; } }); var existing = stored(); if (existing) { apply(existing, false); paint(existing); collapse(); } else if (!inScope(banner)) { // Out of the consent-required region: implied consent, banner never shown. commitSilently(); } else { collapse(); show(); } function commitSilently() { var all = { analytics: true, advertisement: true, functionality: true }; writeCookie(COOKIE, JSON.stringify({ analytics: true, advertisement: true, functionality: true, v: 1, ts: Date.now(), implied: true }), days); apply(all, false); } // Public re-open hook for a footer "Cookie settings" link. window.cpCookieBanner = window.cpCookieBanner || {}; window.cpCookieBanner.open = function () { var s = stored() || { analytics: false, advertisement: false, functionality: false }; paint(s); expand(); show(); }; } function boot() { var banners = document.querySelectorAll("[data-cpcb]"); if (!banners.length) return; init(banners[0]); document.addEventListener("click", function (e) { if (!e.target || !e.target.closest) return; var trigger = e.target.closest("[data-cp-cookie-settings]"); if (!trigger) return; e.preventDefault(); if (window.cpCookieBanner && window.cpCookieBanner.open) window.cpCookieBanner.open(); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", boot); } else { boot(); } })();