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

# Authentication

## Authentication parameters

The Payward Services API uses HMAC signing. Every authenticated request must include the following headers:

* `API-Key` HTTP header parameter: your public API key.
* `API-Nonce` HTTP header parameter: monotonically increasing integer nonce.
* `API-Sign` HTTP header parameter: HMAC-SHA512 signature of the request.

## Setting the API-Key parameter

The value for the `API-Key` HTTP header parameter is your public API key.

Contact your Payward account representative to obtain API credentials.

<Warning>
  From your API key-pair, clearly identify which key is public and which key is private.

  * The public key is sent in the `API-Key` header parameter.
  * The private key is **never** sent, it is only used to encode the signature for `API-Sign` header parameter.
</Warning>

## Setting the API-Nonce parameter

The value for the `API-Nonce` HTTP header parameter must be a monotonically increasing integer for the API key.

Use a high-resolution timestamp, such as nanoseconds since the Unix epoch, unless your integration already has a stronger monotonic counter.

## Setting the API-Sign parameter

The value for the `API-Sign` HTTP header parameter is an HMAC-SHA512 signature of the request, base64-encoded.

```text theme={null}
base64(HMAC-SHA512(secret, path_with_query + SHA256(nonce + body)))
```

### Algorithm steps

1. **Choose a nonce:** generate a monotonically increasing integer.
2. **Build the nonce payload:** concatenate the nonce and the exact request body bytes.
   * For requests without a body: use the nonce only.
   * For requests with a body: use the exact bytes you send on the wire.
3. **Hash the nonce payload:** generate a SHA-256 digest.
4. **Build the message:** concatenate the URL path, including query string, and the SHA-256 digest.
5. **Sign:** generate HMAC-SHA512 over the message using your base64-decoded secret.
6. **Encode:** base64-encode the signature.

### Examples

The following code snippets demonstrate how to generate the signature in Python and JavaScript.

<CodeGroup>
  ```python Python theme={null}
  import hashlib
  import hmac
  import base64
  import time
  import urllib.parse


  def get_payward_signature(urlpath, body, secret, nonce, params=None):
      """
      Generate Payward Services API signature.

      Args:
          urlpath: API endpoint including query string (e.g., '/v1/assets')
          body: Request body string, or None for requests without a body
          secret: Base64-encoded API secret
          nonce: Monotonically increasing integer nonce
          params: Optional query parameters

      Returns:
          Base64-encoded signature string
      """
      nonce_payload = str(nonce).encode("utf-8") if body is None else (str(nonce) + body).encode("utf-8")

      sign_path = urlpath
      if params:
          sign_path += "?" + urllib.parse.urlencode(params)

      message = sign_path.encode("utf-8") + hashlib.sha256(nonce_payload).digest()
      mac = hmac.new(base64.b64decode(secret), message, hashlib.sha512)
      return base64.b64encode(mac.digest()).decode()


  api_secret = "your-api-secret-here"
  endpoint = "/v1/assets"
  nonce = time.time_ns()

  signature = get_payward_signature(endpoint, None, api_secret, nonce)
  print(f"API-Nonce: {nonce}")
  print(f"API-Sign: {signature}")
  ```

  ```javascript Javascript theme={null}
  import crypto from 'crypto';

  /**
   * Generate Payward Services API signature.
   * @param {string} urlpath - API endpoint including query string (e.g., '/v1/assets')
   * @param {string|null} body - Request body string, or null for requests without a body
   * @param {string} secret - Base64-encoded API secret
   * @param {string|number|bigint} nonce - Monotonically increasing integer nonce
   * @param {object|null} params - Optional query parameters
   * @returns {string} Base64-encoded signature
   */
  function getPaywardSignature(urlpath, body, secret, nonce, params = null) {
    const noncePayload = body === null ? String(nonce) : String(nonce) + body;

    let signPath = urlpath;
    if (params) {
      signPath += '?' + new URLSearchParams(params).toString();
    }

    const sha256Hash = crypto.createHash('sha256').update(noncePayload).digest();
    const message = Buffer.concat([Buffer.from(signPath), sha256Hash]);
    const secretBuffer = Buffer.from(secret, 'base64');
    const hmac = crypto.createHmac('sha512', secretBuffer);
    hmac.update(message);

    return hmac.digest('base64');
  }

  const apiSecret = 'your-api-secret-here';
  const endpoint = '/v1/assets';
  const nonce = process.hrtime.bigint().toString();

  const signature = getPaywardSignature(endpoint, null, apiSecret, nonce);
  console.log(`API-Nonce: ${nonce}`);
  console.log(`API-Sign: ${signature}`);
  ```
