---
title: "Record business facts on the server · Agent Wranglers"
url: https://agentwranglers.se/playbook/analytics/events-and-metrics/server-side-business-events
id: ANA-3
level: Recommended
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1094
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Record business facts on the server

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Events and metrics](https://agentwranglers.se/playbook/analytics/events-and-metrics.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: ANA-3
- Level: Recommended
- Chapter: [Events and metrics](https://agentwranglers.se/playbook/analytics/events-and-metrics.md)
- Rule: 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.
- 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.
- How: 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](https://agentwranglers.se/playbook/events/publishing-events/outbox.md) 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](https://agentwranglers.se/playbook/application-security/limits-and-business-logic/server-side-business-rules.md) requires.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### SQL example

```sql
-- 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

```js
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

```python
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})
```

## Related rules

- DEN-17 [Reconcile totals with the source](https://agentwranglers.se/playbook/data-engineering/data-quality-tests/source-reconciliation.md) in Data engineering
- EVT-6 [Send side effects through an outbox](https://agentwranglers.se/playbook/events/publishing-events/outbox.md) in Integration and events
- APS-52 [Work out every amount on the server](https://agentwranglers.se/playbook/application-security/limits-and-business-logic/server-side-business-rules.md) in Application security
- ANA-6 [Measure how many new accounts reach their first result](https://agentwranglers.se/playbook/analytics/events-and-metrics/activation-rate.md) in Product analytics and experiments

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/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.

- Previous: [Validate every event before you store it](https://agentwranglers.se/playbook/analytics/events-and-metrics/event-validation.md)
- Next: [Keep test and bot traffic out of the numbers](https://agentwranglers.se/playbook/analytics/events-and-metrics/exclude-test-traffic.md)
- [Events and metrics](https://agentwranglers.se/playbook/analytics/events-and-metrics.md)
