API keys: create, list and revoke X-Client-Secret

A MONA Pay API key is the client_secret string generated by POST /api/v1/client-keys/generate. The system shows the secret exactly once at creation and then stores only a hash, so it cannot be viewed again. Send the secret in the X-Client-Secret header on every POST, PUT and DELETE request (together with the Bearer token). If you lose it, create a new key and revoke the old one; an account can have many keys.

When you need a key

Action Needs Bearer Needs X-Client-Secret
Sign up, log in no no
Reading data (GET): transactions, VAs, webhook logs yes no
Writing data (POST, PUT, DELETE): create VA, create QR, configure webhooks/Telegram, change password yes yes

Clean practice: one key per integrated system (store, accounting software, bot), named after that system. If one system leaks its key, revoke that key only; the others are unaffected.

POST /api/v1/client-keys/generate

Needs Bearer.

Field Type Required Notes
name string no A memorable name, default Default Key

Response 200:

{
  "success": true,
  "message": "Key generated successfully",
  "data": {
    "id": "0190a1b2-...",
    "client_id": "0190a0f0-...",
    "client_secret": "mps_...secret-shown-only-once",
    "name": "Online store",
    "is_active": true,
    "created_at": "2026-08-28T10:30:00"
  }
}

Store client_secret in an environment variable (MONA_SECRET) right away. Once the screen is closed it cannot be retrieved.

cURL

curl -X POST https://api.monapay.vn/api/v1/client-keys/generate \
  -H "Authorization: Bearer $MONA_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Online store"}'

PHP

<?php
function monaPost(string $path, array $body, string $token, ?string $secret = null): array {
    $headers = "Content-Type: application/json\r\nAuthorization: Bearer $token\r\n";
    if ($secret) $headers .= "X-Client-Secret: $secret\r\n";
    $raw = file_get_contents('https://api.monapay.vn' . $path, false, stream_context_create([
        'http' => ['method' => 'POST', 'header' => $headers, 'content' => json_encode($body), 'ignore_errors' => true],
    ]));
    return json_decode($raw, true) ?? ['success' => false, 'message' => 'Could not read response'];
}

$res = monaPost('/api/v1/client-keys/generate', ['name' => 'Online store'], getenv('MONA_TOKEN'));
$clientSecret = $res['data']['client_secret'] ?? null; // write to .env, shown only once

Node

const res = await fetch('https://api.monapay.vn/api/v1/client-keys/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.MONA_TOKEN}` },
  body: JSON.stringify({ name: 'Online store' }),
});
const { success, data, message } = await res.json();
if (!success) throw new Error(message);
console.log('Write to .env:', data.client_secret); // shown only once

GET /api/v1/client-keys/list

Lists the account’s keys without secrets. Needs Bearer.

curl https://api.monapay.vn/api/v1/client-keys/list \
  -H "Authorization: Bearer $MONA_TOKEN"
{
  "success": true,
  "message": "Keys retrieved",
  "data": [
    { "id": "0190a1b2-...", "client_id": "0190a0f0-...", "name": "Online store", "is_active": true, "created_at": "2026-08-28T10:30:00" }
  ]
}

DELETE /api/v1/client-keys/destroy/{key_id}

Revokes a key. Needs Bearer. Requests using this key’s secret are rejected immediately after revocation.

curl -X DELETE https://api.monapay.vn/api/v1/client-keys/destroy/0190a1b2-... \
  -H "Authorization: Bearer $MONA_TOKEN"

Response: {"success": true, "message": "Key destroyed", "data": null}. A key that does not exist or does not belong to the account returns 404.

Using the secret in a write request

curl -X POST https://api.monapay.vn/api/v1/client-webhooks \
  -H "Authorization: Bearer $MONA_TOKEN" \
  -H "X-Client-Secret: $MONA_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Online store","webhook_url":"https://shop.example.com/webhook/monapay","auth_type":"HMAC_SHA256","secret_key":"your-hmac-secret"}'

Note the two different secrets: client_secret (X-Client-Secret) is for you to call the MONA Pay API; secret_key in a webhook configuration is for MONA Pay to sign payloads sent to your server (see Webhook security). They should be different strings.

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.

Common errors

HTTP Cause Fix
401 Missing or wrong Bearer token Log in again for a new token
404 key_id does not exist or does not belong to the account Call /client-keys/list for the right id
422 key_id is not a valid UUID Copy the id exactly from the list

Keys can also be created and revoked in the dashboard under API Keys, which includes a “copy prompt for AI agent” block to paste into Claude Code, Codex or Cursor.