> For the complete documentation index, see [llms.txt](https://docs.allscale.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allscale.io/allscale-checkout/api-reference/api-doc-optional-response-signing.md).

# API Doc - Optional Response Signing

AllScale Open API\
Version: v1\
Last updated: 2026-01-21

***

## Overview

**Applies only when `signed_response = true` for the store**

***

AllScale supports optional response signing to allow clients to verify:

* Response authenticity
* Payload integrity
* Request–response binding
* Protection against replayed or substituted responses

Response signing is **disabled by default**.

It becomes active only when the store is created or configured with:

```
signed_response = true
```

***

### When Response Signing Is Enabled

When enabled, every API response includes additional headers that allow the client to verify the response cryptographically.

***

### Response Headers

| Header               | Required    | Description                                  |
| -------------------- | ----------- | -------------------------------------------- |
| X-Response-Timestamp | Yes         | Unix timestamp (seconds) generated by server |
| X-Response-Nonce     | Yes         | Unique nonce generated per response          |
| X-Response-Signature | Yes         | HMAC signature of the response               |
| X-Request-Nonce      | Recommended | Echo of request nonce                        |
| X-Request-Id         | Yes         | Unique request identifier                    |

***

### Response Canonical String

When `signed_response = true`, the response signature is computed using:

```
STATUS_CODE
PATH
REQUEST_NONCE
REQUEST_BODY_SHA256
RESPONSE_TIMESTAMP
RESPONSE_NONCE
RESPONSE_BODY_SHA256
```

Each field is joined using newline characters (`\n`).

***

### Field Definitions

| Field                  | Description                         |
| ---------------------- | ----------------------------------- |
| STATUS\_CODE           | HTTP status code (e.g. 200)         |
| PATH                   | Request path (e.g. /v1/payments)    |
| REQUEST\_NONCE         | X-Nonce from the request            |
| REQUEST\_BODY\_SHA256  | SHA256 hash of raw request body     |
| RESPONSE\_TIMESTAMP    | Server timestamp                    |
| RESPONSE\_NONCE        | Random nonce generated per response |
| RESPONSE\_BODY\_SHA256 | SHA256 hash of raw response body    |

> ⚠️ Important: Hashes must be calculated using the exact raw request / response body bytes.

***

### Response Signature Algorithm

**Algorithm**

```
HMAC-SHA256
```

**Encoding**

```
Base64
```

**Formula**

```
signature = Base64(
    HMAC_SHA256(api_secret, canonical_response_string)
)
```

**Header format**

```
X-Response-Signature: v1=<signature>
```

***

### Response Example

#### Response Headers

```
HTTP/1.1 200 OK
X-Response-Timestamp: 1716501552
X-Response-Nonce: 8fae4c9d7e2b4b3aa1f2
X-Response-Signature: v1=dYlX5KQyZPzq2+7Gf+4...
X-Request-Nonce: b4d9a2a1-9c2b-4df4-8b8e-2a13a45fd321
X-Request-Id: req_84f12a8d
```

#### Response Body

```json
{
  "code": 0,
  "payload": {
    "pong": "ok"
  },
  "error": null,
  "request_id": "req_84f12a8d"
}
```

***

### Client Verification Steps

1. Extract headers:

* X-Response-Timestamp
* X-Response-Nonce
* X-Response-Signature
* X-Request-Nonce

2. Compute:

* REQUEST\_BODY\_SHA256
* RESPONSE\_BODY\_SHA256

3. Rebuild canonical string:

```
STATUS_CODE
PATH
REQUEST_NONCE
REQUEST_BODY_SHA256
RESPONSE_TIMESTAMP
RESPONSE_NONCE
RESPONSE_BODY_SHA256
```

4. Compute:

```ini
expected_signature = Base64(HMAC_SHA256(api_secret, canonical))
```

5. Compare:

```ini
expected_signature = == X-Response-Signature
```

6. Validate:

* Timestamp within ±5 minutes
* Response nonce not reused

***

### Postman Response Verification Script

Place this in **Postman → Tests tab**

```javascript
// ================================
// AllScale Response Signature Verify
// ================================

const API_SECRET = (pm.environment.get("API_SECRET") || "").trim();

function sha256Hex(str) {
    return CryptoJS.SHA256(str || "").toString(CryptoJS.enc.Hex);
}

function hmacSha256Base64(key, msg) {
    const mac = CryptoJS.HmacSHA256(msg, key);
    return CryptoJS.enc.Base64.stringify(mac);
}

function getHeader(name) {
    return pm.response.headers.get(name) || "";
}

pm.test("API_SECRET is set", function () {
    pm.expect(API_SECRET).to.not.eql("");
});

// ---- request-side values ----
const path = pm.request.url.getPath();
const requestNonce = pm.request.headers.get("X-Nonce") || "";
const requestBodyRaw = pm.request.body?.raw || "";
const requestBodySha = sha256Hex(requestBodyRaw);

// ---- response-side values ----
const statusCode = pm.response.code;
const responseTimestamp = getHeader("X-Response-Timestamp");
const responseNonce = getHeader("X-Response-Nonce");
const responseSigHeader = getHeader("X-Response-Signature");
const echoedRequestNonce = getHeader("X-Request-Nonce");

// ---- sanity checks ----
pm.test("Response signing headers present", function () {
    pm.expect(responseTimestamp).to.not.eql("");
    pm.expect(responseNonce).to.not.eql("");
    pm.expect(responseSigHeader).to.match(/^v1=.+/);
});

// ---- timestamp check ----
pm.test("Response timestamp within ±5 minutes", function () {
    const now = Math.floor(Date.now() / 1000);
    const ts = parseInt(responseTimestamp, 10);
    pm.expect(Math.abs(now - ts)).to.be.below(300);
});

// ---- response body hash ----
const responseBody = pm.response.text() || "";
const responseBodySha = sha256Hex(responseBody);

// ---- canonical string ----
const canonical = [
    String(statusCode),
    path,
    echoedRequestNonce || requestNonce,
    requestBodySha,
    String(responseTimestamp),
    responseNonce,
    responseBodySha,
].join("\n");

// ---- signature verification ----
const expectedSig = "v1=" + hmacSha256Base64(API_SECRET, canonical);

pm.test("Response signature matches", function () {
    pm.expect(responseSigHeader).to.eql(expectedSig);
});

// ---- optional replay protection ----
pm.test("Response nonce not replayed", function () {
    const key = "RESP_NONCE_CACHE";
    const ttl = 600;
    const now = Math.floor(Date.now() / 1000);

    let cache = [];
    try {
        cache = JSON.parse(pm.environment.get(key) || "[]");
    } catch {
    }

    cache = cache.filter(x => now - x.ts < ttl);
    pm.expect(cache.some(x => x.nonce === responseNonce)).to.eql(false);

    cache.push({nonce: responseNonce, ts: now});
    pm.environment.set(key, JSON.stringify(cache));
});
```

***

### Replay Protection

Responses are protected using:

* Timestamp validation (±5 minutes)
* Unique response nonce
* Request–response binding via request nonce
* Optional nonce replay cache on client side

***

### Error Handling

Even error responses are signed.

Example:

```json
{
  "code": 20002,
  "payload": null,
  "error": {
    "message": "Bad signature",
    "details": {
      "reason": "signature_mismatch"
    }
  },
  "request_id": "req_xxx"
}
```

Clients must still verify the response signature before trusting the error.

***

### Important Notes

#### ✔ When Response Signing Is Enabled

* Client must verify signature
* Client must validate timestamp
* Client should cache nonces
* Client must reject mismatched responses

#### ✔ When Response Signing Is Disabled

* No `X-Response-*` headers are returned
* Client should skip verification
* Suitable for:
  * Internal testing
  * Low-risk integrations
  * Legacy compatibility

***

### Best Practices

* Always verify response signature before processing data
* Cache response nonces for at least 5 minutes
* Reject responses with invalid timestamps
* Use raw response body for hashing
* Log request\_id for debugging
* Never disable verification in production

***

End of document.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.allscale.io/allscale-checkout/api-reference/api-doc-optional-response-signing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
