ANA-3Recommended
Record business facts on the server
Record events that count money, sign-ups or other business facts on the server, at the moment the fact is committed. Use browser events only for behaviour in the page.
How to do it
Ad blockers, closed tabs and failed networks drop browser events, and a reload can send one twice. Emit order_completed from the code that commits the order, or from the confirmed payment webhook, never from the thank-you page. Write it to an outbox in the same transaction, so a rolled-back order is never counted. Give each event an ID derived from the fact, such as the order ID, and drop duplicates on that ID. Stamp it with the time the fact happened, and compare counts by that time, not by arrival. The server already holds the amounts, as Work out every amount on the server requires.
Examples
SQL example
-- Daily reconciliation: must return 0 rows
SELECT day, o.orders, e.events FROM
(SELECT (created_at AT TIME ZONE 'UTC')::date AS day, count(*) AS orders FROM orders GROUP BY 1) o
FULL JOIN (SELECT (occurred_at AT TIME ZONE 'UTC')::date AS day, count(DISTINCT event_id) AS events
FROM events WHERE name = 'order_completed' GROUP BY 1) e USING (day)
WHERE o.orders IS DISTINCT FROM e.events AND day < current_date - 1;
JavaScript example
await db.transaction(async tx => {
const order = await tx.orders.insert(cart);
await tx.outbox.insert({ kind: 'analytics', payload: { event_id: `order-${order.id}`, name: 'order_completed', plan: order.plan, amount_cents: order.totalCents } });
});
Python example
with db.transaction() as tx:
order = tx.orders.insert(cart)
tx.outbox.insert(kind="analytics", payload={
"event_id": f"order-{order.id}", "name": "order_completed",
"plan": order.plan, "amount_cents": order.total_cents})
How to check
Place 20 test orders with an ad blocker on. The events table holds exactly 20 order_completed rows, matching the orders table. A daily query compares both counts and alerts on any difference.
Related rules
- DEN-17 Reconcile totals with the source in Data engineering
- EVT-6 Send side effects through an outbox in Integration and events
- APS-52 Work out every amount on the server in Application security
- ANA-6 Measure how many new accounts reach their first result in Product analytics and experiments
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/analytics/events-and-metrics/server-side-business-events">Agent Wranglers product analytics and experiments playbook</a>. In code you write, add a comment next to it with the same link.