Sales Tracking
Sales tracking is the link between what Clerk shows visitors and the orders they place. Without it, Clerk cannot learn from real purchases, personalise recommendations, or show accurate revenue attribution in the dashboard.
This article explains how tracking works at the API level, how to set it up, and how to debug it when orders are not being registered correctly.
How It Works #
Every tracked order in Clerk goes through a chain of three connected pieces.
Visitor ID #
When a visitor arrives on the webshop, your server generates an anonymous session ID and includes it in every API call made during that session. This is what allows Clerk to connect a visitor’s browsing activity — the products they saw and clicked — to the order they eventually place.
In a Clerk.js setup, the visitor ID is generated and managed automatically. In a direct API setup, you are responsible for creating it, storing it (e.g. in a session variable), and passing it consistently across all calls.
Labels #
Every API call that returns results — search matches, recommendations, email results — must include a labels parameter. This is an array containing at least one descriptive string, like ["Homepage - Trending"] or ["Search - Predictive"], that identifies which element showed those products.
When a visitor clicks a product and later places an order, Clerk uses the label from the click to trace the sale back to the element that drove it. This is the basis for revenue attribution in analytics.
Labels that are missing, identical across all elements, or too generic make it impossible for Clerk to distinguish which element influenced a sale. This is one of the most common reasons attribution looks wrong or is missing entirely.
Sale Call #
When the visitor places an order, a POST request to log/sale ties the order to the visitor ID. Clerk can now connect the full chain: this visitor was shown products by element X (the label), clicked one, and completed a purchase.
For this chain to work, each click on a product shown by Clerk must also be logged via log/click. In a Clerk.js setup this happens automatically. In an API setup, you must call log/click each time a visitor clicks a Clerk product — see the
API guide for details.
This chain — label → click → sale — is what powers both personalisation and the revenue analytics in my.clerk.io.
What Clerk Stores #
Each tracked sale contains:
- A unique sale ID
- Product IDs, quantities, and unit prices
- The visitor ID (links the sale to their session)
- Customer email and customer ID — optional, enables cross-session personalisation
Setup #
Place the sales-tracking call on the order confirmation page, which loads once after a successful purchase. Do not place it on the cart page or at any other point in the checkout flow.
API #
Make a POST request to log/sale from your server when an order is completed. Pass the same visitor ID that was used throughout the session.
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"key": "YOUR_PUBLIC_API_KEY",
"sale": 567,
"products": [
{"id": 12793, "price": 99.95, "quantity": 2},
{"id": 1546, "price": 14.00, "quantity": 1}
],
"email": "luke@skywalker.com",
"customer": 1234,
"visitor": "abc123"
}' \
https://api.clerk.io/v2/log/sale
price is always the unit price of a single item. Clerk multiplies it by quantity internally when calculating revenue. Do not send the line total.
See the API guide for full details on generating and persisting visitor IDs across a session.
Clerk.js #
Clerk.js handles the visitor ID automatically and makes the log/sale call for you. Add the following snippet to your order confirmation page and replace the placeholder values with your platform’s variables:
<span
class="clerk"
data-api="log/sale"
data-sale="ORDER_ID"
data-email="CUSTOMER_EMAIL"
data-customer="CUSTOMER_ID"
data-products='[{"id": 12, "quantity": 1, "price": 99.95}, {"id": 54, "quantity": 2, "price": 9.50}]'>
</span>
Clerk.js picks up the snippet on page load and sends the sale data to the API, using the same visitor ID it has been tracking throughout the session.
Verifying It Works #
Check these three places after placing a test order.
Developers > Logs — Any errors from the tracking call appear here in real time. This is the fastest place to spot a misconfigured request.
Data > Orders — Confirmed tracked orders appear here. Click an order to see which products were tracked and whether Clerk can identify them. If a product shows a placeholder image, the product ID in the sale call did not match any product in your catalogue.
Data > Visitors — Search for a visitor ID to inspect their full session: which products they viewed, which Clerk elements they clicked, and which orders they placed. In a Clerk.js setup, you can force a specific visitor ID during testing by adding ?clerk_visitor=VISITOR_ID to any page URL — Clerk.js will use that ID for the session, making it easy to trace a complete test flow end to end.
The Health dashboard shows a top-level status indicator for sales tracking. A red status means tracked orders have not been received recently.
Debugging #
Follow these steps when sales are not being tracked correctly.
Check Logs #
Go to Developers > Logs and look for any errors from the log/sale endpoint. This applies to both API and Clerk.js setups — any rejected request shows up here immediately with an error message explaining what was wrong.
For Clerk.js setups, also run Clerk("debug") in the browser console before placing a test order. This activates debug mode, which causes Clerk.js to log detailed information about every tracking call it makes — including the visitor ID being used, the labels attached to result calls, and any errors encountered. Look for entries prefixed with [Clerk] in the console output.
Verify API Response #
The log/sale endpoint always returns a response. A successful one looks like this:
{"status": "ok"}
A failed one returns an error with a description of what went wrong, for example:
{"status": "error", "error": "Missing required argument: sale"}
Read the error message — it usually points directly to the problem.
API setup: Log the raw HTTP response from the log/sale call in your server-side code. If you are not already doing this, test the call directly with a known-good payload:
curl -X POST \
-H 'Content-Type: application/json' \
-d '{
"key": "YOUR_PUBLIC_API_KEY",
"sale": 999,
"products": [{"id": 12793, "price": 99.95, "quantity": 1}],
"visitor": "test-visitor-1"
}' \
https://api.clerk.io/v2/log/sale
If this succeeds but production calls do not, compare the two payloads closely — the difference will reveal the problem.
Clerk.js setup: Open browser DevTools, go to the Network tab, and filter for clerk. Place a test order and look for a request starting with sale?key=.... Click it and inspect the response there.
Check Request Parameters #
If orders are not tracking or not being attributed correctly, verify the following across your setup:
API key — Confirm key matches the Public API Key found in Developers > API Keys. A wrong key returns an authentication error immediately.
Visitor ID — Check that visitor is present and that it is the same value used in the result-returning calls earlier in that session. If you generate a new ID on each request, or store it somewhere that gets cleared before checkout, the ID in the sale call will not match the one attached to the clicks.
Labels — Confirm that every call returning results earlier in the session included a labels array with a unique label for each element. If labels are missing or identical across elements, Clerk records the sale but cannot attribute it to any specific element.
Product IDs — Verify that the IDs in the products array match the IDs Clerk has in its catalogue. Search for one of the IDs in Data > Products to confirm. If you cannot find it, you are likely sending variant IDs or SKUs instead of parent product IDs.
Sale ID — Confirm the sale value is unique per order and is not being reused across requests. Clerk silently ignores duplicate sale IDs.
No Call Made #
API setup: The call is not firing from your server. Check your order completion handler — confirm it is actually being reached after a successful purchase and that no exception is silently suppressing the tracking call. Add temporary logging around the log/sale call to confirm it executes.
Clerk.js setup: The Clerk.js library is not running on the confirmation page. Open the browser console and type Clerk. If you see the following error, the library was not loaded:
Uncaught ReferenceError: Clerk is not defined
Confirm that the Clerk.js tracking script is present in the confirmation page template and is not blocked by another script or a cookie consent tool.
No Clerk Impact #
If the sale call succeeds but no orders show as Clerk-influenced in analytics, Clerk recorded the order but could not connect it to a session or attribute it to an element.
log/click not being called — The most common cause. Clerk cannot attribute a sale to an element unless a click was logged before the purchase. In a Clerk.js setup, click tracking is automatic — confirm the library is loaded on the pages where Clerk results are shown. In an API setup, verify that your code calls log/click every time a visitor clicks a product shown by Clerk, and that the call is actually reaching the API (check your server logs or the response).
Missing or invalid labels — Check that every result-returning call and every log/click call includes a valid labels array. If labels are missing, empty, or contain only whitespace, Clerk cannot determine which element the visitor interacted with and the sale will not be attributed.
API setup: Confirm that the visitor ID in the log/sale call exactly matches the one sent in the log/click call when the visitor clicked the product. Even a small difference — a different format, an extra character, or a regenerated ID — breaks the link.
Clerk.js setup: Visitor IDs are managed automatically, but the link can break if the tracking script is absent from one or more pages in the checkout flow. Check that Clerk.js is loaded on every page between the product page and the confirmation page.
Common Mistakes #
These are the most frequent causes of sales tracking failing or attribution not working correctly.
Invalid Product Syntax #
Product IDs must be sent as the same type used in your data feed. If you use integers there, they must be integers in the sale call too — not strings. Sending "id": "123" when Clerk expects "id": 123 causes the call to fail.
The products value must also be valid JSON. Escaped or double-encoded strings like "products": "[{\"id\":\"123\"}]" will not be parsed correctly.
Missing Arguments #
The key, sale, and products arguments are all required. Omitting any of them returns an error. The products array must contain at least one item.
Clerk.js Not Loaded #
If the Clerk.js library is missing from the confirmation page, or is blocked from running, no sale call is made. Two common causes:
- The tracking script is not included in the confirmation page template.
- A cookie consent tool (such as iubenda in automatic blocking mode) is blocking
cdn.clerk.ioorapi.clerk.io. Allowlist both domains so tracking is not silently dropped when visitors decline non-essential cookies.
Wrong Product IDs #
Track the parent product ID, not the variant ID. If a visitor buys a red, size M t-shirt, track the parent ID — not the ID of that specific variant. The same ID must be used in both click-tracking and the sale call. Mismatched IDs break attribution even when both calls succeed.
Visitor ID Changes #
In an API setup, the visitor ID must stay the same from the first page view through to the sale call. If you regenerate the ID on each page load, the click and the sale are associated with different sessions and Clerk cannot connect them.
In a Clerk.js setup this is handled automatically, but it can break if the tracking script is missing from one or more pages in the checkout flow.
Unit Price Mismatch #
price in the products array must be the unit price of a single item. Clerk multiplies it by quantity to calculate revenue. Sending a pre-multiplied line total produces inflated revenue figures in analytics.
Re-used Sale IDs #
Clerk stores only the first call for a given sale ID. If the order confirmation page can be refreshed or revisited, the tracking call fires again with the same sale ID. The second call is silently ignored.
Use a server-side flag or a session marker to ensure the tracking call fires only once per order.
Missing Visitor Data #
If neither visitor nor email is included in the sale call, Clerk records the order but cannot link it to a session or customer profile. The order appears in Data > Orders but never shows as Clerk-influenced in analytics.
Always include the visitor parameter. Adding email and customer when available also improves personalisation across future sessions for that customer.
Disabling Visitor Tracking #
To stop tracking for a specific visitor — for example, when they have declined all tracking via a cookie consent banner — pass "visitor": null in all API calls for that session. Clerk processes the requests normally but does not log any activity against a visitor profile.
In a Clerk.js setup, set it in the config instead:
Clerk('config', {
key: 'YOUR_API_KEY',
visitor: null
});
Sales are still logged when visitor tracking is disabled. Clerk receives the order data and includes it in analytics, but without any session context — no click history, no browsed products, no element attribution. Clerk only knows what was bought, not how the visitor got there.