> For the complete documentation index, see [llms.txt](https://docs.xpayconnect.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xpayconnect.io/documentation/eng/concepts/auth.md).

# Authorization

Every API request must include a signature in the **x-api-key** header. The signature is formed based on the API key and the request body.

## Headers

| Header             | Description                                            |
| ------------------ | ------------------------------------------------------ |
| **client-api-key** | API key in plain text                                  |
| **x-api-key**      | SHA-256 hash of the string `<API_KEY>\|<request_body>` |

## Generating the Signature

1. Concatenate the API key with the request body (JSON string), using `|` as a separator
2. Compute the SHA-256 hash of the resulting string
3. Pass the result in the **x-api-key** header

{% hint style="warning" %}
The JSON string of the request body must not contain spaces. In Python, use `json.dumps(body, separators=(',', ':'))`. In JavaScript, `JSON.stringify()` does not add spaces by default.
{% endhint %}

### Verification example (for debugging)

Use this reference set of values to confirm that your signature implementation is correct. If you plug in the same inputs and get the same `x-api-key` — your formula is implemented correctly.

```
API_KEY (client-api-key):
  sk_test_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

BODY (no spaces, exactly this string):
  {"order_id":"01a00c1a-9f0e-7e42-b8a5-2a1c3d4e5f6a","amount":2300,"type":"sim","merchant_id":"exMerchant"}

String to hash (API_KEY + "|" + BODY):
  sk_test_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6|{"order_id":"01a00c1a-9f0e-7e42-b8a5-2a1c3d4e5f6a","amount":2300,"type":"sim","merchant_id":"exMerchant"}

Expected x-api-key (SHA-256, hex):
  bf942d10d50042bf5541b9a6219914e2b1a09e222931a7ef679753afb045f21d
```

For a GET request without a body using the same key:

```
String to hash (API_KEY + "|" + empty body):
  sk_test_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6|

Expected x-api-key:
  96edfb653fd225e58578db2e7066648956575b8995aacf04fc4832dc1f080142
```

{% hint style="info" %}
If your `x-api-key` does not match the expected value — the issue is almost always in the body: whitespace, Unicode escaping, or a different key order. Log the actual JSON string before hashing and compare it byte-by-byte with the reference.
{% endhint %}

### Examples for POST Requests

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const crypto = require('crypto');
const apiKey = 'YOUR_API_KEY';
const body = { order_id: 'test-001', amount: 2300, type: 'sim', merchant_id: 'exMerchant' };

const bodyStr = JSON.stringify(body);
const xApiKey = crypto.createHash('sha256').update(`${apiKey}|${bodyStr}`).digest('hex');

// Заголовки запроса:
// 'client-api-key': apiKey
// 'x-api-key': xApiKey
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib
import json

api_key = 'YOUR_API_KEY'
body = {'order_id': 'test-001', 'amount': 2300, 'type': 'sim', 'merchant_id': 'exMerchant'}

body_str = json.dumps(body, separators=(',', ':'))  # без пробелов!
x_api_key = hashlib.sha256(f'{api_key}|{body_str}'.encode()).hexdigest()
```

{% endtab %}

{% tab title="PHP" %}

```php
$apiKey = 'YOUR_API_KEY';
$body = ['order_id' => 'test-001', 'amount' => 2300, 'type' => 'sim', 'merchant_id' => 'exMerchant'];

$bodyStr = json_encode($body, JSON_UNESCAPED_UNICODE);
$xApiKey = hash('sha256', $apiKey . '|' . $bodyStr);
```

{% endtab %}
{% endtabs %}

### GET Requests Without a Body

For GET requests or requests without a body, use an empty string in place of the body:

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const xApiKey = crypto.createHash('sha256').update(`${apiKey}|`).digest('hex');
```

{% endtab %}

{% tab title="Python" %}

```python
x_api_key = hashlib.sha256(f'{api_key}|'.encode()).hexdigest()
```

{% endtab %}

{% tab title="PHP" %}

```php
$xApiKey = hash('sha256', $apiKey . '|');
```

{% endtab %}
{% endtabs %}

## Authorization Errors

If the key is missing, invalid, or does not belong to the merchant — the API returns an error in the form `{ "success": false, "message": "..." }`. Use the table below to map the `message` value to the underlying cause.

| HTTP | `message`                            | Cause                                                                                                      |
| ---- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| 401  | `Missing Client-API-Key header`      | The `client-api-key` header was not sent                                                                   |
| 401  | `Missing X-API-Key header`           | The `x-api-key` header was not sent                                                                        |
| 401  | `Invalid or inactive Client API key` | API key not found or disabled                                                                              |
| 401  | `Invalid X-API-Key`                  | Signature mismatch — check the `SHA-256(API_KEY \| body)` formula and that the JSON body has no whitespace |
| 403  | `Invalid merchant_id`                | The `merchant_id` in the request does not belong to the key owner                                          |
