MONA Pay API authentication: Bearer token and X-Client-Secret

Every MONA Pay API call needs the header Authorization: Bearer <access_token>. The token comes from POST /api/v1/client/login using the username and password of your my.monapay.vn account and is valid for 86,400 seconds (24 hours). Write calls (POST, PUT, DELETE) add the header X-Client-Secret with a secret generated under API keys. Every response is wrapped in the same envelope {"success": true, "message": "...", "data": ...}.

Base URL

Environment URL
Production https://api.monapay.vn
Legacy alias (still running, for pre-2026 integrations) https://ipn.mona.host

Accounts are usable immediately after sign-up: log in, create an API key, no approval step. A separate sandbox (simulated data, no real bank) is in progress.

Common response envelope

Every endpoint returns the same envelope, including on errors:

{
  "success": true,
  "message": "Success",
  "data": { }
}
Field Type Meaning
success boolean true when processed, false on a business error
message string Short message for logging
data object / array / null Returned data, null if none

Validation errors (missing field, wrong type) return HTTP 422 in the FastAPI style with a detail[] list of the offending fields.

Two layers of authentication

Layer Header Used for Where to get it
Bearer token Authorization: Bearer <access_token> Every request (except sign-up and login) POST /api/v1/client/login
Client secret X-Client-Secret: <client_secret> POST, PUT, DELETE POST /api/v1/client-keys/generate

Keep the token and the secret in environment variables, never hard-coded, never committed to git.

POST /api/v1/client/register-client

Creates a new account. No authentication. The account is usable immediately; log in right away, no approval. The sign-up form at my.monapay.vn/auth (Sign up tab) calls this same endpoint.

Field Type Required Notes
username string (1-255) yes Login name
password string (1-255) yes Password
name string (1-255) yes Business name / display name
curl -X POST https://api.monapay.vn/api/v1/client/register-client \
  -H 'Content-Type: application/json' \
  -d '{"username":"shopabc","password":"StrongPassword#2026","name":"Shop ABC"}'

Response 200/201:

{ "success": true, "message": "Client registered successfully", "data": {} }

POST /api/v1/client/login

Exchanges username + password for an access_token. No authentication.

Field Type Required
username string yes
password string yes

Response 200:

{
  "success": true,
  "message": "Login successful",
  "data": {
    "access_token": "Zx9...64-url-safe-characters",
    "expires_in": 86400,
    "token_type": "Bearer"
  }
}

expires_in is in seconds. When it expires, log in again; there is no refresh token. If the account has 2FA enabled in the dashboard, web login asks for an OTP as well; for direct API calls use an account without 2FA or contact us.

cURL

curl -X POST https://api.monapay.vn/api/v1/client/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"shopabc","password":"StrongPassword#2026"}'

PHP

<?php
$res = json_decode(file_get_contents('https://api.monapay.vn/api/v1/client/login', false, stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => "Content-Type: application/json\r\n",
        'content' => json_encode(['username' => getenv('MONA_USER'), 'password' => getenv('MONA_PASS')]),
    ],
])), true);

if (empty($res['success'])) {
    throw new RuntimeException('MONA Pay login failed: ' . ($res['message'] ?? 'unknown'));
}
$accessToken = $res['data']['access_token']; // keep it, valid for 24 hours

Node

const res = await fetch('https://api.monapay.vn/api/v1/client/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username: process.env.MONA_USER, password: process.env.MONA_PASS }),
});
const json = await res.json();
if (!json.success) throw new Error('MONA Pay login failed: ' + json.message);
const accessToken = json.data.access_token; // keep it, valid for 24 hours

GET /api/v1/client/me

Information about the logged-in account. Needs Bearer.

curl https://api.monapay.vn/api/v1/client/me \
  -H "Authorization: Bearer $MONA_TOKEN"

Response: data contains the client record (id, username, name, active status, creation time).

PUT /api/v1/client/change-password

Changes your own password. Needs Bearer + X-Client-Secret.

Field Type Required
old_password string yes
new_password string yes
curl -X PUT https://api.monapay.vn/api/v1/client/change-password \
  -H "Authorization: Bearer $MONA_TOKEN" \
  -H "X-Client-Secret: $MONA_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"old_password":"StrongPassword#2026","new_password":"NewPassword#2026"}'

Enforcement status (checked 28/08/2026): the production server does not yet reject write calls that lack X-Client-Secret; the enforcing update is written and waiting to be deployed. Send the header now so nothing changes for you when it is switched on.

After changing the password the old token keeps working until it expires; to cut it off immediately, log in again and revoke the key under API keys.

Common errors

HTTP Cause Fix
401 Wrong username/password or expired token Check the credentials (new accounts can log in immediately, there is no approval queue); log in again if the token expired
401 Authorization scheme must be Bearer Malformed header Use Authorization: Bearer <token> with a space after Bearer
422 Missing required field or wrong type Read detail[] in the body and fix the field names
500 MONA Pay-side error Retry after a few seconds; if it repeats, report it to us with the message

Next steps

  1. Create an API key to get X-Client-Secret.
  2. Link an ACB account and create virtual accounts.
  3. Configure webhooks to receive incoming payments.