# Network · Satellite analytics > The hourly analytics reporting contract — how every network app POSTs its traffic rollup to 2plot.ai so /traffic shows the whole network. --- .. toc:: ## 2plot.ai is the analytics home Every application in the network reports its own traffic to the hub, so the owner-only `/traffic` dashboard answers, per app and for the network as a whole: how many people visited each day, which pages they read, how long sessions ran, and roughly where visitors came from. Two signals feed the picture: 1. **Health** — the hub's pulse sweeps every satellite's `/healthz` **once an hour** and records up/down + latency (the "Satellite health & reach" panel on `/traffic`, and `/admin/heartbeat`). 2. **Traffic** — each satellite POSTs a signed rollup to the hub **once an hour** (or at minimum once a day). Report today's numbers-so-far each time: the hub folds all of a day's reports together, taking the growing maximum — and when it sees the counts drop sharply (a free-tier container was evicted and the satellite's ledger restarted from zero), it banks the previous peak and adds the new window on top. Satellites therefore do NOT need persistent disks to report accurately; every container lifetime's peak is summed into the day. Reports persist in the heartbeat store and its Postgres mirror, so they survive hub deploys too. ## The endpoint ``` POST https://2plot.ai/api/satellite/traffic Content-Type: application/json X-AI-Canvas-Timestamp: X-AI-Canvas-Signature: ``` The signature is the same scheme as the wallet-provision webhook — `HMAC_SHA256(CROSS_APP_WEBHOOK_SECRET, f"{timestamp}." + raw_body)`, timestamp within ±300 s. The secret is the shared `CROSS_APP_WEBHOOK_SECRET` env value every satellite already holds. ## The payload Required (v1 — the minimum that puts an app on the network chart): ```json { "app": "media", "date": "2026-07-23", "human_hits": 412, "bot_hits": 77 } ``` `app` is the satellite's network-directory key (`pirates`, `buzz`, `xyz`, `media`, `cast`, `dev`, …). `date` is the day the numbers cover (the app's own local day is fine — just be consistent). Optional v2 fields — adopt piecemeal, each unlocks more of `/traffic`: ```json { "visitors": 96, "sessions": 143, "median_session_s": 74.5, "pages": [{"path": "/studio", "hits": 210}, {"path": "/", "hits": 88}], "countries": {"US": 61, "DE": 12, "JP": 9} } ``` - `visitors` — unique (IP, browser) pairs that day, humans only. - `sessions` — visit groups split on 30-minute gaps (the hub's own session rule; keep it so numbers compare). - `median_session_s` — median first→last-hit span of **multi-page** sessions only. Never pad single-hit visits into it. - `pages` — up to 20 `{path, hits}` rows, most-visited first. - `countries` — up to 20 `{country: hits}`, humans only. Behind Cloudflare, read the `CF-IPCountry` header (and use `CF-Connecting-IP` as the client address) instead of geolocating — it's free and accurate. ## Reference sender ```python import hmac, hashlib, json, time, urllib.request def report_traffic(rollup: dict, secret: str): body = json.dumps(rollup).encode() ts = str(int(time.time())) sig = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest() req = urllib.request.Request( "https://2plot.ai/api/satellite/traffic", data=body, headers={"Content-Type": "application/json", "X-AI-Canvas-Timestamp": ts, "X-AI-Canvas-Signature": sig}) with urllib.request.urlopen(req, timeout=10) as r: return json.load(r) ``` Schedule it hourly (a background thread, an APScheduler job, or the platform's cron — anything). The hub itself eats this dog food: its hourly `analytics_rollup` pulse job records the hub's own day in the exact same format under `app="hub"`. ## What the hub does with it - `/traffic` → **Network traffic** chart stacks every app's daily humans, with a bots overlay (toggle All / Humans / Bots). - `/traffic` → **Applications** chart draws one line per app so audiences compare at a glance. - `/traffic` → **Satellite health & reach** joins the hourly health sweep (status, latency, uptime) with each app's reported 7-day reach, visitors/day, session medians, and top countries. - Reports ride the heartbeat event store (JSONL + optional Postgres mirror via `DATABASE_URL`), so history accumulates across deploys and feeds product decisions over time. --- *Source: /docs/satellite-analytics*