> 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-webhook-signing-and-payload-guide.md).

# API Doc - Webhook Signing & Payload Guide

**AllScale Open API**\
**Version:** v5\
**Last Updated:** 2026-04-30

***

### Overview

AllScale delivers event notifications to your server via **Webhook callbacks**.

Each webhook request is authenticated using **HMAC-SHA256 request signing**, providing:

* Sender authentication (shared secret)
* Payload integrity (tamper-proof)
* Replay attack protection (timestamp + nonce)
* Stateless verification (no session required)

Webhook payloads are plaintext JSON (not encrypted).

***

### Credentials

When your integration/store is created, you receive:

| Field       | Description                                |
| ----------- | ------------------------------------------ |
| api\_key    | Public identifier                          |
| api\_secret | Secret key used for signature verification |

#### Important Notes

* `api_secret` is shown only once
* It cannot be retrieved again
* Store it securely
* Treat it like a password or private key

***

### Webhook Request

#### Method

```http
POST
```

#### Content-Type

```http
application/json
```

#### Required Headers

| Header              | Description              |
| ------------------- | ------------------------ |
| X-API-Key           | API key                  |
| X-Webhook-Id        | Unique webhook ID        |
| X-Webhook-Timestamp | Unix timestamp(seconds)  |
| X-Webhook-Nonce     | Unique per-request nonce |
| X-Webhook-Signature | HMAC signature           |

***

### Signature Header Format

```http
X-Webhook-Signature: v1=<signature>
```

***

### Replay Protection

To prevent replay attacks:

#### Requirements

* Timestamp must be within **±5 minutes**
* Each nonce must be used **only once**

#### Recommended Implementation

* Store `nonce` in Redis
* TTL: **600 seconds**
* Reject duplicate nonces

***

### Canonical String (v1)

Webhook signatures are generated using a canonical string.

#### Canonical Format

```
allscale:webhook:v1
METHOD
PATH
QUERY_STRING
WEBHOOK_ID
TIMESTAMP
NONCE
BODY_SHA256
```

***

### Field Description

| Field         | Description                  |
| ------------- | ---------------------------- |
| METHOD        | HTTP method (uppercase)      |
| PATH          | URL path only                |
| QUERY\_STRING | Query string without `?`     |
| WEBHOOK\_ID   | From `X-Webhook-Id`          |
| TIMESTAMP     | From `X-Webhook-Timestamp`   |
| NONCE         | From `X-Webhook-Nonce`       |
| BODY\_SHA256  | SHA256 hex of raw body bytes |

⚠️ **Important**

`BODY_SHA256` must be calculated from the **raw request body bytes**,\
**before JSON parsing or re-serialization**.

***

### Signature Algorithm

#### Algorithm

```
HMAC-SHA256
```

#### Encoding

```
Base64
```

#### Formula

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

***

### Header Example

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

***

## Webhook Payload

The request body sent by AllScale is **exactly** the following JSON structure.

### JSON Field Definitions

