> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xpaylabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HMAC-SHA256: XPayLabs API Authentication Reference

> Complete reference for computing HMAC-SHA256 signatures to authenticate XPayLabs API requests, including the signing algorithm and per-request fields.

Every API request to XPayLabs must include a cryptographic signature. This page details the exact signing algorithm, the purpose of each field, and common pitfalls.

## Signing Algorithm

The `sign` field is computed as:

```
sign = HEX(HMAC-SHA256(JSON.stringify(data), merchant_token))
```

Where:

* `data` is the request body payload (the `data` field of `ReqPayload`)
* `merchant_token` is your configured secret token
* `HMAC-SHA256` produces a binary digest
* `HEX` converts it to a lowercase hexadecimal string (64 characters)

## Important: JSON Serialization

The `data` object must be serialized to JSON **with no extra whitespace**. Different JSON libraries may produce different output:

| Library                                 | Output                                               | Valid? |
| --------------------------------------- | ---------------------------------------------------- | ------ |
| `JSON.stringify(obj)`                   | `{"amount":"100.00","symbol":"USDT","chain":"TRON"}` | ✅ Yes  |
| `json.dumps(obj, separators=(",",":"))` | `{"amount":"100.00","symbol":"USDT","chain":"TRON"}` | ✅ Yes  |
| `json.dumps(obj, indent=2)`             | Formatted with spaces/newlines                       | ❌ No   |

<Warning>
  The signature is computed over the `data` field only — not over the full `ReqPayload` envelope. The `sign`, `timestamp`, and `nonce` fields are excluded from signing.
</Warning>

## Request Envelope

Each request must include these four fields as a JSON object:

```json theme={null}
{
  "sign": "a1b2c3d4e5f6...",
  "timestamp": 1717000000,
  "nonce": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "amount": "100.00",
    "symbol": "USDT",
    "chain": "TRON"
  }
}
```

## Language Examples

### Node.js

```javascript theme={null}
import crypto from "crypto";

function sortKeys(obj) {
  return Object.keys(obj)
    .sort()
    .reduce((acc, k) => {
      acc[k] = obj[k];
      return acc;
    }, {});
}

function signRequest(data, token) {
  return crypto
    .createHmac("sha256", token)
    .update(JSON.stringify(sortKeys(data)), "utf8")
    .digest("hex");
}
```

### Python

```python theme={null}
import hmac, hashlib, json

def sign_request(data: dict, token: str) -> str:
    json_str = json.dumps(data, separators=(",", ":"), sort_keys=True)
    return hmac.new(token.encode(), json_str.encode(), hashlib.sha256).hexdigest()
```

### Java

```java theme={null}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public static String signRequest(Object data, String token) throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
    String json = mapper.writeValueAsString(data);
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(token.getBytes("UTF-8"), "HmacSHA256"));
    byte[] digest = mac.doFinal(json.getBytes("UTF-8"));
    return bytesToHex(digest);
}
```

### Go

```go theme={null}
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
)

func SignRequest(data interface{}, token string) string {
    jsonBytes, _ := json.Marshal(data)  // compact JSON
    mac := hmac.New(sha256.New, []byte(token))
    mac.Write(jsonBytes)
    return hex.EncodeToString(mac.Sum(nil))
}
```

## Verification

XPayLabs verifies each request by:

1. Recomputing the HMAC-SHA256 signature from the `data` field and your stored merchant token.
2. Checking that the computed signature matches the provided `sign` value.
3. Validating that the `timestamp` is within 5 minutes of the server clock.
4. Ensuring the `nonce` has not been used before.

If any check fails, the gateway returns a `401 Unauthorized` response:

```json theme={null}
{
  "code": 401,
  "msg": "Sign verification failed",
  "data": null
}
```

## GET Request Signing

For `GET` requests that require authentication (e.g., `/v1/order/status/{orderId}`, `/v1/order/pay`, `/v1/order/getOrderStatus`), the `sign` parameter is passed as a query string value. The signature is computed over the relevant parameters (typically `orderId`):

```
sign = HEX(HMAC-SHA256(orderId, merchant_token))
```
