콘텐츠로 이동

Hand Guests to Your Own Payment Portal (External Portal)

이 콘텐츠는 아직 번역되지 않았습니다.

Summary The External Portal lets you use your own payment or authentication system for the hotspot. MKController’s captive collects the guest and hands off a one-time session token to your portal; after the guest pays, your server makes one API call with that token and a duration in minutes, and gets back a link that unlocks timed internet. Set it up in Integrations: turn it on, set your Redirect URL, generate an API key, and (optionally) add Wallet Garden domains your portal needs.

Most WiFi Monetization flows use MKController’s own screens to collect data and take payment. The External Portal flips that: instead of paying on our captive, the guest is handed to your own external portal — your website, your checkout, your rules.

The mechanics are simple. MKController generates a session token for the guest, and with that token you decide who gets access and for how long. When you’re ready, you exchange the token for a redirect link that grants the guest a timed internet session on the MikroTik.

It is configured per voucher, and it is mutually exclusive with the built-in collect-data / payment flow — when the External Portal is on, the guest goes straight to your portal.

  • You already run a payment portal or membership system and want the Wi-Fi to use it.
  • You need a currency, checkout, or business rule the built-in flow doesn’t cover.
  • You’re an integrator building a custom guest experience on top of MKController.

If you just want guests to pay by card, Pix, or mobile money, use the built-in Payment Gateways and Pix instead — they need no code.

Open WiFi Monetization, choose your voucher, open its configuration, and go to the Integrations section. Find the External Portal card.

Step 1: Turn it on and set the Redirect URL

Section titled “Step 1: Turn it on and set the Redirect URL”

Enable External Portal and enter the Redirect URL of your own portal — the page guests land on to pay or sign in.

Generate the company API key used to authenticate the session call. Copy it right away — the full key is shown only once (you can always Rotate it to get a new one). This is the same key surfaced under Voucher → Integrations → External Portal.

Step 3: Add Wallet Garden domains (optional)

Section titled “Step 3: Add Wallet Garden domains (optional)”

Wallet Garden is the list of extra domains your portal needs to reach before the guest is authenticated — for example your payment gateway or a CDN/library host. Add each domain so the captive lets that traffic through pre-login. Click Add for each, then Save.

  1. A guest connects and hits the captive. Because the External Portal is on, MKController mints a one-time sessionId and redirects the guest to your portal:

    <REDIRECT_URL>?sessionId=<one-time-token>
  2. Your portal reads sessionId from the URL and runs your flow (payment, login, whatever you need).

  3. When the guest is cleared, your server calls the MKController session endpoint (below) with the sessionId and the number of minutes to grant, authenticated with your API key.

  4. MKController opens a codeless session on the MikroTik (no personal data, just the device) and returns a redirectUrl. Open it in the guest’s browser to complete the router login — internet is now live for the duration you set.

The sessionId is single-use and expires after 30 minutes, so a token can’t be replayed or shared.

Your portal’s server makes this call (keep the API key on the server — never in the browser).

Request

Terminal window
curl -X POST 'https://app.mkcontroller.com/app/mkcontroller-server/rest/voucherExternalPortal/session' \
-H 'Authorization: Bearer mk_live_your_company_api_key' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'sessionId=SESSION_ID_FROM_THE_URL' \
--data-urlencode 'minutes=60'

Response (200)

{ "data": { "redirectUrl": "https://app.mkcontroller.com/captive/radius_connect.html?username=..." } }

Parameters

FieldWhere it comes fromNotes
AuthorizationVoucher → Integrations → External PortalBearer <company API key> (mk_live_… / mk_test_…).
sessionIdThe ?sessionId= your portal receivedSingle-use, 30-minute TTL.
minutesYou decideDuration of the internet session to grant.
redirectUrlReturned by the callOpen it in the guest’s browser to complete the MikroTik login.

Errors

  • 401 — invalid API key, or a sessionId that belongs to another company.
  • 400sessionId is invalid, already consumed, or expired.
  • Keep the API key on your server. Anyone with it can open sessions for your account — rotate it if it leaks.
  • The sessionId is single-use with a 30-minute TTL — it can’t be reused after you redeem it.
  • Sessions opened this way carry no personal data — only the device’s MAC — so nothing sensitive rides through the handoff.

Do I need to write code? Yes — the External Portal is for teams running their own portal. If you don’t need that, use the built-in Payment Gateways or Pix instead.

Can I use my own currency or checkout? Yes. Payment happens entirely on your portal; MKController only unlocks access when you make the session call.

What’s the Wallet Garden for? It whitelists the extra domains your portal must reach before the guest logs in (payment gateway, CDN, libraries). Without it, that traffic is blocked pre-authentication.

