A proxy dashboard has one job: tell the customer what they're using, what it costs, and whether it's working. The version we shipped last a month ago did all three, but each one had small holes that started to add up. A "Connected" badge that stayed green when an account had run out of credit. A "Test proxy" button that always tested the Standard tier even when the customer was paying for CleanProxy or Mobile. A cost estimate that recalculated based on which tier the picker was set to, not which tier the customer had actually been using. None of these were show-stoppers in isolation. Together they meant the dashboard was technically accurate about its inputs and quietly misleading in what it implied.
We spent a week rebuilding it. This post walks through what changed, why, and what the rebuild taught us about how customers actually use this product.
The Simple ↔ Advanced split
The biggest change is structural. The previous dashboard tried to serve a first-day customer and a power user from the same screen — every option visible, every code snippet rendered, every country in a long search field. That's the right call when you're building for one persona, and it's the wrong call when you're building for two.
We split the page into Simple and Advanced modes with a small toggle in the header. Simple shows three tier cards, common countries, one cURL example, and a Test button. Advanced keeps everything the old dashboard had — IP whitelisting, sticky sessions, city-level geo, code samples in Python / Node / browser / curl, the full country list, the password-syntax reference. The toggle remembers the customer's choice across sessions in localStorage and (new for this release) syncs the preference to the server so opening the dashboard from a different device picks up where you left off.
New customers land on Simple by default. Power users flip to Advanced once and stay there. Both flows render the same data through the same APIs — Advanced is a superset, not a parallel universe.
A real per-tier spend breakdown
The headline change behind the Simple view is that the dashboard can now answer "where is my proxy spend actually going?"
Before this release, the gateway recorded one byte counter per customer per day. That's enough to bill correctly (the customer's balance ledger has always been right) but it left a real gap: if you used Standard ($3/GB) for most of your traffic and Mobile ($10/GB) for a few critical requests, the dashboard could show you total spend but couldn't tell you that the small Mobile traffic dominated the cost.
We instrumented the gateway to record per-tier counters alongside the existing totals. Every routed request now increments a counter shape like proxy:usage:<key>:<date>:tier:<tier> and proxy:requests:<key>:<date>:tier:<tier> — sub-millisecond overhead per request, no impact on routing latency. The "Used Today" line in the dashboard has a new see spend breakdown link that opens a modal:
| Tier | Requests | Data | Spend |
|---|---|---|---|
| Mobile | 247 | 95.2 MB | $0.93 |
| CleanProxy | 1,203 | 198.4 MB | $0.97 |
| Standard | 2,397 | 118.9 MB | $0.35 |
Three windows — Today, 7 days, 30 days. The modal shows totals (spend, data, requests) and a per-tier table sorted by spend. The instrumentation is new, so anything from before deploy day is honestly disclosed at the bottom of the modal: "Per-tier data captured from 2026-05-16 onward. Earlier requests are counted in your total balance but not split by tier."
The non-obvious value of this view is the same dollar discipline that a credit-card statement gives. A customer who flipped to Mobile to test something and forgot to flip back can see the cost concentration immediately and adjust. We expect this to drive a small but consistent shift toward customers using the right tier for the right traffic.
Tier-aware Test proxy
The old Test button always issued a password=standard request regardless of which tier the customer was paying for. This was a small mistake with big consequences — a CleanProxy customer would click Test, see a clean residential exit IP, and conclude "CleanProxy works"; in reality they had tested the Standard route.
The new version takes a tier argument. The Simple dashboard renders a dropdown next to the Test button that defaults to the customer's currently-selected tier but is independently controllable:
[Test proxy ⚡] on tier: [CleanProxy ($5/GB) ▾] One request via your selected tier
The point of the independence is the common case "I'm building with CleanProxy but want to spot-check Standard before I commit to spend." The backend validates the tier name against the gateway's actual tier list (standard, cleanproxy, cleanproxy_geo, sticky_clean, mobile); unknown values get a clear 400 rather than a silent fallback. The response echoes back the tier the gateway actually used, so the result panel can label what was tested:
● 92.40.x.x
Location: Manchester, United Kingdom
ISP: Vodafone Mobile UK
Tested as: mobile
The ISP field is new. It is the single most useful "is this a real residential IP" signal a customer can see at a glance — a real telco name like Vodafone or Comcast tells you the proxy is doing what it promises; DigitalOcean or OVH in the same slot tells you a datacenter IP slipped through and the proxy network has a quality problem to fix.
The accuracy sweep
Beyond the structural changes, we did a systematic audit of every state the dashboard could render. A surprising number of small bugs turned up — none individually serious, all collectively eroding trust. The full list, by what they fixed:
Status badge accuracy. The "Connected" badge was hardcoded green regardless of account state. A customer who'd been suspended, had hit a cooldown, or had run out of credit would see "Connected" while every proxy request was being refused upstream. The badge now derives from real state — green "Connected" only when the account is active and has positive balance; amber "Out of credit" when balance hits zero; red "Suspended" when admin-disabled.
Pending account state. The first 10–30 seconds after signup, while the proxy account is being provisioned, the customer's api_key is the literal string pending. The old dashboard happily rendered a proxy URL containing pending as the auth username — which customers were copying into their scripts and reporting as "broken". The new dashboard suppresses the connection details and shows a clear "Provisioning your proxy account…" notice during this window.
Low-balance threshold. The warning fired at $0.50 remaining. At Standard tier ($3/GB) that's about 170 MB before service interruption — too late to be useful. Bumped to $5, which gives 1–2 GB of runway across tiers. A separate red "Out of credit" notice fires at $0.
Mismatched tier banner. A customer paying for the sticky_clean tier (only exposed in Advanced) used to see the Simple picker default to Standard with no explanation. They could end up thinking their actual subscription wasn't reflected in the UI. The new behaviour: the picker still defaults to Standard for the action, but a blue info banner makes the customer's real tier visible and links them to Advanced.
Failed-test rendering. A failed Test proxy request used to render the error text where the IP normally went ("Connection failed" sat in monospace where an IP address would). The new flow keeps testResult and testError in separate state and renders the error in a visually distinct red-bordered panel with an actionable retry message: "Usually a transient upstream blip. Click Test proxy again — it normally clears on retry."
Country pinning + Mobile. The country picker used to be enabled for every tier, including Mobile — but mobile carrier IPs are NAT'd at the ASN level, so country pinning on Mobile is a no-op at the gateway. The picker is now disabled when Mobile is selected, with a small caption explaining why.
Stale test results. Changing tier or country in the picker used to leave the old test IP showing — misleading because that IP came from a different tier than what was now selected. The result now clears as soon as the inputs change.
Polling while hidden. The 30-second balance/usage poller kept ticking even when the customer had the tab in the background. This wasted bandwidth on their side and Redis hits on ours. The poller now pauses when document.hidden and resumes (with an immediate refresh) on visibility change.
"Used Today" timezone clarity. The counter is keyed on UTC day, but the dashboard label said "Used Today" with no timezone hint. Customers in non-UTC timezones occasionally raised confusion when their counter reset at 11am local instead of midnight. The label now reads "Used Today (UTC)" — small clarification, no functional change.
Bonus credit visibility. The Paid/Bonus breakdown under Credit Balance only showed when BOTH paid and bonus deposits were non-zero. Customers with only signup bonus credit (no paid deposit yet) saw a bare dollar figure with no explanation of where the credit came from. The breakdown now shows whenever either is non-zero.
Better byte formatting. The old formatBytes returned "0.01 MB" for any non-zero usage below the MB threshold — making single-byte test responses look like real usage. New thresholds: 0 for zero bytes, <1 KB for sub-KB, KB for under a MB, MB up to 1 GB, GB above that.
Cross-device preferences and the soft toggle
Small but worth calling out. The previous "Switch to Advanced" links inside Simple used localStorage.setItem(...) + window.location.reload(). That meant flipping mode triggered a full page reload, killing any in-flight test result or polling state.
We added a small /api/account/preferences endpoint backed by a Redis hash per user. The Simple/Advanced toggle now syncs across devices: change to Advanced on your laptop, open the dashboard on your phone, it lands in Advanced too. Locally, the state change is a soft React state update rather than a page reload — keeps any test result or session you had visible during the swap.
What we deliberately did not do
A few things came up in the audit that we chose to defer or skip.
Sticky-session controls in Simple. Adding session pinning to the Simple picker would have made it un-simple. Customers who need sticky sessions are by definition power users; they should be in Advanced where the full session-pinning UI lives. The cross-link is one click away.
Multi-language code examples in Simple. Same logic. cURL covers ~80% of the "how do I actually use this" answer. Python, Node, browser, and language-specific code snippets live in Advanced. Simple's job is to get the customer to a working URL fast, not to teach them every integration pattern.
Per-tier pre-flight combo validation. The gateway already rejects unknown tier names. A theoretical future check would validate every (tier, country) combination against a capability map before sending the test, catching e.g. "Mobile + Switzerland" if mobile carriers don't cover that country. We don't have that signal exposed yet; built when there's a real customer pain point.
Reformatting historical "before metering" data into per-tier buckets. The per-tier metering is new code; everything before its deploy date has no tier attribution. Rather than guess (some heuristic based on what tier the customer was on at the time, which is a moving target), we surface the limitation honestly in the modal's footer.
Operational notes
The metering change touches the request hot path. Each proxied request now does two extra HINCRBY calls into the per-tier counters. We benchmarked it at sub-millisecond per request on the production Redis path; the gateway's total request-handling budget is in tens of milliseconds, so the addition is statistical noise. Zero customer-visible latency impact.
The cost-basis prices in the per-tier breakdown are computed client-side from a copy of the gateway's tier table. If we change pricing in the gateway, the dashboard's "Spend" column drifts until the table is updated. We considered storing per-tier spend directly in Redis but rejected it — pricing changes would require a backfill, and the customer's authoritative balance ledger is unaffected either way. The math is cheap; recomputing every time is the better shape.
What we hope this teaches
Mostly, the audit reminded us how easy it is for a dashboard to drift into low-grade dishonesty. None of the bugs we shipped were lies — every one was an unintended consequence of a sensible-at-the-time decision (hardcode the badge because nothing has changed yet; always test Standard because that's the default; show latency because we have the data). The cumulative effect was a dashboard that was more useful for the engineer who built it than for the customer who used it.
We don't think this is a rare class of mistake. Most dashboards have a few — the boundary between "accurate measurement" and "honest presentation" is exactly where small bugs hide. The fix is not to write more code; it's to spend a half-day reading every line of the UI as if you don't know what it's supposed to mean. That's the audit that produced this rebuild.
If you spot something in your dashboard that doesn't match what you're actually seeing on the proxy — please tell us. The shortest path to a better product is a customer noticing what the team behind it missed.