| Field                            | Type            | Required | Description                                                                                                                                                                                                                                       |
| -------------------------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| all\_scale\_transaction\_id      | string          | ✅        | AllScale transaction ID for this payment/transfer                                                                                                                                                                                                 |
| all\_scale\_checkout\_intent\_id | string          | ✅        | AllScale checkout intent ID associated with the payment                                                                                                                                                                                           |
| webhook\_id                      | string          | ✅        | Unique webhook ID (must match `X-Webhook-Id`)                                                                                                                                                                                                     |
| amount\_cents                    | integer         | ✅        | Always the integer the merchant submitted on `POST /v1/checkout_intents/`. For fiat-priced intents this is fiat cents; for stable-coin-priced intents it is stable-coin "cents" (1.00 coin = 100 cents, equivalent to `int(amount_coins * 100)`). |
| currency                         | integer \| null | ➖        | Currency enum value (int). Must be interpreted using AllScale Currency enum mapping. `null` when the intent was priced natively in a stable coin — read `coin_symbol` instead.                                                                    |
| currency\_symbol                 | string \| null  | ➖        | Fiat currency symbol (e.g., `USD`, `CAD`, `CNY`). `null` when the intent was priced natively in a stable coin — read `coin_symbol` instead.                                                                                                       |
| amount\_coins                    | string          | ✅        | Stablecoin amount as a **decimal string** (to avoid float issues), e.g. `"12.340000"`                                                                                                                                                             |
| coin\_contract\_address          | string          | ✅        | Official ERC-20 token contract address                                                                                                                                                                                                            |
| coin\_symbol                     | string          | ✅        | Stablecoin symbol (e.g., `USDT`, `USDC`)                                                                                                                                                                                                          |
| chain\_id                        | integer         | ✅        | EIP-155 chainId identifying the EVM network (<https://chainid.network/>)                                                                                                                                                                          |
| tx\_hash                         | string          | ✅        | On-chain transaction hash                                                                                                                                                                                                                         |
| tx\_from                         | string          | ✅        | Sender wallet address                                                                                                                                                                                                                             |
| payment\_method\_type            | integer         | ✅        | Payment method type used for this transaction. Value must correspond to the `PaymentMethodType` enum: `0=UNKNOWN`, `1=WALLET_SCAN`, `2=WALLET_CONNECT`, `3=ALL_SCALE_PAY`.                                                                        |
| user\_id                         | string \| null  | ➖        | Optional merchant/user identifier                                                                                                                                                                                                                 |
| order\_id                        | string \| null  | ➖        | Optional merchant order identifier                                                                                                                                                                                                                |
| user\_name                       | string \| null  | ➖        | Optional customer/user display name                                                                                                                                                                                                               |
| extra\_obj                       | object \| null  | ➖        | Optional arbitrary JSON object with extra fields                                                                                                                                                                                                  |

### Actual Webhook Body Example (Matches Real Structure)

#### Fiat-priced intent (merchant created with `currency`)

```json
{
  "all_scale_transaction_id": "txn_123",
  "all_scale_checkout_intent_id": "chk_456",
  "webhook_id": "whk_84f12a8d",

  "amount_cents": 1234,
  "currency": 1,
  "currency_symbol": "USD",

  "amount_coins": "12.340000",
  "coin_contract_address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  "coin_symbol": "USDT",

  "chain_id": 1,
  "tx_hash": "0x...",
  "tx_from": "0x...",

  "payment_method_type": 1,
  "user_id": "user_001",
  "order_id": "order_8899",
  "user_name": "Alice",
  "extra_obj": {
    "source": "mobile",
    "note": "promo-applied"
  }
}
```

#### Stable-coin-priced intent (merchant created with `stable_coin`)

`amount_cents` is still an integer and equals `amount_coins * 100`. `currency` and `currency_symbol` are `null` — the buyer paid the merchant directly in `coin_symbol`, no fiat currency is involved.

```json
{
  "all_scale_transaction_id": "txn_123",
  "all_scale_checkout_intent_id": "chk_456",
  "webhook_id": "whk_84f12a8d",

  "amount_cents": 1000,
  "currency": null,
  "currency_symbol": null,

  "amount_coins": "10.000000",
  "coin_contract_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "coin_symbol": "USDC",

  "chain_id": 1,
  "tx_hash": "0x...",
  "tx_from": "0x...",

  "payment_method_type": 1,
  "user_id": "user_001",
  "order_id": "order_8899",
  "user_name": "Alice",
  "extra_obj": null
}
```

***

### Verification Flow (Your Server Side)

1. Extract headers
2. Validate timestamp (±300 seconds)
3. Validate nonce (store with TTL)
4. Read raw request body bytes (before parsing JSON)
5. Compute SHA256 of raw body
6. Rebuild canonical string
7. Compute expected signature
8. Timing-safe compare
9. Only after verification → process payload

***

### Response Expectations

Your endpoint should respond:

* `200 OK` if processed successfully
* Non-200 if rejected (signature invalid, timestamp invalid, etc.)

AllScale will log the HTTP status and may retry depending on configured policy.

***

### Best Practices

✅ Always verify `X-Webhook-Signature` before processing\
✅ Validate timestamp within ±5 minutes\
✅ Cache nonce for replay protection\
✅ Use idempotency via `webhook_id`\
✅ Convert `amount_coins` using Decimal (not float)\
❌ Never log secrets or signatures

***

### Troubleshooting

| Issue              | Cause            | Fix                                         |
| ------------------ | ---------------- | ------------------------------------------- |
| Signature mismatch | Body modified    | Use raw bytes exactly as received           |
| Signature mismatch | Wrong path/query | Use the exact request path and query string |
| Signature mismatch | Wrong secret     | Verify correct `api_secret`                 |
| Timestamp rejected | Clock drift      | Sync server time (NTP)                      |
| Replay rejected    | Nonce reused     | Generate unique nonce; store with TTL       |

***

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-webhook-signing-and-payload-guide.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.