</CodeGroup>

## Complete request example

Here's a complete example making an authenticated GET request to list assets.

<CodeGroup>
  ```python Python theme={null}
  import os
  import time
  import hashlib
  import hmac
  import base64
  import requests

  API_KEY = os.environ.get("PAYWARD_API_KEY")
  API_SECRET = os.environ.get("PAYWARD_API_SECRET")
  BASE_URL = "https://api.services.payward.com"


  def get_payward_signature(urlpath, body, secret, nonce):
      nonce_payload = str(nonce).encode("utf-8") if body is None else (str(nonce) + body).encode("utf-8")
      message = urlpath.encode("utf-8") + hashlib.sha256(nonce_payload).digest()
      mac = hmac.new(base64.b64decode(secret), message, hashlib.sha512)
      return base64.b64encode(mac.digest()).decode()


  def list_assets():
      endpoint = "/v1/assets"
      nonce = time.time_ns()
      signature = get_payward_signature(endpoint, None, API_SECRET, nonce)

      headers = {
          "API-Key": API_KEY,
          "API-Nonce": str(nonce),
          "API-Sign": signature,
      }

      response = requests.get(BASE_URL + endpoint, headers=headers)
      return response.json()


  assets = list_assets()
  print(assets)
  ```

  ```javascript Javascript theme={null}
  import crypto from 'crypto';

  const API_KEY = process.env.PAYWARD_API_KEY;
  const API_SECRET = process.env.PAYWARD_API_SECRET;
  const BASE_URL = 'https://api.services.payward.com';

  function getPaywardSignature(urlpath, body, secret, nonce) {
    const noncePayload = body === null ? String(nonce) : String(nonce) + body;
    const sha256Hash = crypto.createHash('sha256').update(noncePayload).digest();
    const message = Buffer.concat([Buffer.from(urlpath), sha256Hash]);

    const secretBuffer = Buffer.from(secret, 'base64');
    const hmac = crypto.createHmac('sha512', secretBuffer);
    hmac.update(message);

    return hmac.digest('base64');
  }

  async function listAssets() {
    const endpoint = '/v1/assets';
    const nonce = process.hrtime.bigint().toString();
    const signature = getPaywardSignature(endpoint, null, API_SECRET, nonce);

    const response = await fetch(BASE_URL + endpoint, {
      method: 'GET',
      headers: {
        'API-Key': API_KEY,
        'API-Nonce': nonce,
        'API-Sign': signature,
      },
    });

    return response.json();
  }

  const assets = await listAssets();
  console.log(assets);
  ```
</CodeGroup>

## Query parameters in signature

When your request includes query parameters, they must be included in the URL path used for signature generation. Use the exact same path string in both the signature and the request URL.

```javascript theme={null}
const params = { page_size: 10, quote: 'USD' };
const queryString = new URLSearchParams(params).toString();
const signaturePath = `/v1/assets?${queryString}`;
const nonce = process.hrtime.bigint().toString();

const signature = getPaywardSignature(signaturePath, null, API_SECRET, nonce);
```

## Request body in signature

For requests that send a body (POST, PUT), the body bytes used to compute the signature must match the bytes sent on the wire exactly. Differences in whitespace, key ordering, or encoding will produce a signature mismatch.

The recommended pattern is to serialize the body once and reuse the same string for both signing and the HTTP request:

```javascript theme={null}
const body = JSON.stringify({ from_asset: 'USD', to_asset: 'BTC', amount: '100.00' });
const nonce = process.hrtime.bigint().toString();

const signature = getPaywardSignature('/v1/swap/quote', body, API_SECRET, nonce);

await fetch(`${BASE_URL}/v1/swap/quote`, {
  method: 'POST',
  headers: {
    'API-Key': API_KEY,
    'API-Nonce': nonce,
    'API-Sign': signature,
    'Content-Type': 'application/json',
  },
  body,
});
```

## Troubleshooting

| Error               | Cause                   | Solution                                            |
| ------------------- | ----------------------- | --------------------------------------------------- |
| `Invalid signature` | Signature doesn't match | Verify secret encoding, nonce, path, and body bytes |
| `Missing API-Key`   | Header not set          | Check the header name is exactly `API-Key`          |
| `Invalid nonce`     | Nonce was reused        | Generate a larger nonce for every request           |
