> For the complete documentation index, see [llms.txt](https://docs.forepaas.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forepaas.org/v2/getting-started/authentication.md).

# Authentication

Learn how to authenticate with the ForeGate PaaS API using gateway HMAC-SHA256 signatures, API keys, and user sessions

ForeGate PaaS API traffic on `openapi.foregate.com` — REST and `/socket/*` WebSocket channels — passes through an Aliyun API Gateway. Every request to that host must be signed with an HMAC-SHA256 gateway signature, must identify your application with an API key, and (for user-scoped endpoints) must carry a user session cookie.

The public price-history endpoint and market WebSocket on `openapiws.foregate.com` are the exception: they require no gateway signature, API key, or session cookie. See [Public Market Data](/v2/api-reference/api-reference/public-market-data.md).

{% hint style="warning" %}
**Security Notice:** Never expose your APP Secret or API key in client-side code, version control, or public repositories. The APP Secret is never sent over the wire — it is only used locally to compute signatures. If a secret is ever exposed, contact your ForeGate account manager immediately to rotate it.
{% endhint %}

## Your Credentials

ForeGate provides three credentials when you onboard:

| Credential     | Purpose                                                       | Where it appears                                                   |
| -------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
| **APP Key**    | Identifies your application to the gateway                    | Sent as the `x-ca-key` header                                      |
| **APP Secret** | Signs requests (HMAC-SHA256)                                  | Never sent — used only to compute `x-ca-signature`                 |
| **API Key**    | Identifies your partner account to the ForeGate service layer | Sent as the `x-api-key` header (and participates in the signature) |

{% hint style="info" %}
The **APP Key** and the **API Key** are different values with different jobs. The APP Key/Secret pair satisfies the gateway; the API key satisfies the ForeGate business layer. You need all three credentials on every PaaS request to `openapi.foregate.com`; none are sent to `openapiws.foregate.com`.
{% endhint %}

## Authentication Matrix

| Endpoint type                                                                                                           | Gateway signature | `x-api-key` |            Session `Cookie`           |
| ----------------------------------------------------------------------------------------------------------------------- | :---------------: | :---------: | :-----------------------------------: |
| Market data (`/markets/*`, `/search`)                                                                                   |         ✅         |      ✅      |                   —                   |
| User registration (`POST /user/register`)                                                                               |         ✅         |      ✅      | — (this call **creates** the session) |
| Public user profile (`GET /user/{id}`)                                                                                  |         ✅         |      ✅      |                   —                   |
| User-scoped REST (`/user/profile`, `/deposit/*`, `/withdraw/*`, `/account/*`, `/orderbook/*`, `/ordering/*`, `/trades`) |         ✅         |      ✅      |                   ✅                   |
| WebSocket market channels (`/socket/clob-price-update`, `/socket/market-price`)                                         |   ✅ (at upgrade)  |      ✅      |                   —                   |
| WebSocket user channels (`/socket/user-info`, `/socket/user-positions`, `/socket/user-open-orders`)                     |   ✅ (at upgrade)  |      ✅      |            ✅ (also signed)            |

## Required Request Headers

Every request to the signed PaaS host must include the following wire-level headers:

| Header                   | Required              | Description                                                                                                                                         |
| ------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-ca-key`               | Yes                   | Your APP Key, verbatim.                                                                                                                             |
| `x-ca-signature`         | Yes                   | HMAC-SHA256 of the [string-to-sign](#the-string-to-sign), keyed with your APP Secret, base64-encoded.                                               |
| `x-ca-signature-headers` | Yes                   | The custom headers that participate in the signature — lowercase names, alphabetical order, comma-separated. For REST requests this is `x-api-key`. |
| `x-api-key`              | Yes                   | Your ForeGate API key. Participates in the signature.                                                                                               |
| `accept`                 | Yes                   | Always `application/json`.                                                                                                                          |
| `date`                   | Yes                   | Format `YYYY-MM-DD HH:mm:ss` in **UTC**. Must be within \~15 minutes of server time.                                                                |
| `content-type`           | POST/PUT              | Always `application/json`.                                                                                                                          |
| `content-md5`            | POST/PUT              | `base64(md5(request body))`. GET and other body-less requests leave it empty — but its position in the string-to-sign remains.                      |
| `Cookie`                 | User-scoped endpoints | The session cookie returned by `POST /user/register` via `Set-Cookie`.                                                                              |

## The String-to-Sign

Concatenate the following components with `\n` (newline):

```
{METHOD}
{accept}
{content-md5}
{content-type}
{date}
{signed custom headers}
{path}
```

Component rules:

1. **METHOD** — uppercase HTTP method (`GET`, `POST`, …). WebSocket upgrades sign as `GET`.
2. **accept** — `application/json`.
3. **content-md5** — `base64(md5(body))` for POST/PUT; an **empty line** for body-less requests (the line must still be present).
4. **content-type** — `application/json` for POST/PUT; an **empty line** for body-less requests.
5. **date** — the exact value of your `date` header.
6. **Signed custom headers** — one line per header listed in `x-ca-signature-headers`, formatted as `name:value`, names lowercase, sorted alphabetically. For REST requests the list is just `x-api-key`, so this is a single line: `x-api-key:<X-API-KEY>`. WebSocket user channels also sign the cookie (`cookie:<value>` sorts before `x-api-key:<value>`).
7. **path** — the request path. When the request has query parameters, append them sorted alphabetically by key, **without URL encoding** (e.g. `/socket/user-positions?marketId=<v>&userId=<v>`).

**Example** — string-to-sign for `POST /user/register`:

```
POST
application/json
<base64 md5 of body>
application/json
2026-06-01 17:51:23
x-api-key:<X-API-KEY>
/user/register
```

**Example** — string-to-sign for a body-less `GET` (note the two empty lines where `content-md5` and `content-type` would be):

```
GET
application/json


2026-06-01 17:51:23
x-api-key:<X-API-KEY>
/markets/list?page=1&pageSize=10
```

## Computing the Signature

{% tabs %}
{% tab title="Node.js" %}

```javascript
import crypto from 'crypto';

const stringToSign = [
  method,                       // e.g. 'POST'
  'application/json',           // accept
  contentMd5,                   // '' for GET
  contentType,                  // '' for GET, 'application/json' for POST/PUT
  date,                         // 'YYYY-MM-DD HH:mm:ss' (UTC)
  'x-api-key:' + apiKey,
  path,                         // incl. sorted query string, if any
].join('\n');

const signature = crypto
  .createHmac('sha256', appSecret)
  .update(stringToSign, 'utf8')
  .digest('base64');            // → x-ca-signature
```

{% endtab %}

{% tab title="Python" %}

```python
import base64
import hashlib
import hmac

string_to_sign = "\n".join([
    method,                      # e.g. "POST"
    "application/json",          # accept
    content_md5,                 # "" for GET
    content_type,                # "" for GET, "application/json" for POST/PUT
    date,                        # "YYYY-MM-DD HH:mm:ss" (UTC)
    "x-api-key:" + api_key,
    path,                        # incl. sorted query string, if any
])

signature = base64.b64encode(
    hmac.new(app_secret.encode(), string_to_sign.encode(), hashlib.sha256).digest()
).decode()                       # → x-ca-signature
```

{% endtab %}
{% endtabs %}

### Computing `content-md5` (POST/PUT only)

{% tabs %}
{% tab title="Node.js" %}

```javascript
const contentMd5 = crypto
  .createHash('md5')
  .update(body, 'utf8')          // the exact raw body string you send
  .digest('base64');             // → content-md5
```

{% endtab %}

{% tab title="Python" %}

```python
content_md5 = base64.b64encode(
    hashlib.md5(body.encode()).digest()   # the exact raw body string you send
).decode()                                # → content-md5
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Sign the **exact raw body string** you transmit. If you serialize the body twice (once for the hash, once for the request) and key order or whitespace differs, the gateway will reject the request.
{% endhint %}

## User Sessions

User-scoped endpoints authenticate the end user with a session cookie:

1. Call [`POST /user/register`](/v2/api-reference/api-reference/user.md#register-a-user) for your user. The response carries the session in a `Set-Cookie` header.
2. Store the cookie and send it back in the `Cookie` header on every user-scoped request.
3. If the session becomes invalid or expires you will receive `403 Invalid or expired session` — register again to obtain a fresh session.

{% hint style="warning" %}
For REST requests the cookie is **sent but not signed** (`x-ca-signature-headers` stays `x-api-key`). For the WebSocket user channels the cookie **must also participate in the signature** — see the [WebSocket API](/v2/advanced/websocket-api.md).
{% endhint %}

## Complete Wire-Level Example

A fully assembled `POST /user/register` request (replace the placeholders with your provisioned credentials and a freshly computed signature):

```bash
curl -v --http1.1 -X POST \
  -H 'accept: application/json' \
  -H 'date: 2026-06-01 17:51:23' \
  -H 'x-ca-key: <APP Key>' \
  -H 'x-ca-signature: <base64 HMAC-SHA256>' \
  -H 'content-type: application/json' \
  -H 'content-md5: <base64 md5 of body>' \
  -H 'x-ca-signature-headers: x-api-key' \
  -H 'x-api-key: <X-API-KEY>' \
  --data-raw '{"userEmail":"userciismd@gmail.com","merchantId":"101001","marchantUserId":"mer_ciismd"}' \
  'https://openapi.foregate.com/user/register'
```

<details>

<summary><strong>Debugging Signature Failures</strong></summary>

When the gateway rejects a signature it returns `400 Bad Request` with the plain-text body `Invalid Signature` and — crucially — an `x-ca-error-message` response header containing `Server StringToSign:` followed by the string the server computed. Compare it byte-by-byte with yours. Common mismatches:

1. **Uppercase header names** in `x-ca-signature-headers` or the signed-header lines. The gateway normalizes header names to lowercase before signing; if you sign `X-API-KEY:` you will never match.
2. **Missing empty lines** for `content-md5`/`content-type` on GET requests — the positions must be preserved even when empty.
3. **Query parameters not sorted** alphabetically by key, or URL-encoded when they should not be.
4. **Clock skew** — the `date` header must be within \~15 minutes of server time, in `YYYY-MM-DD HH:mm:ss` in UTC, not ISO 8601.
5. **Body mismatch** — the `content-md5` must be computed over the exact bytes sent.
6. **Wrong secret** — signing with the API key instead of the APP Secret.

A `401 Unauthorized` with body `Invalid Key` means the `x-ca-key` (APP Key) is unknown or not authorized for the API — distinct from a signature mismatch.

</details>
