Engineering notes
Billing Gateway is a multi-tenant cloud platform for billing and access control on MikroTik networks. Operators sell timed or data-capped Wi-Fi vouchers; the platform authorises every login, enforces the limits centrally, and gives the operator the numbers on their phone.
It's built to handle specific constraints: Starlink behind CGNAT, mains power that comes and goes, a currency that moves, and a market that runs on cash. Most of the interesting decisions below come from one of those four.
June 2026
Live since
8
Routers
9,800
Vouchers activated
60
Days, no downtime
Last updated June 2026 · about 7 minutes to read
If you run a hotspot and want the product rather than the internals, start here.
Almost every small hotspot, WISP, guest Wi-Fi in this market runs on one of two free tools: Mikhmon, which runs on a computer at the shop, or MikroTik's own User Manager, which runs on the router itself. Both work. Both share the same structural weakness — all the state lives at the site.
That produces four failures, and operators describe all four without ever connecting them to a cause:
State is lost when the hardware (router) is..
Either reset, replace or damaged. Resulting with the users, vouchers and usage history being lost with it. A batch of printed vouchers becomes worthless paper as history is lost.
Expiry is enforced locally, so it fails quietly
Counters live on the router. After a reboot, or a script that did not survive an upgrade, sessions simply keep running. Nobody is notified, because nothing errored — the customer just keeps browsing.
Nothing is auditable
There is no independent record of what was sold, so cash cannot be reconciled against usage. The owner estimates the sales and hopes it correct.
Operations require physical presence
Generating a batch of vouchers, changing a price, or cutting off a customer means somebody travels to the site.
The design goal that follows solves this issues: move every piece of state that matters: identity, entitlement, usage, money.. are moved off the router and into one place that is authoritative, auditable and reachable from a phone or any other remote device. The router should hold no records it cannot afford to lose in case it fails or malfunctions.
The router keeps no user database. It holds a hotspot, a WireGuard peer and a heartbeat schedule, and it asks the platform about every single login. Everything that decides whether someone gets online (the voucher, its remaining time and data, the tenant's account standing) all live in one place.
The tunnel is outbound. Standard Starlink is behind CGNAT, so there is no public IP to reach and no port to forward. The router dials out to the platform over WireGuard instead, which means a site needs nothing more than a working internet connection.
Only control traffic crosses it. Customer browsing leaves through the local WAN as normal. What travels over the tunnel is RADIUS, accounting and the occasional API call — so bandwidth cost stays flat regardless of how much a site sells, and so does hosting cost. That single fact is why the pricing is per router rather than per voucher in most cases.
One endpoint, three protocols. The API routes on
Service-Type
and
Framed-Protocol,
so hotspot, PPPoE and WPA2-Enterprise share the same authorization path and differ only in which action
handles them and which attributes come back.
Every customer I have arrived through a technician, and every one of those technicians had built this same configuration stack and steps by hand, many times. The cumbersome setup pain was not a feature request but one of the main reasons they switched. So the target was one paste into a terminal on a factory-reset router to enable automatic configuration of all requirements.
Run once on the router
/tool fetch url=https://your-server/p/KEY mode=https dst-path=ztp.rsc; :execute { /import ztp.rsc }
The script it fetches is generated per router and configures:
The onboarding screen then polls for the router's first heartbeat and moves the tenant to their dashboard the moment it arrives. So the technician immediately gets a definite answer instead of wondering whether it worked.
Fetch-and-import is remote code execution by design. Whatever comes back over that connection is executed on the router with full administrative rights, so two things are not optional:
A verified channel
Without certificate verification, anyone positioned between the router and the server can serve their own script. On shared or resold links that is not a hypothetical. RouterOS v7 will verify properly once the CA bundle is imported, which is one more line in the same ZTP command above.
A key that dies after use
The provisioning UUID in the URL is the only secret in the flow, and it travels in a command people copy, paste and screenshot. It expires on first successful import, and after a short window regardless.
Limits that survive CGNAT
Per-address throttling is the obvious control and the weakest one here, most of this market sits behind carrier-grade NAT, so a legitimate technician could possible share an address with multiple strangers. The per-key limit does the real work: a provisioning key is expected to be fetched once, by one router, within minutes of being issued. The per-address limit is set generously, as a backstop against bulk scanning rather than as the main defence.
One database, tenant-scoped, with a panel per tenant. The unusual part is where the tenant comes
from: not a session, not a subdomain, but
NAS-IP-Address
on the incoming RADIUS request.
The router identifies itself, the router resolves to a tenant, and the tenant carries the billing context for the whole request. Nothing about the customer's input decides whose account is charged — which matters, because the customer's input is a six-character code typed into a captive portal by a stranger.
Is the tenant in good standing?
Checked before the database is touched at all. A suspended account costs one comparison rather than a query. And this runs on every login attempt across every site, so efficiency matters.
Is the router ready?
Suspended hardware and hardware that is still provisioning both reject, with different messages. Billing a voucher against a router the platform thinks is half-configured is a good way to produce a ledger nobody can explain later.
Does this voucher belong here?
A voucher is scoped to its tenant, so one operator's codes cannot be redeemed on another operator's router. It can also be pinned to a single router, for an operator running several sites who wants a batch to work at one of them only.
Many-to-many membership is modelled too — every account can have more than one login and assigned different roles and permissions (RBAC). The schema also records which user generated which batch of vouchers or any other event, so the audit trail exists too.
Four conditions shaped more of this system than any technical preference did.
Power fails, a generator picks up, and routers can potentially restart several times a day. Rebooting is the normal operating condition here, not an incident. So nothing is allowed to depend on the router staying up. It reconnects its tunnel by itself, re-registers, and reports in on a schedule automatically. Customers are also re-connected automatically without having to re-type their codes.
The pound went from roughly 6,500 to 7,400 to the dollar in a matter of months. Pricing denominated in SSP silently loses value; pricing shown in dollars is unhelpful to someone who earns and spends in pounds.
So amounts are denominated in USD, charged in SSP at a published rate, and reviewed monthly.
Mobile money collection is built.
Mobile money was growing here until a cash shortage made withdrawals difficult or capped for businesses as of 2026. Collecting a customer's payment into an account the operator cannot draw from does not help him; it moves his money somewhere he cannot reach. But as the economy recovers, mobile money will be the default.
Reconciliation is designed around cash for now, and the platform's own subscriptions are collected in either cash, bank transfer or mobile money.
The platform started per-activation: a small fee deducted from a prepaid wallet each time a voucher was first used. It is a fair model. It scales with the customer's success, costs nothing on a quiet day, and the arithmetic favoured almost every tenant — most were paying meaningfully less than a flat fee would have cost them.
Some asked for the flat fee anyway. Predictability was worth more to them than being right about the price, and a number a technician can quote to a client without opening a spreadsheet is worth more than a number that is technically fairer.
It is now a fixed monthly price per router, which also matches the cost curve: hosting scales with routers and concurrent sessions, not with vouchers sold.
In order of how much it would cost me if it went badly.
Initially; WireGuard, FreeRADIUS, PostgreSQL and the Go service run on a single VPS with no failover. If it went down, every tenant stops authenticating new customers at the same moment, and the database goes with it. I am selling cloud reliability against a local tool that has never had an outage, so this is the gap that mattered the most and needed urgent fixing.
What happens next: Being fixed in cost order: off-box backups and monitoring first, then a managed database so the box becomes stateless and disposable, then a second RADIUS node. MikroTik fails over between RADIUS servers natively, so that part needs no router-side change.
If the platform is unreachable, new logins fail. Sessions already established keep running, and a site whose own internet is down has nothing to sell anyway. So the only case that really pains is "site up, platform down", which is entirely my fault when it happens. That needed to change
What happens next: A local mirror of unused voucher codes on the router would close it, at the cost of reintroducing exactly the drift the central design exists to avoid. Not obviously worth it until uptime is solved properly.
The per-activation fee used bracketed ranges, minimum inclusive and maximum exclusive. A 30-day voucher is 2,592,000 seconds — which falls out of the "1 week to 30 days" band and into "30 days to 1 year", at two and a half times the intended fee. The most commercially important product on the platform was never once charged at its advertised rate.
What happens next: It surfaced because a customer said his wallet was draining faster than expected, not because anything alerted. Boundary conditions in a pricing table are exactly the sort of thing that deserves explicit tests, and did not have them.
PPPoE and WPA2-Enterprise are routed correctly and then rejected with an honest message. The identity model they need is different from a voucher: a named person with a persistent credential and a renewal date, rather than an anonymous single-use code.
What happens next: The plan is one subscribers table and one shared authorization action, with the protocol deciding only which attributes come back — PPPoE and EAP differ in transport, not in identity.
Hardening before features. The platform is only worth building on if it is still there next month.
Harden provisioning
Single-use, short-lived provisioning keys and a verified TLS channel for the setup script. Small, and it removes the one remote code execution path in the system.
Backups and monitoring off the box
Automated database dumps somewhere that is not the VPS, plus alerting that reaches me before it reaches a customer. Cheap, and it is the difference between a bad hour and a dead company.
Pause a subscription
Operators close for a season, or a generator dies for three weeks. Billing stops, configuration and vouchers survive. No foreign platform offers it, because no foreign platform expects a business to stop for a month.
PPPoE and WPA2-Enterprise
One subscribers table for named, recurring identities, and one shared authorization action. The protocol decides which attributes come back: PPPoE adds address assignment, EAP changes the credential exchange but the identity model is the same.
A second RADIUS node
Once the database is managed and the application box is stateless, a spare authorizer on another provider is straightforward. MikroTik fails over between RADIUS servers natively, so nothing changes at the site.
I am happy to talk about any of it — the RADIUS design, the provisioning flow, or what it is actually like running network software in a market like this one.
If you run a hotspot and wanted the product rather than the internals, that page is here.