Tracking Usage
Track Usage From Your Backend
This guide shows how to instrument your backend to send usage events to Brilo. Events are the foundation of usage-based billing — everything downstream (metrics, pricing, invoices) depends on events being tracked reliably.
Where to Track
Track an event as close to the action as possible — when the billable thing actually happens, not after a batch process or at the end of the day. This ensures your draft invoices and usage dashboards reflect real-time state.
Good places to track:
- Inside the API handler that serves the request
- In a middleware layer that wraps billable routes
- In the service method that performs the billable action
- In a message queue consumer if the action is async
Python
import os
import hashlib
import requests
from datetime import datetime, timezone
BRILO_API_KEY = os.environ["BRILO_API_KEY"]
BRILO_BASE_URL = "https://api.brilo.tech"
def track_event(event_name, customer_id, properties=None, timestamp=None):
if timestamp is None:
timestamp = datetime.now(timezone.utc).isoformat()
# Deterministic idempotency ID
raw = f"{customer_id}:{event_name}:{timestamp}"
idempotency_id = hashlib.sha256(raw.encode()).hexdigest()[:32]
payload = {
"event_name": event_name,
"customer_id": customer_id,
"idempotency_id": idempotency_id,
"time_created": timestamp,
"properties": properties or {}
}
response = requests.post(
f"{BRILO_BASE_URL}/api/track",
headers={"X-API-KEY": BRILO_API_KEY, "Content-Type": "application/json"},
json=payload,
timeout=5
)
response.raise_for_status()
return response
# Usage
track_event(
event_name="api_call_made",
customer_id="cust_123",
properties={"endpoint": "/v1/messages", "tokens_used": 1200}
)Non-blocking tracking with a thread
To avoid adding latency to your API response:
import threading
def track_event_async(event_name, customer_id, properties=None):
thread = threading.Thread(
target=track_event,
args=(event_name, customer_id, properties)
)
thread.daemon = True
thread.start()Node.js
const crypto = require("crypto");
const BRILO_API_KEY = process.env.BRILO_API_KEY;
const BRILO_BASE_URL = "https://api.brilo.tech";
async function trackEvent(eventName, customerId, properties = {}, timestamp = null) {
const time = timestamp || new Date().toISOString();
const raw = `${customerId}:${eventName}:${time}`;
const idempotencyId = crypto.createHash("sha256").update(raw).digest("hex").slice(0, 32);
const payload = {
event_name: eventName,
customer_id: customerId,
idempotency_id: idempotencyId,
time_created: time,
properties
};
const response = await fetch(`${BRILO_BASE_URL}/api/track`, {
method: "POST",
headers: {
"X-API-KEY": BRILO_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Brilo tracking failed: ${response.status}`);
}
return response;
}
// Usage
await trackEvent("api_call_made", "cust_123", {
endpoint: "/v1/messages",
tokens_used: 1200
});Fire and forget (non-blocking)
// Don't await — let it run in the background
trackEvent("api_call_made", "cust_123", { tokens_used: 1200 })
.catch(err => console.error("Brilo tracking error:", err));Retry Logic
Network requests fail. Add retry with exponential backoff around event tracking to ensure events reach Brilo:
import time
def track_event_with_retry(event_name, customer_id, properties=None, max_retries=3):
for attempt in range(max_retries):
try:
return track_event(event_name, customer_id, properties)
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
# Log failure and move on — don't block your main flow
print(f"Brilo tracking failed after {max_retries} attempts: {e}")
return None
wait = (2 ** attempt) + (random.random() * 0.5)
time.sleep(wait)Middleware Pattern
For APIs where every request to certain routes is billable, use middleware to track automatically:
# Flask example
from functools import wraps
def track_usage(event_name, get_properties=None):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
result = f(*args, **kwargs)
properties = get_properties(result) if get_properties else {}
track_event_async(event_name, current_user.customer_id, properties)
return result
return wrapper
return decorator
@app.route("/api/messages", methods=["POST"])
@track_usage("message_sent", get_properties=lambda r: {"characters": len(r.data)})
def send_message():
# ... handle requestBackfilling Historical Events
If you're migrating to Brilo and need to load historical usage, send events with the original time_created timestamps:
for row in historical_usage_data:
track_event(
event_name=row["event_type"],
customer_id=row["customer_id"],
properties=row["metadata"],
timestamp=row["created_at"].isoformat()
)
time.sleep(0.05) # Rate limit yourself to avoid hitting API limitsUse deterministic idempotency IDs based on the original record so re-runs don't double-count.
Verifying Your Setup
After tracking a few events, confirm they arrived:
curl -X POST https://api.brilo.tech/api/verify-events \
-H "X-API-KEY: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"idempotency_ids": ["your-idempotency-id-1", "your-idempotency-id-2"],
"lookback_days": 1
}'You can also view recent events in the dashboard under Events.
Next Steps
Updated 4 months ago

