Back to cookbook

Per-customer budgets

What you'll build

A signup flow that, on every new user, creates a Ringside Customer with a monthly budget cap. Two webhooks feed your backend: wallet.low (fired once when the Customer crosses 80% spend) and customer.budget_exceeded (fired when spend hits 100%, after which Ringside returns 402 budget_exceeded on every request until you raise the cap).

What you need

  • An FC API key with scope api:write,api:webhooks
  • A public HTTPS endpoint for Ringside to POST to
  • pip install flask openai

Full code

python
# signup_and_budgets.py import hashlib, hmac, json, os, time import flask from openai import OpenAI app = flask.Flask(__name__) client = OpenAI(api_key=os.environ["FC_API_KEY"], base_url="https://api.fightclub.pro/v1") FC_HEADERS = {"Authorization": f"Bearer {os.environ['FC_API_KEY']}"} WEBHOOK_SECRET = os.environ["FC_WEBHOOK_SECRET"] def create_customer(external_id: str, plan: str): """Plan tier -> monthly budget. Called on every new-user signup.""" budget = {"free": 1.0, "pro": 25.0, "team": 200.0}[plan] r = client.post( "/customers", cast_to=dict, body={ "external_id": external_id, "budget_usd": budget, "rate_limit_rpm": 60 if plan == "free" else 600, "rate_limit_tpm": 10_000 if plan == "free" else 200_000, "metadata": {"plan": plan, "signup_ts": int(time.time())}, }, ) return r @app.post("/hook/ringside") def webhook(): """Ringside delivery — verify HMAC, then act.""" raw = flask.request.get_data() sig = flask.request.headers.get("X-FC-Signature", "") ts = sig.split("t=")[1].split(",")[0] given = sig.split("v1=")[1] expected = hmac.new( WEBHOOK_SECRET.encode(), f"{ts}.{raw.decode()}".encode(), hashlib.sha256, ).hexdigest() if not hmac.compare_digest(given, expected): flask.abort(401) # Reject replay attacks older than 5 minutes. if abs(time.time() - int(ts)) > 300: flask.abort(401) event = json.loads(raw) t = event["type"] data = event["data"] if t == "wallet.low": # 80% threshold — email the user a heads-up. customer_ext = data["customer"]["external_id"] send_email(customer_ext, "You've used 80% of your AI budget this month.") elif t == "customer.budget_exceeded": # 100% threshold — block further requests automatically happens on Ringside's # side. We flip a flag in our own DB so we can show a billing CTA. customer_ext = data["customer"]["external_id"] mark_user_over_budget(customer_ext) send_email(customer_ext, "You're at your cap. Upgrade or wait until next month.") return {"ok": True} def send_email(ext: str, body: str): print(f"[email] {ext}: {body}") def mark_user_over_budget(ext: str): print(f"[db] mark {ext} over_budget=true") # Call once at deploy time: register the webhook endpoint. def register_webhook(): r = client.post( "/webhooks", cast_to=dict, body={ "url": "https://your-app.example.com/hook/ringside", "events": ["wallet.low", "customer.budget_exceeded"], }, ) print(f"secret (save this!): {r['secret']}") if __name__ == "__main__": # Demo: create a Customer on the Pro plan. create_customer("ext:user_42", plan="pro") app.run(port=5000)

Walkthrough

Ringside fires wallet.low exactly once per spend-crossing, it's edge-triggered, so you won't get spammed if spend bounces around 80%. customer.budget_exceeded similarly fires once per billing period. Both events carry the Customer snapshot in data.customer (id, external_id, budget_usd, spent_usd, metadata).

Reject requests where abs(now - t) > 300 seconds, the receiver-tolerance window. Always use hmac.compare_digest (constant-time compare) rather than == to prevent timing side channels.

Once budget_exceeded fires, Ringside auto-blocks new /v1/chat/completions requests for that Customer with 402 budget_exceeded until you either raise the cap via PATCH /v1/customers/:id or a new billing period rolls over.

Run it

bash
export FC_API_KEY=sk_live_xxx export FC_WEBHOOK_SECRET=whsec_xxx # printed by register_webhook() python signup_and_budgets.py # in another terminal: ngrok http 5000 — then register the ngrok URL.

What's next