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 */
});
});
})();
Introducing ClickPost Ally. An agentic AI shopping and support system for D2C brands on Shopify. Sells, resolves and remembers context across all your channnels.
Built on the ClickPost shipment intelligence layer that powers 50M+ orders a month
Other agents read your help center. Ally reads the carrier event, the order state, and every prior conversation. The answer is the truth, not a paraphrase.
01 / Personal from line one
Opens the way your brand would, if it remembered every customer.
Ally knows the customer's order, their last conversation, and what they were browsing. Greetings land. Recommendations feel earned.
โ
customer history recalled instantly
1st
message already knows who you are
02 / Post-Purchase Native
Live shipment state, not cached summaries.
90% of post-purchase questions are logistics. Ally reads ClickPost's live carrier feed, delivery attempts, and delay signals the moment a customer asks.
90%
of post-purchase
questions are logistics
Live
carrier feed,
not cached data
03 / Omnichannel
One brain across every channel.
Chat, WhatsApp, email, voice. Same memory, same brand voice, same authorization. The customer never starts over. The agent never forgets what was promised.
All
channels,
one shared memory
0x
context lost
between channels
04 / Strict authorization
Never invents an exception.
Ally cites your policy doc. When a request moves outside its authorization, it hands off to a human with the full thread attached. No guessing, no improvising.
0
unauthorized
exceptions made
100%
full context handed
off on escalation
Channels
One conversation. Every surface
Watch one thread carry forward: a recommendation on web chat, a price-drop alert on WhatsApp two hours later, an order placed, a delivery tracked, a cross-sell landed. Same Ally, same context.
Web & In-App Chat
A
Ally ยท Lume
Typically replies instantly
Today
Type a messageโฆ
Powered by Ally ยท ClickPost
Web Chat
2:14 PM
Same thread
WhatsApp
4:23 PM
1 brainfull context
WhatsApp
S
Kaavi
online
Today
๐
Message
๐๐ีส
Email
Threaded replies in your brand voice
"Refund of $74 processed. Hits your card in 5 business days."
Inbound Voice
Picks up, verifies, resolves on the line
"Hi Priya. I can see the order. What can I do further?"
Outbound Voice
Calls back with context, not a script
"Your courier is 15 min away. Excited for your new suede jacket?"
Human Handoff
Full thread, full state, no tab switching
"Escalated. Priya on chat. Order + history attached."
A ClickPost Framework
The Resolution Envelope
The seven conditions every ClickPost agent holds before it goes live. Hold one, ship a chatbot. Hold all seven, ship a support team.
Click any condition
THE ENVELOPE
where competence holds
Baseline
Outer = harder
Stakes โ
Pressure โ
Capabilities
The full funnel. End to end
Ally isn't a chatbot that does FAQs. It runs the whole conversation, from the first product question to the second order.
A
Kaavi
Business account
Today
๐
Message
๐ีส
A
Kaavi
Business account
Today
๐
Message
๐ีส
A
Kaavi
Business account
Today
๐
Message
๐ีส
A
Kaavi
Business account
Today
๐
Message
๐ีส
Pre-purchase
Recommends like a store associate who has read every review.
SKU recommendations grounded in your Shopify catalog. Reads stock, sizing, fit, ingredients, compatibility. Surfaces the right product, not the most-clicked one.
2x
conversion when shoppers get pre-purchase answers real time
3/4
shoppers want help finding the right product
Sample
"If you liked the relaxed linen shirt, the oversized version drops Friday in your size."
Post-purchase
Resolves shipments from the source of truth.
WISMO, NDR resolution, returns, refunds, replacements. Live carrier feed, every integration. No screenshots, no escalations for routine tracking.
90%
WISMO queries resolved without human
6.4s
average resolution time
Sample
"Rohit from Swiftship has your package. ETA 2 to 4 PM. Want me to ping you when he is 10 minutes out?"
Policy
Quotes your policy doc. Never improvises.
Returns, refunds, discounts, warranty, loyalty. Cites the rule, gives the exact window, routes edge cases to a human with full context attached.
0
unauthorized exceptions made
100%
of responses cite policy source
Sample
"Your return window closes on Nov 18. We have a 20% off on variant exchanges."
Re-engagement
Upsells at the right moment.
When a package is delivered. When a customer returns asking a question. When a browsed product goes on sale. Surfaces the next conversation, not a generic promo.
5x
cheaper to retain a customer than acquire one.
~70%
probability of selling to an existing customer. Ally knows exactly when to ask.
Sample
"Your gold hoops were delivered yesterday. The matching cuff is back in stock if you wanted to pair them."
Ally is an agentic AI shopping and support system for Shopify brands. It answers pre-sale questions driving conversions, resolves shipment queries, explains your store policies; across chat, WhatsApp, email, and voice. One memory across every channel.
A chatbot answers from a static knowledge base. Ally is agentic: it reads live shipment data, your Shopify catalog, and your policies in real time, remembers every conversation across channels, and personalizes every reply to the customer in front of it. A chatbot says "check your tracking link." Ally says "your package is out for delivery between 2 and 5pm.
Chat, WhatsApp, email, and voice. The same conversation continues across every channel. Your customers never have to repeat themselves.
Yes. Ally syncs your full catalog including SKUs, variants, stock, descriptions, discounts and reviews. Recommendations are always grounded in what's actually available.
150+ languages. Ally auto-detects the customer's language and switches mid-conversation while preserving your brand tone and regional nuance.
Merchants are live within a day. Connect Shopify, configure your policies, and Ally is ready to take conversations.
4.8
4.4
Your customers shouldn't have to wait. Or repeat themselves. Test Ally on your store and shipment data.
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();
}
})();