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 */
});
});
})();
Answer 4 questions to see your RTO cost, how you compare with your category, and what to fix first.
Question 1 of 4
What do you sell?
Pick the closest match.
₹545 cr
came back as RTO last festive season Across brands on ClickPost, Oct to Nov 2025
Up to 16x
more RTO on cash orders than prepaid Fashion brands, Oct to Nov 2025
12%
of Tier 3 orders come back Against 6.6% from metros
ClickPost analysis of 35.8M orders, 1 October to 30 November 2025
Question 2 of 4
How many orders do you ship in your busiest month?
A rough number works.
All prepaidAll cash on delivery
Question 3 of 4
Where do you ship?
Set your current split. Smaller towns send back more orders.
6.6% come back
8.1% come back
11.5% come back
12.0% come back
Move one slider and the others adjust.
Question 4 of 4
What happens when a delivery fails?
Pick what your team does today.
Almost there
Your result is ready.
Enter your details to see it.
We'll use this to send your result, and to book a demo only if you ask.
Your busiest month · {category}
You could lose {total} to RTO in your busiest month.
That's about {orders_lost} orders that never reach the customer.
{cod_pct}% of it comes from cash on delivery.
Your RTO cost, split by payment type.
Cash on delivery—
{cod_rate}% come back. That's {cod_rto} of {cod_sent} orders.
Prepaid—
{pre_rate}% come back. That's {pre_rto} of {pre_sent} orders.
Cash orders come back {multiple}x as often as prepaid.
Where to start
Moving {shift}% of orders to prepaid could protect an estimated {savings}.
Move the slider to see an estimate.
Drag the slider to see how the estimate changes.
Today—
If {shift}% switch—
About {savings} protected (estimate)
This moves {shift}% of all orders, which is {moved} of {order_count}. It assumes switched orders spend the same and come back at your prepaid rate. Shipping costs are not included.
Two more things to know
These depend on your business, so we show the data instead of a cost.
Where you ship
µ÷³پ±±ð°ù³å°ù²¹³پ±´ǰ¨×
Orders to {last_tier} come back {tier_ratio}x as often as metro orders.
{far}% of your orders go to Tier 2 and beyond. Your RTO rate is about {diff}% higher than average.
{far}% of your orders go to Tier 2 and beyond. Your RTO rate is about {diff}% lower than average.
{far}% of your orders go to Tier 2 and beyond. Your RTO rate is in line with the average.
Delivery attempts
µ÷²Իå°ù³å°ù²¹³پ±´ǰ¨×
An order is {ndr_ratio}x more likely to come back after a third failed attempt than after the first.
1st attempt fails28%
3rd attempt fails81%
So, what next
What to fix first
Ranked by likely impact.
01.Get more customers to pay online
In {phrase}, cash orders come back {multiple}x as often as prepaid.
How to fixPayment nudges at checkout, and confirming cash orders before they ship
02.Call after the first failed attempt, not the third
28% come back after one failed attempt. 81% after three.
How to fixParth, ClickPost's AI voice agent, calls customers after a failed delivery.
03.Fix bad addresses before you ship
Tier 3 and beyond gets 42% of orders and sends back 12% of them.
How to fixAddress checking, and letting the customer fix their own
04.Ship each order with the courier most likely to deliver it
On-time delivery ranged from 44% to 59% across the network.
How to fixAI courier allocation picks the best-performing courier for each pincode.
We could lose an estimated {total} to RTO in our busiest month. {cod_pct}% of it comes from cash on delivery.
—From cash orders
—From prepaid orders
—Estimated, if {shift}% move to prepaid
How we calculated this
We used 35.8M orders shipped from 1 October to 30 November 2025, last year's festive season. Use your result to plan this year's peak season.
In {phrase}, {cat_cod_rate}% of cash orders and {cat_pre_rate}% of prepaid orders came back. We adjust both for where you ship, then apply them to your numbers. We keep cash and prepaid separate because an average hides the gap.
We adjust for where you ship using RTO rates by pincode tier: Metro 6.6%, Tier 1 8.1%, Tier 2 11.5%, Tier 3 and beyond 12%.
Costs shown are order value only. Shipping, packaging and unsold stock are not included, so your real cost is likely higher.
Customer returns after delivery are not included. We didn't have that data, so we left it out.
Your answer on failed deliveries doesn't change the numbers. The data doesn't link it to RTO rates.
"Something else" groups food, jewellery, gifting, pet, kids and household. Treat its benchmark as rough.
This is an estimate from category data and your answers. It is not a measurement of your business.
We use cookies to keep the site working, understand usage, and improve your experience. Learn more
Strictly Necessary
Always active for essential site performance and security.
Performance & Analytics
Enables anonymous usage analysis to refine our services.
Targeting & Advertising
Used to present relevant promotions and advertising.
.
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();
}
})();