How long is the session token valid? sessionId is single-use and expires after 30 minutes.

Where do the API key and Redirect URL live? In the voucher’s Integrations → External Portal card.


Explore next: Configure Payment Gateways · Receive Pix Payments · Link a Device · Get Paid — Payout & Financeiro

Building an integration? Talk to our team on WhatsApp or start your free MKController account.

Appendix: Minimal One-File Portal (for testing)

Section titled “Appendix: Minimal One-File Portal (for testing)”

A tiny self-contained page that simulates the portal’s final step: it reads sessionId from the URL, sends the session call, and redirects the guest. Paste your company API key into API_KEY, host it anywhere (or on your MikroTik under the Wallet Garden), and open it with ?sessionId=<token>.

⚠️ This exposes the API key in the browser — it’s for testing only. In production, make the session call from your server.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Portal — Grant access</title>
<style>
*{box-sizing:border-box}
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
background:#0f141b;color:#e6edf3;
font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
.card{width:100%;max-width:380px;margin:18px;padding:22px;
background:#182230;border:1px solid #2c3a4d;border-radius:14px}
h1{font-size:19px;margin:0 0 16px}
label{display:block;font-size:12px;color:#8b9bb0;margin:14px 0 5px}
.val{font-family:"SFMono-Regular",Consolas,Menlo,monospace;font-size:13px;
background:#0b1017;border:1px solid #2c3a4d;border-radius:8px;padding:9px 10px;
word-break:break-all;color:#3ea6ff}
input{width:100%;padding:10px;border-radius:8px;border:1px solid #2c3a4d;
background:#1f2b3a;color:#e6edf3;font-size:15px}
input:focus{outline:none;border-color:#3ea6ff}
button{width:100%;margin-top:20px;padding:13px;border:none;border-radius:8px;
background:#3ea6ff;color:#001;font-size:16px;font-weight:700;cursor:pointer}
button:disabled{opacity:.5;cursor:not-allowed}
.msg{margin-top:14px;font-size:13px;min-height:18px}
.err{color:#f85149} .ok{color:#3fb950}
</style>
</head>
<body>
<div class="card">
<h1>Grant access</h1>
<label>Session ID</label>
<div id="sid" class="val"></div>
<label for="minutes">Session length (minutes)</label>
<input id="minutes" type="number" inputmode="numeric" min="1" value="60">
<button id="go">Continue</button>
<div id="msg" class="msg"></div>
</div>
<script>
(function(){
"use strict";
// ─────────── SET THESE ───────────
var API_KEY = "PASTE_YOUR_API_KEY_HERE"; // company key (mk_live_… / mk_test_…)
var BASE_URL = "https://app.mkcontroller.com/app/mkcontroller-server/rest";
// ──────────────────────────────────
var byId = function(id){ return document.getElementById(id); };
// sessionId comes from the URL (?sessionId=...) — the captive redirects here with it
function qs(name){
var m = new RegExp("[?&]"+name+"=([^&]*)").exec(window.location.search);
return m ? decodeURIComponent(m[1].replace(/\+/g," ")) : "";
}
var sessionId = qs("sessionId");
byId("sid").textContent = sessionId || "(no sessionId in the URL)";
function setMsg(text, cls){
var m = byId("msg"); m.textContent = text; m.className = "msg " + (cls||"");
}
byId("go").addEventListener("click", function(){
var minutes = parseInt(byId("minutes").value, 10);
if(!sessionId){ setMsg("No sessionId in the URL.", "err"); return; }
if(!minutes || minutes < 1){ setMsg("Enter the time in minutes.", "err"); return; }
if(API_KEY === "PASTE_YOUR_API_KEY_HERE" || !API_KEY){ setMsg("API key not set in the file.", "err"); return; }
var btn = byId("go"); btn.disabled = true;
setMsg("Granting…", "");
var body = "sessionId=" + encodeURIComponent(sessionId) + "&minutes=" + encodeURIComponent(minutes);
fetch(BASE_URL + "/voucherExternalPortal/session", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Bearer " + API_KEY
},
body: body
})
.then(function(res){ return res.text().then(function(t){ return {ok:res.ok, status:res.status, text:t}; }); })
.then(function(r){
var json = null; try{ json = JSON.parse(r.text); }catch(e){}
var redirectUrl = json && json.data && json.data.redirectUrl;
if(r.ok && redirectUrl){
setMsg("Access granted. Redirecting…", "ok");
window.location.href = redirectUrl; // completes the MikroTik login
}else{
btn.disabled = false;
setMsg("Failed (HTTP " + r.status + "). " + (r.text || ""), "err");
}
})
.catch(function(e){
btn.disabled = false;
setMsg("Network error: " + e, "err");
});
});
})();
</script>
</body>
</html>