Skip to content
chatAgent
Esc
↑↓navigate↵open⌘Jpreview
On this page

Accounting

Accounting & Metrics Webhook Stream

Token-protected usage accounting and system metrics, exposed as a Server-Sent Events (SSE) stream, a WebSocket stream, and a JSON snapshot.

Endpoints

Method Path Auth Description
GET /accounting/snapshot Bearer token Single JSON snapshot
GET /accounting/stream Bearer token SSE stream of snapshots every ACCOUNTING_STREAM_INTERVAL s (default 5)
WS /accounting/ws ?token= or Sec-WebSocket-Protocol Continuous WebSocket stream of snapshots

Authentication

HTTP endpoints require the header:

Authorization: Bearer <ACCOUNTING_TOKEN>

The WebSocket endpoint accepts the token either as a query parameter (?token=<ACCOUNTING_TOKEN>) or as the first Sec-WebSocket-Protocol value. If ACCOUNTING_TOKEN is empty, all accounting endpoints are disabled.

Example: snapshot

curl -H "Authorization: Bearer $ACCOUNTING_TOKEN" \
  https://chatagent.emwee.co/accounting/snapshot

Example: SSE stream

curl -N -H "Authorization: Bearer $ACCOUNTING_TOKEN" \
  https://chatagent.emwee.co/accounting/stream

Each SSE event is:

event: accounting
data: { ...snapshot json... }

Example: WebSocket stream

const ws = new WebSocket(
  `wss://chatagent.emwee.co/accounting/ws?token=${ACCOUNTING_TOKEN}`
);
ws.onmessage = (e) => {
  const snapshot = JSON.parse(e.data);
  console.log(snapshot.agents_online, snapshot.totals, snapshot.hardware);
};

Each WebSocket frame is a JSON object with the exact same schema as a snapshot (see below), pushed every ACCOUNTING_STREAM_INTERVAL seconds.


Snapshot Schema

Every event carries a single JSON object:

{
  "ts": 1785639022.2,
  "agents_online": 3,
  "totals": {
    "messages_received": 120,
    "messages_replied": 115,
    "words_replied": 45200,
    "message_units": 452.0,
    "images_sent": 18
  },
  "per_number": [
    {
      "whatsapp_number": "94707373703",
      "messages_received": 40,
      "messages_replied": 38,
      "words_replied": 15000,
      "message_units": 150.0,
      "images_sent": 6,
      "last_activity": 1785639022.2
    }
  ],
  "tokens": {
    "totals": {
      "input_tokens": 125000,
      "output_tokens": 48000,
      "cache_read_tokens": 30000,
      "cache_write_tokens": 1500,
      "total_tokens": 204500
    },
    "per_model": [
      {
        "provider": "google",
        "model": "gemini-3.8-flash",
        "input_tokens": 80000,
        "output_tokens": 30000,
        "cache_read_tokens": 30000,
        "cache_write_tokens": 1500,
        "total_tokens": 141500,
        "calls": 412
      }
    ],
    "per_number": [
      {
        "whatsapp_number": "94707373703",
        "input_tokens": 45000,
        "output_tokens": 20000,
        "cache_read_tokens": 12000,
        "cache_write_tokens": 600,
        "total_tokens": 77600
      }
    ]
  },
  "hardware": {
    "cpu_percent": 12.5,
    "cpu_count": 8,
    "memory_total_bytes": 17179869184,
    "memory_used_bytes": 6442450944,
    "memory_percent": 37.5,
    "disk_total_bytes": 214748364800,
    "disk_used_bytes": 85899345920,
    "disk_percent": 40.0,
    "uptime_seconds": 86400.5,
    "hostname": "webhook-1",
    "pid": 42
  }
}

Field reference

Field Type Description
ts number Unix epoch seconds of the snapshot
agents_online integer Number of currently live agent actors
totals.messages_received integer Total inbound messages across all numbers
totals.messages_replied integer Total replies sent
totals.words_replied integer Total words in all replies
totals.message_units number Billing units, 1 message unit = 100 words (words_replied / 100)
totals.images_sent integer Total product images sent
per_number[].whatsapp_number string The sender’s WhatsApp number (wa_id)
per_number[].messages_received integer Inbound messages for that number
per_number[].messages_replied integer Replies for that number
per_number[].words_replied integer Words in replies for that number
per_number[].message_units number words_replied / 100 for that number
per_number[].images_sent integer Images sent to that number
per_number[].last_activity number Unix seconds of the last recorded activity
tokens.totals.input_tokens integer Total model input (prompt) tokens
tokens.totals.output_tokens integer Total model output (completion) tokens
tokens.totals.cache_read_tokens integer Total cached-prompt tokens read
tokens.totals.cache_write_tokens integer Total cached-prompt tokens written
tokens.totals.total_tokens integer Total tokens (input + output + cache_read + cache_write)
tokens.per_model[].provider string LLM provider (google / openai / anthropic)
tokens.per_model[].model string Model id (e.g. gemini-3.8-flash)
tokens.per_model[].input_tokens integer Input tokens for that model
tokens.per_model[].output_tokens integer Output tokens for that model
tokens.per_model[].cache_read_tokens integer Cached tokens read for that model
tokens.per_model[].cache_write_tokens integer Cached tokens written for that model
tokens.per_model[].total_tokens integer Total tokens for that model
tokens.per_model[].calls integer Number of LLM calls for that model
tokens.per_number[].whatsapp_number string Sender’s WhatsApp number
tokens.per_number[].input_tokens integer Input tokens for that number
tokens.per_number[].output_tokens integer Output tokens for that number
tokens.per_number[].cache_read_tokens integer Cached tokens read for that number
tokens.per_number[].cache_write_tokens integer Cached tokens written for that number
tokens.per_number[].total_tokens integer Total tokens for that number
hardware.cpu_percent number Current CPU utilization (0-100)
hardware.cpu_count integer Logical CPU cores
hardware.memory_total_bytes integer Total physical memory
hardware.memory_used_bytes integer Used physical memory
hardware.memory_percent number Memory usage percent
hardware.disk_total_bytes integer Root filesystem total
hardware.disk_used_bytes integer Root filesystem used
hardware.disk_percent number Root filesystem usage percent
hardware.uptime_seconds number System uptime
hardware.hostname string Node hostname
hardware.pid integer Application process id

Billing convention

One reply message is billed as 1 unit per 100 words (rounded to 2 decimals). Images are counted separately in images_sent. Example: a reply of 250 words = 2.5 message units; sending 3 product images adds 3 to images_sent.


Storage

Counters persist in the SQLite database (CHATAGENT_DB_PATH) in the accounting table, keyed by whatsapp_number. The ConversationStore exposes:

  • record_inbound(user_id) — increment received
  • record_reply(user_id, words, images) — increment replied + words + images
  • accounting_report() — raw per-number rows

LLM token usage is stored in a separate SQLite database (CHATAGENT_TOKENS_DB_PATH, default ./data/tokens.db) in the token_usage table via TokenUsageStore:

  • record_usage(number, provider, model, *, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_tokens) — one row per LLM call
  • report() — aggregated totals + per-model + per-number

Each LLM call inserts a row; report() sums cached/input/output/total tokens per model and per WhatsApp number.

Last updated on September 16, 2026