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

> Generate a client_secret (shown once), list and revoke keys. The secret is sent in the X-Client-Secret header on every POST/PUT/DELETE.
> Source: https://monapay.vn/en/docs/api/api-keys · Updated: 29/08/2026 · MONA Pay is the payment gateway and bank API of The MONA Group that lets Vietnamese businesses receive and confirm bank transfers in real time via virtual accounts (VA), VietQR, webhooks and Telegram — built so both developers and AI agents can integrate in minutes.

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:

```json
{
  "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**

```bash
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
<?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**

```js
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.

```bash
curl https://api.monapay.vn/api/v1/client-keys/list \
  -H "Authorization: Bearer $MONA_TOKEN"
```

```json
{
  "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.

```bash
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

```bash
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](/en/docs/webhooks/bao-mat)). They should be different strings.

<div class="callout warn">

**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.

</div>

## 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.
