Documentation Index Fetch the complete documentation index at: https://mintlify.com/sistemashm24/pagos_hotspot_api/llms.txt
Use this file to discover all available pages before exploring further.
A MikroTik captive portal is typically a plain HTML page — hosted on the router itself or on an external web server — that MikroTik redirects unauthenticated clients to when they first join the network. The Pagos Hotspot API turns that static page into a full point-of-sale: fetch available plans, collect and tokenize a card, and hand back internet credentials in one round-trip. This guide covers every API call you need to implement and provides complete JavaScript examples.
The captive portal page runs entirely in the browser. Your server never sees raw card numbers — only a single-use token produced by Conekta.js or the Mercado Pago SDK on the client side.
Overview: Three API Calls
Every captive portal integration needs exactly three API interactions:
1. Public Config GET /api/v1/config/publicFetches the payment gateway public keys. Call this once on page load.
2. Plan Catalog GET /api/v1/catalogo_perfiles_ventaLists the available internet plans for this router. Render them as selectable cards.
3. Process Payment POST /api/v1/payments/pagar-conekta
or
POST /api/v1/payments/pagar-mercado-pagoSubmits the card token and triggers the full purchase flow.
Getting the Client MAC and IP Address
MikroTik injects the connecting device’s MAC address and IP into the captive portal redirect URL using built-in variables. When it redirects to your portal page, the URL looks like:
https://portal.example.com/?mac=AA:BB:CC:DD:EE:FF&ip=192.168.88.100&ssid=WiFi-HotSpot
Parse these at page load with standard URL query parameter handling:
const params = new URLSearchParams ( window . location . search );
const clientMac = params . get ( "mac" ) || "" ;
const clientIp = params . get ( "ip" ) || "" ;
const clientSsid = params . get ( "ssid" ) || "" ;
console . log ( "Client MAC:" , clientMac );
console . log ( "Client IP:" , clientIp );
These values are passed directly to the payment endpoint so the API can attempt an auto-connection after a successful purchase.
Step 1 — Fetch Public Configuration
Call this endpoint on page load to get the Conekta and Mercado Pago public keys. Do not hardcode keys in your portal HTML.
async function loadPublicConfig ( apiKey ) {
const response = await fetch ( "https://api.tuempresa.com/api/v1/config/public" , {
headers: {
"X-API-Key" : apiKey ,
"Content-Type" : "application/json"
}
});
if ( ! response . ok ) {
throw new Error ( "No se pudo cargar la configuración" );
}
const config = await response . json ();
// config.conekta_public_key → initialize Conekta.js
// config.mercadopago_public_key → initialize MP SDK
return config ;
}
Step 2 — Load the Plan Catalog
Fetch the list of available plans and render them for the customer to choose from.
async function loadCatalog ( apiKey ) {
const response = await fetch ( "https://api.tuempresa.com/api/v1/catalogo_perfiles_venta" , {
headers: {
"X-API-Key" : apiKey
}
});
if ( ! response . ok ) {
throw new Error ( "No se pudo cargar el catálogo" );
}
return await response . json (); // Array of plan objects
}
Step 3 — Tokenize the Card
Before calling the payment endpoint, the raw card data must be tokenized client-side by Conekta.js. The API never receives raw card numbers.
<!-- Load Conekta.js -->
< script src = "https://cdn.conekta.io/js/latest/conekta.js" ></ script >
function tokenizeCard ( cardData ) {
return new Promise (( resolve , reject ) => {
Conekta . Token . create (
{
card: {
number: cardData . number ,
name: cardData . name ,
exp_year: cardData . exp_year , // e.g., "26"
exp_month: cardData . exp_month , // e.g., "12"
cvc: cardData . cvc
}
},
( token ) => resolve ( token . id ),
( err ) => reject ( new Error ( err . message_to_purchaser || "Error al procesar tarjeta" ))
);
});
}
Step 4 — Process the Payment
Submit the card token alongside the selected plan and the client’s MAC/IP. Pass auto_connect: true to trigger automatic internet access after a successful charge.
async function payWithConekta ({ apiKey , productId , cardToken , customer , mac , ip }) {
const response = await fetch ( "https://api.tuempresa.com/api/v1/payments/pagar-conekta" , {
method: "POST" ,
headers: {
"X-API-Key" : apiKey ,
"Content-Type" : "application/json"
},
body: JSON . stringify ({
product_id: productId ,
card_token: cardToken ,
customer_name: customer . name ,
customer_email: customer . email ,
customer_phone: customer . phone ,
user_type: "usuario_contrasena" ,
mac_address: mac ,
ip_address: ip ,
auto_connect: true
})
});
if ( ! response . ok ) {
const err = await response . json ();
throw new Error ( err . detail || "Error al procesar el pago" );
}
return await response . json ();
}
Step 5 — Handle the Payment Response
Once the API returns, check auto_conexion.estado to decide what to show the user.
function handlePaymentResult ( result ) {
const { usuario_hotspot , auto_conexion , producto } = result ;
if ( auto_conexion . estado === "conectado" ) {
// Device is already online — show a success screen
showSuccessScreen ({
message: "¡Ya estás conectado! Disfruta de " + producto . nombre ,
credentials: usuario_hotspot // Show as backup in case they need to reconnect
});
} else {
// Not auto-connected — show credentials for manual login
showCredentialsScreen ({
username: usuario_hotspot . usuario ,
password: usuario_hotspot . contrasena ,
message: auto_conexion . mensaje
});
}
// Always persist credentials for future auto-reconnect attempts
localStorage . setItem ( "hs_username" , usuario_hotspot . usuario );
localStorage . setItem ( "hs_password" , usuario_hotspot . contrasena );
localStorage . setItem ( "hs_stored_mac" , auto_conexion . mac || "" );
}
Returning User Flow — Auto-Reconnect on Page Load
When the captive portal page loads, check localStorage for saved credentials. If found, attempt auto-reconnect before showing the purchase UI.
async function tryAutoReconnect ( apiKey , currentMac , currentIp ) {
const username = localStorage . getItem ( "hs_username" );
const password = localStorage . getItem ( "hs_password" );
const storedMac = localStorage . getItem ( "hs_stored_mac" );
if ( ! username || ! password ) return false ; // No saved session
try {
const response = await fetch ( "https://api.tuempresa.com/api/v1/hotspot/auto-reconnect" , {
method: "POST" ,
headers: {
"X-API-Key" : apiKey ,
"Content-Type" : "application/json"
},
body: JSON . stringify ({
username: username ,
password: password ,
stored_mac: storedMac ,
current_mac: currentMac ,
current_ip: currentIp
})
});
const data = await response . json ();
if ( data . auto_conexion === "conectado" ) {
showSuccessScreen ({ message: "Reconectado exitosamente. ¡Bienvenido de vuelta!" });
return true ;
}
if ( data . estado === "expirado" ) {
// Plan expired — clear credentials and show the purchase UI
localStorage . removeItem ( "hs_username" );
localStorage . removeItem ( "hs_password" );
localStorage . removeItem ( "hs_stored_mac" );
}
return false ;
} catch ( err ) {
console . warn ( "Auto-reconnect failed:" , err );
return false ;
}
}
On page load:
( async () => {
const params = new URLSearchParams ( window . location . search );
const currentMac = params . get ( "mac" ) || "" ;
const currentIp = params . get ( "ip" ) || "" ;
const API_KEY = "jwt_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ; // from your backend config
const reconnected = await tryAutoReconnect ( API_KEY , currentMac , currentIp );
if ( ! reconnected ) {
// Load config and catalog for the purchase UI
const config = await loadPublicConfig ( API_KEY );
Conekta . setPublicKey ( config . conekta_public_key );
const plans = await loadCatalog ( API_KEY );
renderPlans ( plans );
showPurchaseUI ();
}
})();
CORS Configuration
The API server must allow requests from your captive portal’s origin. Add the portal domain to the BACKEND_CORS_ORIGINS environment variable on the API server.
If your portal is served directly from the MikroTik router (e.g., http://192.168.88.1/), add that IP to the CORS origins list. For externally hosted portals, add the full domain including protocol and port. BACKEND_CORS_ORIGINS = ["https://portal.tuempresa.com","http://192.168.88.1" ]
Test Card Numbers
Use the following card numbers in Conekta’s sandbox environment to test different scenarios without real charges:
Card Number Result 4242 4242 4242 4242Approved (paid) 4000 0000 0000 0002Declined CVV: 123 Any of the above Expiry: Any future date Any of the above