Shopify
Install the Lapis pixel in theme.liquid, persist the click ID as a cart attribute, and send orders from the orders/create webhook.
Shopify locks down checkout, so the reliable pattern is: cookie, then cart attribute, then order note attribute, then webhook. Shopify carries cart attributes onto the order automatically, so you need no checkout customization.
1. Install the pixel
In Shopify admin, go to Online Store → Themes → ⋯ → Edit code → layout/theme.liquid. Add this before the closing </body> tag:
<script
src="https://cdn.trylapis.com/pixel/v1/lapis-pixel.js"
data-key="pk_YOUR_PIXEL_KEY"
></script>
2. Persist the click ID as a cart attribute
Directly below the pixel script, add:
<script>
(function () {
function getLapisClickId() {
var cookie = document.cookie.split("; ").find(function (entry) {
return entry.indexOf("lapis_sid=") === 0;
});
if (!cookie) return null;
try {
var session = JSON.parse(
decodeURIComponent(cookie.split("=").slice(1).join("="))
);
return session.sid || null;
} catch (e) { return null; }
}
var clickId = getLapisClickId();
if (!clickId) return; // organic visitor, so do nothing
fetch("/cart/update.js", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ attributes: { lapis_click_id: clickId } }),
}).catch(function () {});
})();
</script>
This is idempotent, so it is safe to run on every page view. The attribute lands on the order as note_attributes.lapis_click_id.
3. Send orders from the webhook
Create an orders/create webhook that points at your backend (Settings → Notifications → Webhooks, or via the Admin API), then:
// POST /webhooks/shopify/orders-create
app.post("/webhooks/shopify/orders-create", async (req, res) => {
res.sendStatus(200); // ack Shopify first, because it retries slow responses
const order = req.body;
const attrs = Object.fromEntries(
(order.note_attributes || []).map((a) => [a.name, a.value])
);
if (!attrs.lapis_click_id) return; // organic order, so skip
await fetch("https://api.trylapis.com/api/v1/pixel/server-events", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.LAPIS_SERVER_EVENT_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
event_id: `order_created:${order.id}`,
event_type: "order_created",
lapis_click_id: attrs.lapis_click_id,
value: parseFloat(order.total_price),
currency: order.currency || "USD",
properties: { order_number: String(order.order_number) },
}),
});
});
Tip: Shopify retries webhooks aggressively. Keying
event_idtoorder.idmakes every retry deduplicate, so you never double-count.
Shopify-specific notes
- Republish after editing the theme. Changes to
theme.liquidon the live theme go out immediately, but if you edit an unpublished theme, the live one stays unchanged. checkout_started: use thecheckouts/createwebhook withevent_id: "checkout_started:" + checkout.token. The cart attribute is already on the checkout object.- Shopify Plus and checkout extensibility: the cart-attribute pattern works unchanged. You do not need
checkout.liquidaccess. - Headless Shopify (Hydrogen): treat it as custom code. Set the attribute via the Storefront API
cartAttributesUpdatemutation instead of/cart/update.js. - No app required: this is intentionally a theme-code integration. There is no Lapis Shopify app to install or update.
Full event details: Ecommerce and Shopify events.