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

ROI Calculator · Protection

Make shipping protection pay for itself

Calculate recovered revenue from lost, damaged, and stolen orders. See what shipping protection adds to your margin with ClickPost's ROI calculator. 

1. Tell us about your business

Defaults are typical benchmarks. Change any value to fit your business.

2. Fine-tune the impact assumptions

Defaults are ClickPost platform benchmarks. Leave them for a conservative estimate, or match your own data.

How it works

Offer shipping protection in four steps

A small checkout add-on funds its own claims, so lost, damaged and stolen parcels stop coming straight off your margin.

div.cp-roi__hvis (8)

Add it at checkout

A small, optional fee the shopper can opt into as they pay. Low friction, clear value.

div.cp-roi__hvis (9)

Build a resolution pool

Those fees collect into a pool that pays for parcels lost, damaged or stolen in transit.

div.cp-roi__hvis (10)

Resolve claims fast

Approve, then reship or refund, funded by the pool instead of straight off your margin.

div.cp-roi__hvis (11)

Keep the shopper

A quick, funded fix turns a bad delivery into a reason to trust you and order again.

Cost anatomy

Eight factors that determine shipping-loss cost

These drive how much delivery incidents cost you, and how much protection can offset.
Issue rate

Issue rate

The share of shipments that end up lost, damaged or stolen. Your exposure starts here.

Resolution cost

Resolution cost

What a reship or refund actually costs per claim, product plus postage plus handling.

Attach rate

Attach rate

The share of orders that opt into protection. It sets the size of the fee pool.

Protection fee

Protection fee

The price of the add-on and the margin it carries after payouts.

Claim rate on protected orders

Claim rate on protected orders

How often protected orders actually file. It decides how far the pool stretches.

Fraud

Fraud and abuse

Illegitimate claims that drain the pool without a real incident behind them.

Value at risk per order

Value at risk per order

Higher order value means higher exposure on every incident that does happen.

Retention after resolution

Retention after resolution

Whether a shopper you fixed comes back. This is where a claim quietly turns positive.

The math

ROI calculation formulas

Transparent methodology. These are the formulas the calculator runs. The benchmark constants inside them come from ClickPost platform data, and you can change every rate in the advanced panel.
Step 1
Protection fees collected
protectedOrders × fee

Gross revenue from shoppers who opt into protection, where protectedOrders = N × attach.

Step 2
Claims self-funded
protectedOrders × issue × resolutionCost

Loss and damage payouts, now covered by the fee pool instead of your P&L.

Step 3
Net protection margin
fees − claims

What is left of the fee pool once resolutions are paid.

Step 4
Retained-shopper revenue
claims × retentionLift × AOV

Repeat orders from shoppers whose claim was resolved cleanly.

Total ROI
net margin + retained revenue
N = annual orders · AOV = average order value · attach = attach rate · fee = protection fee · issue = shipment issue rate · retentionLift = post-claim repeat lift

Powering post-purchase for 600+ brands worldwide

  • GOPURE

    ClickPost helped us create a return experience that fit our business instead of forcing us to fit the platform. They customized our workflows without additional costs or developer support. The automation they built for our returns, freed up 5 hours a week.

    Sandy Vangyi
    Sandy Vangyi
    Director of CX, GOPURE
  • BG

    We’ve been using Clickpost and it’s genuinely been super helpful. The post-purchase upsells have been a solid bonus for incremental revenue.

    Samarth Sindhi
    Samarth Sindhi
    Chief Executive Officer, MarsGHC
  • VanillaPura - BG

    Great experience and fantastic support from ClickPost. This immediately cut our top post-purchase support tickets, making ROI easy to quantify. Plus, the customization features and upsell tools help increase AOV. Highly recommend.

    Paul Fulton
    Paul Fulton
    VanillaPura
High Performer  Mid Market - Winter 2026
Highest User Adoption - Mid Market - Winter 2026
Momentum Leader - Winter 2026
User Loves Us
High Performer  Mid Market - Winter 2026
Highest User Adoption - Mid Market - Winter 2026
Momentum Leader - Winter 2026
User Loves Us

4.8 ★

out of 5 stars on G2.com

Users Love Us - G2 Winter 2026
Momentum Leader - Winter 2026
Highest User Adoption - Winter 2026
High Performer Mid-Market - Winter 2026
Users Love Us - G2 Winter 2026
Momentum Leader - Winter 2026
Highest User Adoption - Winter 2026
High Performer Mid-Market - Winter 2026
Users Love Us - G2 Winter 2026
Momentum Leader - Winter 2026
Highest User Adoption - Winter 2026
High Performer Mid-Market - Winter 2026
Users Love Us - G2 Winter 2026
Momentum Leader - Winter 2026
Highest User Adoption - Winter 2026
High Performer Mid-Market - Winter 2026

4.8 ★

out of 5 stars on G2.com

5B+

shipments automated per year

Post-purchase that plugs into
your existing stack

  • 600+ Carrier Partners
  • 75+ Communication Gateways
  • 50+ Storefronts, OMS, WMS
  • Shopify
  • Magento
  • Klaviyo
  • Attentive
  • Shipbob
  • Shiphero
  • UPS
  • USPS

It estimates the money you lose to failed, lost, stolen, and damaged deliveries, and how much of that ClickPost helps you avoid by catching delivery problems earlier. Shipping issues drive a large share of refund requests, so this is often a bigger number than teams expect.

Your monthly orders, average order value, the share of shipments that hit a delivery problem, and what a lost or disputed order costs you to resolve. Conservative defaults are provided for the rates you don't track yet.

They use US delivery-loss and package-theft data, which matters because porch piracy and delivered-not-received disputes run high in the US market. As with the other tabs, the defaults sit at the low end and you can enter your own issue rate.

ClickPost works on the operations side, not the insurance side. It verifies addresses before dispatch, manages delivery exceptions and failed deliveries in real time, and flags at-risk shipments early, so fewer orders turn into lost-package claims and refunds in the first place.

It sits alongside them. ClickPost reduces the number of exceptions that become claims, while your protection or insurance provider still covers the ones that get through. You keep your current coverage and use ClickPost to lower how often you need it.

Both. You recover margin on orders that would have been refunded or reshipped, and you cut the support time spent on lost and delayed package tickets. The calculator reports the avoided cost so you can weigh it against current claim and refund spend.

. 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(); } })();