One free-tier user can't quietly run up a four-figure bill on your credit pool. Every signup gets a Customer record with a hard monthly cap, and once they reach it Ringside returns 402 on their requests instead of your wallet absorbing the overage.
Two webhooks feed your backend. customer.budget_exceeded fires when a single Customer hits its cap. wallet.low fires when your own shared credit pool crosses its low-balance threshold, which is your cue to top up before every customer starts failing at once. There's no per-customer 80% warning event today, so treat the cap as a hard stop and lean on wallet.low for the early signal at account level.
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, "monthly_budget_usd": budget, "rpm_limit": 60 if plan == "free" else 600, "tpm_limit": 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": # Your shared Ringside credit pool crossed its low-balance threshold. # Payload is {balance_usd, threshold_usd}: account-level, no customer. # Top up before customers start hitting wallet_empty 402s. alert_ops( f"Ringside pool low: ${data['balance_usd']} left " f"(threshold ${data['threshold_usd']}). Top up." ) elif t == "customer.budget_exceeded": # One Customer hit its monthly cap. Ringside already returns 402 for that # Customer; we flip a flag in our own DB so we can show a billing CTA. # Payload is {customer_id, budget_usd, spent_usd, endpoint}. cus_id = data["customer_id"] mark_user_over_budget(cus_id) send_email(cus_id, "You're at your cap. Upgrade or wait until next month.") return {"ok": True} def send_email(customer: str, body: str): print(f"[email] {customer}: {body}") def alert_ops(body: str): print(f"[ops] {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 downward crossing of your pool's low-balance threshold, it's edge-triggered, so you won't get spammed if the balance bounces around the line. Its data is {balance_usd, threshold_usd}, account-level, with no customer attached. customer.budget_exceeded fires when a Customer hits its cap; its data is {customer_id, budget_usd, spent_usd, endpoint} (the customer_id is the cus_... id, not your external_id, so look the user up by that).
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 the cap is hit, Ringside blocks new /v1/chat/completions requests for that Customer with 402 payment_required, code customer_budget_exceeded, until you raise monthly_budget_usd via PATCH /v1/customers/:id or a new billing period rolls over. Other Customers are unaffected.
Run it
bashexport FC_API_KEY=ko_0d7f2a91c4e35b86af10d2c7e94b6f3a5d81c02e7b4936af18d5c60e2a7f9b34 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.