Appearance
How Kiwire fits together
Six steps between a guest joining the SSID and getting online. Every portal problem is a break in one of them, and knowing which one tells you whose configuration to open.
The controller, the zone and the journey
When a guest's device joins the SSID, the network hardware — the controller, in Kiwire's terms — redirects their browser to Kiwire's portal entry point, carrying its own controller ID (the "nasid") plus whatever it knows about the session: VLAN, SSID, the guest's IP and MAC. That first request is where things get turned into something useful: Kiwire resolves which tenant the controller belongs to and switches to that tenant's own database, then resolves which zone the session falls into from the controller's nasid, VLAN, SSID and the guest's IP. The zone it loads carries the guest-facing policy for that patch of network — capacity limits, any forced profile — and, if one is set, the journey to run.
The journey is what actually puts a page in front of the guest: it ties together a designed portal template, the login methods on offer, and where the guest is sent after they log in. A controller tells Kiwire who is asking; a zone tells Kiwire which policy applies; a journey tells Kiwire what the guest sees. All three have to be wired together — controller registered, zone mapped to it, journey attached to the zone — before a guest gets a working portal page.
Login methods and the profile
The journey decides which login options are on offer for that zone: a registered Kiwire account, a voucher code, SMS/WhatsApp/email OTP, social login, QR login, sponsored or public self-signup, paid access, or an enterprise identity source (Active Directory, LDAP, SAML, Okta, Microsoft Entra ID). Whichever one the guest uses, the credential eventually reaches the same login check, which resolves the account and runs it through the same sequence of checks regardless of how the guest signed in.
Every account — guest, voucher, top-up — carries a profile: the bandwidth cap, data quota, session time limit and idle timeout that apply to it. On a successful login, the login check loads the account's profile and builds the RADIUS attributes from it. A zone can override this: if the zone has a forced profile set, the zone's profile is applied instead of the account's own, regardless of what the account was created with (the one exception is a guest who has just paid for a specific plan — that skips the zone override so the paid plan sticks).
RADIUS: authentication and accounting
Kiwire's RADIUS handling runs the other way round from what you might expect: the RADIUS server (FreeRADIUS) calls into Kiwire, not the other way around. FreeRADIUS is configured to call a single Kiwire endpoint, asking for one of four jobs — two lookups hand back the shared secret and unique ID it needs to trust a given NAS; one is the real authentication call, and one is where interim-update and stop accounting packets land.
The authentication call runs the guest's credentials through the login check — account existence, a dedup window against replayed requests, the tenant's concurrent-user and licence login-quota caps, account status, value date and expiry, zone access and zone capacity, MAC allow-listing if enabled, credit (session time) and quota checks with auto-reset/advance-profile handling, per-user simultaneous-connection limits, and voucher activation on first use. A pass builds a RADIUS Access-Accept attribute map — bandwidth caps, session timeout, quota, from the resolved profile; a failure returns Access-Reject. Once RADIUS accepts, the guest's browser still has to be handed off to the NAS's own login endpoint, and every vendor wants that handoff shaped differently — Kiwire builds the correct form fields, HTTP method and URL for each supported vendor (Mikrotik, Aruba, Ruijie/Wifidog, Huawei NCE, TP-Link Omada, Ubiquiti UniFi, and others). Accounting packets (start/interim/stop) from the NAS are queued as a background job rather than processed inline, and that's also where the live session list gets kept current.
Tenants, sub-tenants and the licence
Kiwire is multi-tenant at the database level: one landlord database holds the tenant registry, and each tenant gets its own database, named after the tenant. When a request comes in, Kiwire switches to that tenant's own database. Which tenant a request is currently operating as is worked out from the admin session — the tenant being browsed, or the signed-in admin's own default tenant — and that lookup is cached in memory so it isn't a database hit on every request.
What a tenant is allowed to do is governed by its licence key. Decoded from the key, it carries limits like the device quota, and a list of licensed modules. The licence sync takes those modules and rebuilds the tenant's permission set from them — anything not covered by the licence gets deleted from the tenant's permissions outright, on top of a fixed platform baseline every tenant keeps regardless of licence. Sub-tenants sit a level below this: they're a grouping inside one tenant's own database (with their own admins and roles), not a separate database. A controller can be assigned to a specific sub-tenant so its traffic is reported separately, without needing a whole extra tenant to do it.
Where the numbers come from
Nothing that happens on the portal side writes straight to a reporting table. Kiwire writes real-time counters into fast in-memory storage instead — logins, accounting deltas, page impressions and more, each keyed by tenant, zone and the current hour — and keeps them for eight days. Once an hour, at five minutes past, a scheduled roll-up sweeps up the previous completed hour's counters, sums them, and writes or updates rows in the tenant's monthly hourly-bucket report tables — then deletes the counters it just processed.
The practical consequence: the current, still-running hour is never in those report tables. It only exists as a live counter until the next roll-up picks it up. Anything built on the hourly report tables — the dashboard's "today" figures, most reports — is therefore always missing whatever has happened in the last, in-progress hour. Numbers that need to be live instead of hourly, like an active session count, are read straight from the live session list rather than from the report tables for exactly this reason.