---
updatedAt: 2026-08-18T09:12:55.000Z
agentTools:
  projectIndex: https://docs.onechain.nexus/llms.txt
---

# 5. EndPoint APIs

# 5. Endpoint Specification

The interface specification between RAMP and your server. For where each call sits in the sequence, see [2. Architecture](https://docs.onechain.nexus/docs/architecture); for where to register them, see [4-3. Ramp Settings](https://docs.onechain.nexus/docs/3-ramp-settings).

> **Wallet and contract addresses and Project IDs in the examples are masked in the form&#x20;**`0x6de3----7b28`**.** Only the first and last four characters are kept, with `-` filling the middle; the total length matches the real value. Copying them as-is will not work — replace them with your own project's real values.

**Call direction**

| Item                                                                                                              | Direction              | Implemented by   |
| ----------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------- |
| [5.3 `initialize`](https://docs.onechain.nexus/docs/endpint-apis#53-issuing-a-uuid--initialize)                   | Your server → RAMP     | Provided by RAMP |
| [5.4 `Get Assets`](https://docs.onechain.nexus/docs/endpint-apis#54-game-asset-lookup-api--get-assets)            | RAMP B/E → your server | **You**          |
| [5.5 `Validate Order`](https://docs.onechain.nexus/docs/endpint-apis#55-signature-validation-api--validate-order) | RAMP B/E → your server | **You**          |
| [5.6 `Handle Order Result`](https://docs.onechain.nexus/docs/endpint-apis#56-result-webhook--handle-order-result) | RAMP B/E → your server | **You**          |

**Path summary**

| Name                 | Method · Path                                                 |
| -------------------- | ------------------------------------------------------------- |
| RAMP F/E entry       | `https://ramp.crosstoken.io/exchange?uuid=…`                  |
| Issue a UUID         | `POST https://cross-ramp-api.crosstoken.io/api/v2/initialize` |
| Game asset lookup    | `GET {Get Assets}`                                            |
| Signature validation | `POST {Validate Order}`                                       |
| Result reception     | `POST {Handle Order Result}`                                  |

**Header summary**

| Header                 | Used in                                                 | Meaning                         |
| ---------------------- | ------------------------------------------------------- | ------------------------------- |
| `X-HMAC-SIGNATURE`     | `initialize` · `Validate Order` · `Handle Order Result` | HMAC-SHA256 integrity signature |
| `X-Dapp-Authorization` | `Get Assets` · `Validate Order` · `Handle Order Result` | `Bearer {accessToken}`          |
| `X-Dapp-SessionID`     | Same as above                                           | User character identifier       |

***

## 5.1 RAMP F/E Entry URL

```
https://ramp.crosstoken.io/exchange?uuid={{uuid}}&accessToken={{userAccessToken}}&sessionId={{userSessionId}}&network=testnet
```

| Parameter     | Description                                                                                                   |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| `uuid`        | Unique identifier for the Mint/Burn. Issued by [5.3](#53-issuing-a-uuid--initialize); **valid for 5 minutes** |
| `accessToken` | Used for RAMP → dApp server requests. **Your dApp server must verify it**                                     |
| `sessionId`   | User session / character identifier. **Your dApp server verifies it**                                         |
| `network`     | `testnet` / `mainnet`                                                                                         |

***

## 5.2 Request Integrity — HMAC-SHA256

* **Key** — the **HMAC Key (Secret)** issued when the RAMP contract is deployed → [4-2.2](04-console/02-contracts.md)
  * **It is issued as a Base64URL string.** You must **decode it and use the resulting 32 bytes as the key**.
* **Signed payload** — the request body **exactly as received, as a raw string**
* **Algorithm** — HMAC-SHA256, output as a **hex string**
* **Header** — `X-HMAC-SIGNATURE`
* **Where it applies**
  * You → RAMP: **generate** `X-HMAC-SIGNATURE` for the `initialize` request
  * RAMP → you: **verify** `X-HMAC-SIGNATURE` on `Validate Order` and `Handle Order Result` requests

> **Failure cause #1 — not decoding the key.** Passing the Base64URL string directly gives you a 43-byte key and a completely different signature. Decoded, it is 32 bytes.
>
> **Failure cause #2 — re-serializing the body.** Turning a parsed object back into JSON with `JSON.stringify` changes key order, whitespace, and unicode escaping, which changes the bytes and breaks the signature. Use **the raw body exactly as received**.

### Implementation example (Node.js)

```javascript
const crypto = require("crypto");

// The HMAC Key is issued as a Base64URL string. Use the decoded bytes as the key.
function decodeHmacKey(secretBase64Url) {
  return Buffer.from(secretBase64Url, "base64url"); // Node 16+
}

// The signed payload is the raw request body string. Never re-serialize the JSON.
function generateHmacSignature(rawBody, secretBase64Url) {
  return crypto
    .createHmac("sha256", decodeHmacKey(secretBase64Url))
    .update(rawBody, "utf8")
    .digest("hex");
}

function verifyHmacSignature(rawBody, receivedSignature, secretBase64Url) {
  // Validate the format first — timingSafeEqual throws when the lengths differ.
  if (
    typeof receivedSignature !== "string" ||
    !/^[0-9a-fA-F]{64}$/.test(receivedSignature)
  ) {
    return false;
  }
  const expected = Buffer.from(
    generateHmacSignature(rawBody, secretBase64Url),
    "hex",
  );
  const received = Buffer.from(receivedSignature, "hex");
  if (expected.length !== received.length) return false;

  // Constant-time comparison to prevent timing attacks
  return crypto.timingSafeEqual(expected, received);
}
```

* `timingSafeEqual`**&#x20;throws a&#x20;**`RangeError`**&#x20;when the two buffers differ in length.** Without the length and hex checks above, a single malformed header makes your server return a 500.
* `Buffer.from(x, 'hex')` **silently discards invalid hex.** That is why the regex check is needed.
* On Node versions below 16, or in environments without `base64url` support, convert manually.
  * Replace `-` with `+` and `_` with `/`, pad with `=` until the length is a multiple of 4, then Base64-decode.

### Capturing the raw body (Express)

```javascript
const express = require("express");
const app = express();

// Keep the original bytes aside using the verify callback.
app.use(
  express.json({
    verify: (req, res, buf) => {
      req.rawBody = buf.toString("utf8");
    },
  }),
);

app.post("/order-validate", (req, res) => {
  const ok = verifyHmacSignature(
    req.rawBody, // rawBody, not req.body (the parsed object)
    req.get("X-HMAC-SIGNATURE"),
    process.env.RAMP_HMAC_SECRET,
  );
  if (!ok)
    return res
      .status(401)
      .json({ success: false, errorCode: "INVALID_SIGNATURE" });

  // After verification passes, handle the logic with req.body
});
```

> If your framework parses the body automatically and discards the original, **verification is structurally impossible**. Confirm how to capture the raw body before building the endpoints.

***

## 5.3 Issuing a UUID — `initialize`

```
POST https://cross-ramp-api.crosstoken.io/api/v2/initialize
```

### Request Headers

| Header             | Value                                                          |
| ------------------ | -------------------------------------------------------------- |
| `Content-Type`     | `application/json`                                             |
| `X-HMAC-SIGNATURE` | The HMAC signature → [5.2](#52-request-integrity--hmac-sha256) |

### Request Body

* **Basic information** (all optional)
  * `player_id` string — the unique in-game player ID
  * `name` string — character name
  * `wallet_address` string — the player's wallet address (an empty string is allowed)
  * `server` string — identifier of the game server they are connected to
* `intent` object — **required**
  * `network` string — `mainnet` / `testnet`
  * `project_id` string — the ONE RAMP **Project ID**, a 32-character hex value. → [4-1.5](04-console/01-account-project.md)
  * `token` string — the **token contract address**
  * `mint_fee_bps` number — Mint fee rate in bps (`1000` = 10%) → [4-3.4](04-console/03-ramp-settings.md)
  * `burn_fee_bps` number — Burn fee — **fixed at&#x20;**`0`
  * `mint_method` string — **fixed at&#x20;**`mint`
  * `burn_method` string — **fixed at&#x20;**`burn-permit`
  * `materials[]` — the game assets **consumed** on Mint
    * `id` string — item ID
    * `amount` number — quantity consumed
    * `icon_url` string — icon image URL
    * `is_non_fungible` boolean — asset kind. `true` = individually identified item (sword, arrow, etc.) / `false` = fungible amount-based asset (gold, etc.)
  * `outputs[]` — the game assets **granted** on Burn
    * `id` · `amount` · `icon_url` · `is_non_fungible` (same structure)

### Request example

```http
POST https://cross-ramp-api.crosstoken.io/api/v2/initialize
Content-Type: application/json
X-HMAC-SIGNATURE: {{HMAC-SIGNATURE-VALUE}}

{
  "player_id": "player_id_01",
  "name": "character_name_01",
  "wallet_address": "0xwalletaddresss",
  "server": "server_01",
  "intent": {
    "network": "testnet",
    "project_id": "79bc87b18d7941caee2fb2f5226d1736",
    "token": "0xFFF6--------------------------------2AF1",
    "mint_fee_bps": 2000,
    "burn_fee_bps": 0,
    "mint_method": "mint",
    "burn_method": "burn-permit",
    "materials": [
      {
        "id": "item_gold",
        "amount": 100,
        "icon_url": "https://console-contents.crosstoken.io/studios/projects/assets/019ac48c-ed97-7a09-be8e-2fdb499e3c11.png",
        "is_non_fungible": false
      }
    ],
    "outputs": [
      {
        "id": "item_gold",
        "amount": 80,
        "icon_url": "https://console-contents.crosstoken.io/studios/projects/assets/019ac48c-ed97-7a09-be8e-2fdb499e3c11.png",
        "is_non_fungible": false
      }
    ]
  }
}
```

### Response

* `data.uuid` string — the UUID used when loading RAMP F/E. **Valid for 5 minutes**

```json
{
  "code": 200,
  "message": "OK",
  "data": {
    "uuid": "9cf3a7e5-7d2c-4ef3-ba6f-911d5078416b"
  }
}
```

### Failure responses

| Situation                  | code | message               | data                           |
| -------------------------- | ---- | --------------------- | ------------------------------ |
| `X-HMAC-Signature` missing | 400  | Bad Request           | `X-HMAC-Signature is required` |
| HMAC signature mismatch    | 500  | Internal Server Error | `invalid mac`                  |
| `project_id` missing       | 500  | Internal Server Error | `ramp not found`               |
| Invalid `network` value    | 500  | Internal Server Error | `ramp not exists`              |

***

## 5.4 Game Asset Lookup API — `Get Assets`

**You implement this** · RAMP B/E → your server

* Returns the user's asset balances for display in RAMP F/E.
* Requests arrive at the project's `Get Assets`**&#x20;endpoint** in the console.
* **You must verify&#x20;**`X-Dapp-Authorization`**&#x20;(accessToken)** and respond according to the result.

### Request

```http
GET /api/assets
Host: https://your-server.com
Content-Type: application/json
X-Dapp-Authorization: Bearer {{accessToken}}
X-Dapp-SessionID: {{sessionId}}
```

| Header                 | Description                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------- |
| `X-Dapp-Authorization` | The accessToken issued by your server — the `accessToken` value from the RAMP F/E URL query  |
| `X-Dapp-SessionID`     | The user character identifier you manage — the `sessionId` value from the RAMP F/E URL query |

### Response

| Field                      | Type    | Description                                                                                          | Required |
| -------------------------- | ------- | ---------------------------------------------------------------------------------------------------- | -------- |
| `success`                  | boolean | Whether the request succeeded                                                                        | Y        |
| `errorCode`                | string  | Error code (`null` on success)                                                                       | Y        |
| `data`                     | object  | Response data (`null` on failure)                                                                    | Y        |
| `data.v1`                  | object  | Version 1 data                                                                                       | Y        |
| `data.v1.player_id`        | string  | Unique ID of the user's character                                                                    | N        |
| `data.v1.name`             | string  | Name of the user's character                                                                         | N        |
| `data.v1.wallet_address`   | string  | The user's wallet address                                                                            | N        |
| `data.v1.server`           | string  | Connected server information                                                                         | N        |
| `data.v1.assets`           | array   | The user's game asset balances                                                                       | Y        |
| `data.v1.assets[].id`      | string  | **Unique game asset identifier — must match the asset ID in the&#x20;**`initialize`**&#x20;request** | Y        |
| `data.v1.assets[].balance` | string  | The user's actual balance                                                                            | Y        |

```json
{
  "success": true,
  "errorCode": null,
  "data": {
    "v1": {
      "player_id": "player_id",
      "name": "player_name",
      "wallet_address": "0x62c5...6707",
      "server": "test",
      "assets": [
        {
          "id": "item_gold",
          "balance": "1000.123"
        }
      ]
    }
  }
}
```

> The **Game Asset ID** is the reference value used throughout Mint and Burn. Managing it as an **asset-related environment variable** on your game server is recommended.

***

## 5.5 Signature Validation API — `Validate Order`

**You implement this** · RAMP B/E → your server

* Verifies that the user's signature matches the transaction request data you sent.
* Requests arrive at the project's `Validate Order`**&#x20;endpoint** in the console.

**What your server must do**

* **① Verify HMAC integrity** — `X-HMAC-SIGNATURE`
* **② Verify the accessToken**
* **③ Verify that the requested game assets and amounts are valid under your policy** ← skipping this leads directly to asset/token mismatches
* **④ Sign the&#x20;**`digest`**&#x20;with the Validator private key and return it**

> **Important** You must validate the game asset values in the request parameters sent by RAMP B/E. Check for violations of your own policies — authentication, asset amounts, and so on — before responding.

### Request Headers

| Header                 | Description                                                              |
| ---------------------- | ------------------------------------------------------------------------ |
| `X-HMAC-SIGNATURE`     | The request raw data signed with the HMAC Key issued by the RAMP console |
| `X-Dapp-Authorization` | The accessToken issued by your server                                    |
| `X-Dapp-SessionID`     | The user character identifier you manage                                 |

### Request Body

| Field                     | Type   | Description                                                                         | Required |
| ------------------------- | ------ | ----------------------------------------------------------------------------------- | -------- |
| `user_sig`                | string | The value signed by the user with ONEpocket                                         | Y        |
| `user_address`            | string | The user's ONEpocket address                                                        | Y        |
| `project_id`              | string | The Project ID created in the console                                               | Y        |
| `digest`                  | string | Hash digest of the transaction data. **The payload to sign with the Validator Key** | Y        |
| `uuid`                    | string | Unique request identifier                                                           | Y        |
| `intent`                  | object | Token issuance/burn information                                                     | Y        |
| `intent.method`           | string | Method to execute — `mint`**&#x20;/&#x20;**`burn-permit`                            | Y        |
| `intent.type`             | string | `assemble` = issue · `disassemble` = burn                                           | Y        |
| `intent.from`             | array  | Source asset list                                                                   | Y        |
| `intent.from[].type`      | string | `asset` = game asset · `ERC20` = game token                                         | Y        |
| `intent.from[].id`        | string | Game asset ID or token contract address                                             | Y        |
| `intent.from[].amount`    | number | Quantity used                                                                       | Y        |
| `intent.to`               | array  | Destination asset list                                                              | Y        |
| `intent.to[].type`        | string | `ERC20` = game token · `asset` = game asset                                         | Y        |
| `intent.to[].id`          | string | Token contract address or game asset ID                                             | Y        |
| `intent.to[].amount`      | number | Quantity                                                                            | Y        |
| `intent.target_candidate` | object | Target candidate information (additional option)                                    | N        |

### Request example — Mint

```http
POST /api/validate
Host: https://your-server.com
Content-Type: application/json
x-dapp-authorization: Bearer {{accessToken}}
x-dapp-sessionid: {{sessionId}}
x-hmac-signature: {{hmac_signature}}

{
  "user_sig": "0x58ea88cc20a571d2bc4f4a7ab687158e1924887c005a8a2ccce9a7c8f669adbb222932f9e760b923b6f359870169d58a171d47516ee71167313d5068dbd84c641c",
  "user_address": "0x6de3--------------------------------7b28",
  "project_id": "3a4--------------------------2d7",
  "digest": "0x6d196d0881bb8e322c194fbf53518089b240055134044491a78b14920098e396",
  "uuid": "86b555dd-e622-43fe-a799-c5c4536dd8c6",
  "intent": {
    "method": "mint",
    "type": "assemble",
    "from": [
      {
        "type": "asset",
        "id": "item_gold",
        "amount": 100
      }
    ],
    "to": [
      {
        "type": "ERC20",
        "id": "0x14f6--------------------------------1D81",
        "amount": 1
      }
    ],
    "target_candidate": {}
  }
}
```

### Request example — Burn (reversed direction)

> For Burn, `intent.method` is `burn-permit`, `intent.type` is `disassemble`, and the `from`/`to` direction is the reverse of Mint.

```json
{
  "user_sig": "0xb1378a978b5e77d750c44d4b9bdf4d883d2e2bad8e09c8928e8d83176359cc9376a959c3576c82f2214c9fece66c73417669bf667734ec8f94880885c5d1b84a1c",
  "user_address": "0x6de3--------------------------------7b28",
  "project_id": "3a4--------------------------2d7",
  "digest": "0x7bd721630a8c7e6b1c1050934fc3bf69cadaef0253c46f92f1b03242c5f2e731",
  "uuid": "d7360515-8547-427e-acb5-6556c8376fd4",
  "intent": {
    "method": "burn-permit",
    "type": "disassemble",
    "from": [
      {
        "type": "ERC20",
        "id": "0x14f6--------------------------------1D81",
        "amount": 1
      }
    ],
    "to": [
      {
        "type": "asset",
        "id": "item_gold",
        "amount": 50
      }
    ],
    "target_candidate": {}
  }
}
```

### Response

| Field               | Type    | Description                                                          | Required |
| ------------------- | ------- | -------------------------------------------------------------------- | -------- |
| `success`           | boolean | Whether the request succeeded                                        | Y        |
| `errorCode`         | string  | Error code (`null` on success)                                       | Y        |
| `data.userSig`      | string  | The user signature you received                                      | Y        |
| `data.validatorSig` | string  | **The&#x20;**`digest`**&#x20;signed with the Validator Key (ECDSA)** | Y        |

```json
{
  "success": true,
  "errorCode": null,
  "data": {
    "userSig": "0x58ea88cc20a571d2bc4f4a3ab687158e1924887c005a8a2cdce9a7c8f669bdbb222932f9e760b923b6f359870169d58a171d47516ee71167313e5068dbd84c631c",
    "validatorSig": "0xfa7c12023378170c615bdd64be3e7aa195ff98b42fe84dad34348017fc1150db157e077dd5053328040b476479edebfe5d773bb8602de6bc088951de7c597fd31b"
  }
}
```

> The Validator **address that produced&#x20;**`validatorSig`**&#x20;must be registered in the console** for RAMP's verification (step 14) to pass.

### Failure response — `errorCode`

If validation fails, return `success: false` with an `errorCode` and **do not sign the&#x20;**`digest`.

| Code      | Message shown to the user                                  | Korean                          | Meaning                                            |
| --------- | ---------------------------------------------------------- | ------------------------------- | -------------------------------------------------- |
| **10010** | Game authentication failed. Please log in again.           | 게임 인증에 실패했습니다. 다시 로그인해 주세요.     | Game account authentication failed                 |
| **10011** | Transaction request failed. Please try again.              | 트랜잭션 요청에 실패했습니다. 다시 시도해 주세요.    | Transaction request processing failed              |
| **10012** | Unverified wallet. Please contact customer support.        | 미인증 지갑입니다. 고객센터에 문의해 주세요.       | The wallet is not verified                         |
| **90001** | Transaction timed out. Please wait a moment and try again. | 트랜잭션 제한 시간입니다. 잠시 후 다시 시도해 주세요. | Mainly used when an in-game token cooldown applies |

```json
{
  "success": false,
  "errorCode": "10010",
  "data": null
}
```

* These messages are **shown to users verbatim in RAMP F/E**. Do not surface internal server error messages — **map them to the defined codes**.
* For how the codes map to the validation steps, see the order below. Mapping ① and ② to `10010`, ④ to `10011`, and wallet-related rejections to `10012` is the natural fit.
* Keep detailed diagnostic logs **on your server only**, and include just the code in the response.

***

### Required validation order before signing

Follow this order when handling this endpoint. **Step ⑥ signing happens only when ①–⑤ all pass.**

* **① HMAC integrity** — does `X-HMAC-SIGNATURE` match?
* **② Authentication** — is `X-Dapp-Authorization` (accessToken) valid and unexpired?
* **③ Ownership** — does the character in `X-Dapp-SessionID` belong to that account?
* **④ Asset and amount policy** — for the asset IDs and amounts in `intent.from` and `intent.to`:
  * are they within the user's actual in-game balance?
  * is the combination and amount allowed by your game policy?
  * do the values RAMP requested **match** what your server calculated?
* **⑤ Duplication** — has the same `uuid` already been processed?
* **⑥ Signing** — sign the `digest` only if everything above passed

### Validator Signature (ECDSA)

* **Key** — the Validator private key you generated, whose **address is registered in the console** → [3.3](https://docs.onechain.nexus/docs/prerequisites#33-validator-key) · [4-2.2](https://docs.onechain.nexus/docs/2-contracts#4-22-deploying-the-ramp-contract)
* **Payload** — the `digest` delivered in the `Validate Order` request (**a 32-byte hash**)
* **Curve** — secp256k1 (`signingKey.sign` in `ethers`)
* **Return value** — a **65-byte** signature serialized as `r + s + v`, returned as `data.validatorSig` (see Response above)

#### Implementation example (Node.js · ethers v6)

```javascript
const { ethers } = require("ethers");

const privateKey = process.env.VALIDATOR_PRIVATE_KEY;

// Example: a 32-byte digest
const digest =
  "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";

const wallet = new ethers.Wallet(privateKey);

// Convert the digest to bytes
const digestBytes = ethers.getBytes(digest);

// Create the ECDSA signature
const signature = ethers.Signature.from(
  wallet.signingKey.sign(digestBytes),
).serialized;

console.log(signature);
```

***

## 5.6 Result Webhook — `Handle Order Result`

**You implement this** · RAMP B/E → your server

* Delivers the blockchain transaction result to your game server.
* It is sent to the endpoint you registered in the console, and your server **must respond with HTTP 200**.
* If there is no response or an HTTP 500, RAMP B/E **resends** it.

### Retry policy

* **Up to 20 times over 12 hours** after the first delivery
  * 2 attempts at **5-minute** intervals
  * 7 attempts at **15-minute** intervals
  * 10 attempts at **60-minute** intervals

### Request Body

| Field                             | Type          | Description                                                                     |
| --------------------------------- | ------------- | ------------------------------------------------------------------------------- |
| `session_id`                      | string        | Session identifier managed by the client and server                             |
| `uuid`                            | string (UUID) | Unique identifier for a single request — **recommended as the idempotency key** |
| `tx_hash`                         | string        | Blockchain transaction hash — **recommended to retain**                         |
| `receipt`                         | object        | Transaction execution receipt                                                   |
| `receipt.status`                  | string        | `0x1`**&#x20;= success ·&#x20;**`0x0`**&#x20;= failure**                        |
| `receipt.type`                    | string        | Transaction type (EIP-1559 or a chain-specific type; `0x7` = gas sponsored)     |
| `receipt.root`                    | string        | State root (usually empty)                                                      |
| `receipt.cumulativeGasUsed`       | string        | Cumulative gas used within the block                                            |
| `receipt.logsBloom`               | string        | Event log bloom filter                                                          |
| `receipt.logs`                    | array         | Array of event logs emitted during the transaction                              |
| `receipt.logs[].address`          | string        | Address of the contract that emitted the event                                  |
| `receipt.logs[].topics`           | string\[]     | Event signature and indexed parameters                                          |
| `receipt.logs[].data`             | string        | Non-indexed event data                                                          |
| `receipt.logs[].blockNumber`      | string        | Number of the block containing the event                                        |
| `receipt.logs[].transactionHash`  | string        | Hash of the transaction containing the event                                    |
| `receipt.logs[].transactionIndex` | string        | Transaction index within the block                                              |
| `receipt.logs[].blockHash`        | string        | Block hash                                                                      |
| `receipt.logs[].blockTimestamp`   | number        | Block timestamp (0 on some chains)                                              |
| `receipt.logs[].logIndex`         | string        | Event index within the transaction                                              |
| `receipt.logs[].removed`          | boolean       | Whether it was removed by a chain reorganization                                |
| `receipt.transactionHash`         | string        | Transaction hash                                                                |
| `receipt.contractAddress`         | string        | Address of the created contract (`0x0` if not a creation TX)                    |
| `receipt.gasUsed`                 | string        | Actual gas used                                                                 |
| `receipt.effectiveGasPrice`       | string        | Effective gas price applied                                                     |
| `receipt.blockHash`               | string        | Block hash                                                                      |
| `receipt.blockNumber`             | string        | Block number                                                                    |
| `receipt.transactionIndex`        | string        | Transaction index within the block                                              |
| `intent`                          | object        | In-game Mint/Burn information                                                   |
| `intent.method`                   | string        | The method that ran (`mint` / `burn-permit`)                                    |
| `intent.type`                     | string        | RAMP internal processing type (`assemble` / `disassemble`)                      |
| `intent.from`                     | array         | Assets consumed (`type` · `id` · `amount`)                                      |
| `intent.to`                       | array         | Tokens issued (`type` · `id` · `amount`)                                        |
| `intent.target_candidate`         | object        | Target candidate information (currently an empty object)                        |
| `intent.fee_rate`                 | number        | Fee rate in bps (`2000` = 20%)                                                  |
| `intent.actual`                   | string        | The amount the user actually receives after the fee                             |

> **If&#x20;**`receipt.status`**&#x20;is not&#x20;**`0x1`**, the blockchain transaction failed and you must restore the game assets.**

### Request example (abbreviated)

```http
POST /api/result
Host: https://your-server.com
Content-Type: application/json
x-dapp-authorization: Bearer {{accessToken}}
x-dapp-sessionid: {{user_unique_id}}
x-hmac-signature: {{hmac_signature}}

{
  "session_id": "{{user_unique_id}}",
  "uuid": "26e7ee8f-008d-4337-8e26-a7a061361785",
  "tx_hash": "0x685d9a05d5d280ff707610876691f3a9ee32319cec46a47a5d785dd4dca207a8",
  "receipt": {
    "type": "0x7",
    "root": "0x",
    "status": "0x1",
    "cumulativeGasUsed": "0x1f531",
    "logs": [
      {
        "address": "0xe9fa--------------------------------4579",
        "topics": [
          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
          "0x0000000000000000000000000000000000000000000000000000000000000000",
          "0x000000000000000000000000a18e--------------------------------d310"
        ],
        "data": "0x0000000000000000000000000000000000000000000000000b1a2bc2ec500000",
        "blockNumber": "0x11655a0",
        "transactionHash": "0x685d9a05d5d280ff707610876691f3a9ee32319cec46a47a5d785dd4dca207a8",
        "transactionIndex": "0x0",
        "blockHash": "0x865bed1d085e0136f4d04eee86e59c6fa6288d73c216635cce847b2580803e61",
        "blockTimestamp": 0,
        "logIndex": "0x0",
        "removed": false
      }
    ],
    "transactionHash": "0x685d9a05d5d280ff707610876691f3a9ee32319cec46a47a5d785dd4dca207a8",
    "contractAddress": "0x0000000000000000000000000000000000000000",
    "gasUsed": "0x1f531",
    "effectiveGasPrice": "0xee6b2800",
    "blockHash": "0x865bed1d085e0136f4d04eee86e59c6fa6288d73c216635cce847b2580803e61",
    "blockNumber": "0x11655a0",
    "transactionIndex": "0x0"
  },
  "intent": {
    "method": "mint",
    "type": "assemble",
    "from": [
      {
        "type": "asset",
        "id": "item_gold",
        "amount": 100
      }
    ],
    "to": [
      {
        "type": "ERC20",
        "id": "0xe9fa--------------------------------4579",
        "amount": 1
      }
    ],
    "target_candidate": {},
    "fee_rate": 2000,
    "actual": "0.8"
  }
}
```

### Response

```json
{
  "success": true,
  "errorCode": null,
  "data": null
}
```

## 5.7 Constants

| Item                               | Value                                                                                      |
| ---------------------------------- | ------------------------------------------------------------------------------------------ |
| UUID validity                      | **5 minutes**                                                                              |
| `intent.mint_method`               | **fixed at&#x20;**`mint`                                                                   |
| `intent.burn_method`               | **fixed at&#x20;**`burn-permit`                                                            |
| `intent.type`                      | `assemble` = Mint · `disassemble` = Burn                                                   |
| `intent.from[].type` / `to[].type` | `asset` = game asset · `ERC20` = game token                                                |
| `intent.burn_fee_bps`              | **fixed at&#x20;**`0`                                                                      |
| Fee unit                           | **bps** — `100` = 1% · `1000` = 10% · `2000` = 20%                                         |
| Minimum fee rate                   | **10% or higher**                                                                          |
| Where the fee rate is set          | `intent.mint_fee_bps`**&#x20;in the&#x20;**`initialize`**&#x20;request** (not the console) |
| Where the fee address is set       | Console `Ramp Settings` → `Fee Settings`                                                   |
| Transaction success check          | `receipt.status === "0x1"`                                                                 |
| Gas-sponsored transaction type     | `receipt.type === "0x7"`                                                                   |
| Webhook retries                    | Up to **20 times** within 12 hours — 5 min × 2 · 15 min × 7 · 60 min × 10                  |
| Expected Webhook response          | **HTTP 200**                                                                               |
| Issuance limit reset basis         | **UTC-0**                                                                                  |
| Contract type covered              | **ERC-20**                                                                                 |

***

## 5.8 Error Reference

### `initialize` failure responses

| code | message               | data                           | Cause                         | What to do                                                                           |
| ---- | --------------------- | ------------------------------ | ----------------------------- | ------------------------------------------------------------------------------------ |
| 400  | Bad Request           | `X-HMAC-Signature is required` | Missing header                | Add the header                                                                       |
| 500  | Internal Server Error | `invalid mac`                  | HMAC signature mismatch       | Sign the raw body and refresh the secret → [5.2](#52-request-integrity--hmac-sha256) |
| 500  | Internal Server Error | `ramp not found`               | `project_id` missing or wrong | Check the Project ID in the console → [4-1.5](04-console/01-account-project.md)      |
| 500  | Internal Server Error | `ramp not exists`              | Invalid `network` value       | Check `mainnet` / `testnet`                                                          |

### `Validate Order` response error codes

The four `errorCode` values your server returns → [5.5 Failure response](https://docs.onechain.nexus/docs/endpint-apis#55-signature-validation-api--validate-order)

### By symptom

| Symptom                                            | Check first                                                                                                                                                                             |
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Mint/Burn does not work at all                     | The token contract ↔ project **link status** → [4-2.3](https://docs.onechain.nexus/docs/2-contracts#4-23-linking-the-token-contract-to-a-project)                                       |
| Assets show as 0 in the F/E                        | Whether `assets[].id` in the `Get Assets` response matches the asset ID in the `initialize` request → [3.8](https://docs.onechain.nexus/docs/prerequisites#38-designing-game-asset-ids) |
| Cannot enter the F/E                               | UUID expired after 5 minutes · `network` mismatch                                                                                                                                       |
| Signature validation keeps failing                 | The HMAC secret · the registered Validator address                                                                                                                                      |
| The result webhook keeps arriving                  | Whether you return HTTP 200 · whether `uuid` idempotency is implemented                                                                                                                 |
| The user receives a different amount than expected | Check `intent.fee_rate` and `intent.actual` → [4-3.4](https://docs.onechain.nexus/docs/3-ramp-settings#4-34-fee-settings)                                                               |
| Issuance suddenly stops                            | `Issuance Limit per Period` or `Max Supply` exhausted                                                                                                                                   |
| RAMP itself is not exposed                         | The `Enable Ramp` toggle · contract link status → [4-3.1](https://docs.onechain.nexus/docs/3-ramp-settings#4-31-enable-ramp)                                                            |
| Settings are not saved                             | `Save Changes` was not clicked in Ramp Settings                                                                                                                                         |

### Incident triage order

* **① Console link status** — is the token ↔ project link still in place? → [4-2.3](https://docs.onechain.nexus/docs/2-contracts#4-23-linking-the-token-contract-to-a-project)
* **② Endpoint responses** — do all three URLs return 200 from outside your network?
* **③ HMAC** — is the secret current? (check the reissue history)
* **④ Network** — does `intent.network` in the request match the console project's network?
* **⑤ Limits** — have you hit an issuance limit?
* **⑥ Transaction** — look up the `tx_hash` in the explorer → [3.6](https://docs.onechain.nexus/docs/prerequisites#36-network-information)

***

## 5.9 Integration Testing

Verify the whole flow on Testnet. **If you only check the happy path, asset incidents on failure will first surface in production.**

**Preparation**

* Set the console project to **Testnet** → [4-3.1](https://docs.onechain.nexus/docs/3-ramp-settings#4-31-enable-ramp)
* Get test coins from the **TESTNET Faucet** → [1.5](https://docs.onechain.nexus/docs/introduce-1#15-links)
* Make sure the test account has enough game assets

### Happy paths

| # | Scenario        | What to check                                                                               |
| - | --------------- | ------------------------------------------------------------------------------------------- |
| 1 | Mint succeeds   | Game assets deducted · tokens received · `receipt.status = 0x1`                             |
| 2 | Fee calculation | The user's received amount matches `intent.actual` · `fee_rate` matches the requested value |
| 3 | Burn succeeds   | Tokens burned · game assets granted · `intent.type = disassemble`                           |
| 4 | Asset display   | The balance shown in RAMP F/E matches the `Get Assets` response                             |

### Failure and edge paths — **must be verified**

| #  | Scenario                | How to reproduce                                        | Expected behavior                                                                                                     |
| -- | ----------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| 5  | Mint transaction fails  | Force an issuance-limit overflow or insufficient gas    | Receive `status != 0x1` → **restore the deducted assets**                                                             |
| 6  | Burn transaction fails  | Same                                                    | **Do not grant the game assets**                                                                                      |
| 7  | Request exceeds balance | Set `amount` in `intent` higher than the actual balance | Rejected in `Validate Order` · no signature produced                                                                  |
| 8  | Asset ID mismatch       | Use an ID different from your server's response         | The request is rejected                                                                                               |
| 9  | Forged HMAC             | Sign with the wrong secret                              | `initialize` → `invalid mac` · rejected when the webhook arrives                                                      |
| 10 | Missing header          | Remove `X-HMAC-SIGNATURE`                               | `X-HMAC-Signature is required` (400)                                                                                  |
| 11 | Expired accessToken     | Call with an expired token                              | Rejected in `Get Assets` and `Validate Order`                                                                         |
| 12 | Expired UUID            | Enter the F/E more than 5 minutes after issuance        | Entry fails → call `initialize` again                                                                                 |
| 13 | Webhook resend          | Temporarily make your result API return 500             | Confirm the resend after 5 minutes (up to 20 times in 12 hours)                                                       |
| 14 | Duplicate `uuid`        | Receive the same result webhook twice                   | **No duplicate application**                                                                                          |
| 15 | Contract not linked     | Attempt a Mint before linking                           | It does not work → [4-2.3](https://docs.onechain.nexus/docs/2-contracts#4-23-linking-the-token-contract-to-a-project) |

### Load and boundary cases

* Whether asset deduction is duplicated when the same user sends **back-to-back requests**
* Behavior when the issuance limit (`Issuance Limit per Period`) is reached, and the **UTC-0 reset**
* Precision when handling fractional assets (`balance: "1000.123"`)

***

## 5.10 FAQ

**I get an&#x20;**`invalid mac`**&#x20;error.**
The HMAC signature does not match. Check that you **Base64URL-decoded** the HMAC Key, that you sign and verify using the **raw body** instead of re-serializing, and that the secret is current. → [5.2](https://docs.onechain.nexus/docs/endpint-apis#52-request-integrity--hmac-sha256)

**What is the&#x20;**`method`**&#x20;value for Burn?**
It is `burn-permit`. Both the `burn_method` constant in `initialize` and the `Validate Order` request use `burn-permit`.

**I received the webhook multiple times.**
That is expected. If you do not return 200, it is resent up to 20 times over 12 hours. Handle idempotency by `uuid`.

**The transaction failed — what happens to the user's assets?**
Mint deducts assets first, so **your server must restore them**. Burn runs the transaction first, so on failure you simply **do not grant** the assets.

<br />