Goals and orders
Once the tag is installed it defines one global function, ds(), with three commands: goal for signups, demos booked and anything else you want counted; order for revenue the page itself knows about; identify to say who a visitor is in your own terms.
Track a goal
ds("goal", "signup");
// optionally, with a value
ds("goal", "demo-booked", { value: 500 });The goal name can be up to 64 characters; pick short stable names, they become rows in your dashboard's goals panel. The value is optional and is counted alongside the goal. Each goal event inherits the visitor's journey, so your goals get the same channel attribution as your orders.
Goal properties
ds("goal", "signup", { plan: "pro", seats: 5, trial: true });Every key on the object other than value is a property: up to 10 per goal, keys of lowercase letters, digits, - and _ up to 64 characters (the tag lowercases them for you), values a string, number or boolean stored as text up to 255 characters. A key that breaks the rule is dropped on arrival from the tag and refused by the events API, which names it.
On the dashboard, the goals panel opens a details view per goal: each property value with how many different people completed the goal that way and the recorded order revenue for those people in the period. A value is a link that filters the whole page to those people (?f_goal=signup&f_goal.prop.plan=pro, see filters). The API returns the same rows as goalProperties.
Identify a visitor
ds("identify", { user_id: "u_1001", email: "ada@example.com", plan: "pro" });user_id is your own id for the person, required, up to 128 characters with no spaces. email is optional and is stored only as a SHA-256 of the trimmed, lowercased address; the address itself is never written anywhere, and the hash is what lets a later Stripe payment by the same address find this visit. Any other key is a property under the same rules as goal properties; a later identify merges its keys over the earlier ones, and a new email replaces the old hash.
One person, one row. When the same user_id turns up on two cookies (a second browser, a cleared cookie), the later visitor is folded into the earlier one: its events move to the earlier id and the earlier row keeps its first-seen date. The tag is told the surviving id and switches its cookie to it, so window.datastated.visitorId changes and nothing splits again. Identifying the same cookie twice folds nothing new.
The same call exists server-side for owner keys, POST /api/sites/{domain}/identify, in the API reference.
Your #1 metric
Every breakdown on the dashboard ranks by visitors and by revenue. A site that does not sell on the page can rank by a goal instead: Settings → Your #1 metric, pick the goal, and supported visitor breakdowns show how many people completed it in place of revenue, so you still see which sources and pages convert. Revenue is the default and a single select switches back.
Track clicks without writing code
Put data-ds-goal on any element and every click on it (or anything inside it) counts as that goal. Add data-ds-goal-value for a value:
<button data-ds-goal="start-trial" data-ds-goal-value="49">
Start free trial
</button>No JavaScript, works in any site builder that lets you set attributes, and it fires even when your framework intercepts the click.
Scroll goals work the same way. Put data-ds-scroll on the element people should reach and it counts once per pageview when it scrolls into view. data-ds-scroll-threshold sets how much of it must be visible, 0 to 1, default 0.5:
<section data-ds-scroll="read-pricing" data-ds-scroll-threshold="0.5">Track an order
ds("order", { value: 49.99, currency: "USD", id: "ord_1001" });Call it on your thank-you or confirmation page. Pass your own order id and a refresh of that page, or the same order reported again through the events API, counts once: one row per site and id, up to 128 characters. Without an id every call is a new order. A browser order is self-reported, even when it has an id. To confirm payment, use the Stripe connection; the Shopify guide describes store-order coverage. Use the provider's connection instead of also sending the same purchase under a different id. On Stripe, the thank-you page can also hand its Checkout Session id to the events API and the payment is joined to this visit on the spot; see tie payments to visits.
Calling before the tag loads
The tag loads deferred, so a call that fires very early can beat it. Add this queue stub before your first ds() call. It queues arrays until the tag arrives. With data-require-consent, calls can wait for the first grant; denial or withdrawal discards them, and activity while denied is not replayed:
<script>
window.ds = window.ds || function () {
var call = Array.prototype.slice.call(arguments);
var queue = window.ds.q = window.ds.q || [];
if (call[0] === "consent" && (call[1] === "denied" || call[1] === "revoked")) {
queue.length = 0;
}
queue.push(call);
};
</script>Server-side conversions
Your backend can report a conversion without relying on a browser request. It must still honour the visitor's applicable consent choice. Sending a value through the events API authenticates your request, not the payment; send orders only after your payment system confirms them.
curl -X POST https://app.datastated.com/api/sites/acme.com/events \
-H "Authorization: Bearer dsk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "order",
"value": 49.99,
"currency": "USD",
"event_id": "pay_12345",
"visitor_id": "THE_BROWSER_VISITOR_ID"
}'event_id is your own idempotency key (a payment id, a job id): retries and duplicate webhooks merge instead of double-counting. On an order it is also the order's id. A refund is its own call with "kind": "refund", the order_id it reverses and a value; it becomes a negative row linked to the order and comes off net revenue without touching the order.
Ship window.datastated.visitorId to your server and pass it as visitor_id, and the conversion inherits that visitor's standing credit; without it the event still counts, unattributed. Goals work the same way with "kind": "goal", a "name" and an optional "props" object under the rules above. Full details in the API reference.
Questions? Email us at hello@datastated.com.