Guides
NEXUS
Guides

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; for where to register them, see 4-3. Ramp Settings.

Wallet and contract addresses and Project IDs in the examples are masked in the form 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

ItemDirectionImplemented by
5.3 initializeYour server → RAMPProvided by RAMP
5.4 Get AssetsRAMP B/E → your serverYou
5.5 Validate OrderRAMP B/E → your serverYou
5.6 Handle Order ResultRAMP B/E → your serverYou

Path summary

NameMethod · Path
RAMP F/E entryhttps://ramp.crosstoken.io/exchange?uuid=…
Issue a UUIDPOST https://cross-ramp-api.crosstoken.io/api/v2/initialize
Game asset lookupGET {Get Assets}
Signature validationPOST {Validate Order}
Result receptionPOST {Handle Order Result}

Header summary

HeaderUsed inMeaning
X-HMAC-SIGNATUREinitialize · Validate Order · Handle Order ResultHMAC-SHA256 integrity signature
X-Dapp-AuthorizationGet Assets · Validate Order · Handle Order ResultBearer {accessToken}
X-Dapp-SessionIDSame as aboveUser character identifier

5.1 RAMP F/E Entry URL

https://ramp.crosstoken.io/exchange?uuid={{uuid}}&accessToken={USERACCESSTOKEN}&sessionId={USERSESSIONID}&network=testnet
ParameterDescription
uuidUnique identifier for the Mint/Burn. Issued by 5.3; valid for 5 minutes
accessTokenUsed for RAMP → dApp server requests. Your dApp server must verify it
sessionIdUser session / character identifier. Your dApp server verifies it
networktestnet / mainnet

5.2 Request Integrity — HMAC-SHA256

  • Key — the HMAC Key (Secret) issued when the RAMP contract is deployed → 4-2.2
    • 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
  • HeaderX-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)

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 throws a RangeError 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)

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

HeaderValue
Content-Typeapplication/json
X-HMAC-SIGNATUREThe HMAC signature → 5.2

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
    • token string — the token contract address
    • mint_fee_bps number — Mint fee rate in bps (1000 = 10%) → 4-3.4
    • burn_fee_bps number — Burn fee — fixed at 0
    • mint_method string — fixed at mint
    • burn_method string — fixed at 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

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
{
  "code": 200,
  "message": "OK",
  "data": {
    "uuid": "9cf3a7e5-7d2c-4ef3-ba6f-911d5078416b"
  }
}

Failure responses

Situationcodemessagedata
X-HMAC-Signature missing400Bad RequestX-HMAC-Signature is required
HMAC signature mismatch500Internal Server Errorinvalid mac
project_id missing500Internal Server Errorramp not found
Invalid network value500Internal Server Errorramp 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 endpoint in the console.
  • You must verify X-Dapp-Authorization (accessToken) and respond according to the result.

Request

GET /api/assets
Host: https://your-server.com
Content-Type: application/json
X-Dapp-Authorization: Bearer {{accessToken}}
X-Dapp-SessionID: {{sessionId}}
HeaderDescription
X-Dapp-AuthorizationThe accessToken issued by your server — the accessToken value from the RAMP F/E URL query
X-Dapp-SessionIDThe user character identifier you manage — the sessionId value from the RAMP F/E URL query

Response

FieldTypeDescriptionRequired
successbooleanWhether the request succeededY
errorCodestringError code (null on success)Y
dataobjectResponse data (null on failure)Y
data.v1objectVersion 1 dataY
data.v1.player_idstringUnique ID of the user's characterN
data.v1.namestringName of the user's characterN
data.v1.wallet_addressstringThe user's wallet addressN
data.v1.serverstringConnected server informationN
data.v1.assetsarrayThe user's game asset balancesY
data.v1.assets[].idstringUnique game asset identifier — must match the asset ID in the initialize requestY
data.v1.assets[].balancestringThe user's actual balanceY
{
  "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 endpoint in the console.

What your server must do

  • ① Verify HMAC integrityX-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 digest 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

HeaderDescription
X-HMAC-SIGNATUREThe request raw data signed with the HMAC Key issued by the RAMP console
X-Dapp-AuthorizationThe accessToken issued by your server
X-Dapp-SessionIDThe user character identifier you manage

Request Body

FieldTypeDescriptionRequired
user_sigstringThe value signed by the user with ONEpocketY
user_addressstringThe user's ONEpocket addressY
project_idstringThe Project ID created in the consoleY
digeststringHash digest of the transaction data. The payload to sign with the Validator KeyY
uuidstringUnique request identifierY
intentobjectToken issuance/burn informationY
intent.methodstringMethod to execute — mint / burn-permitY
intent.typestringassemble = issue · disassemble = burnY
intent.fromarraySource asset listY
intent.from[].typestringasset = game asset · ERC20 = game tokenY
intent.from[].idstringGame asset ID or token contract addressY
intent.from[].amountnumberQuantity usedY
intent.toarrayDestination asset listY
intent.to[].typestringERC20 = game token · asset = game assetY
intent.to[].idstringToken contract address or game asset IDY
intent.to[].amountnumberQuantityY
intent.target_candidateobjectTarget candidate information (additional option)N

Request example — Mint

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.

{
  "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

FieldTypeDescriptionRequired
successbooleanWhether the request succeededY
errorCodestringError code (null on success)Y
data.userSigstringThe user signature you receivedY
data.validatorSigstringThe digest signed with the Validator Key (ECDSA)Y
{
  "success": true,
  "errorCode": null,
  "data": {
    "userSig": "0x58ea88cc20a571d2bc4f4a3ab687158e1924887c005a8a2cdce9a7c8f669bdbb222932f9e760b923b6f359870169d58a171d47516ee71167313e5068dbd84c631c",
    "validatorSig": "0xfa7c12023378170c615bdd64be3e7aa195ff98b42fe84dad34348017fc1150db157e077dd5053328040b476479edebfe5d773bb8602de6bc088951de7c597fd31b"
  }
}

The Validator address that produced validatorSig 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 digest.

CodeMessage shown to the userKoreanMeaning
10010Game authentication failed. Please log in again.게임 인증에 실패했습니다. 다시 로그인해 주세요.Game account authentication failed
10011Transaction request failed. Please try again.트랜잭션 요청에 실패했습니다. 다시 시도해 주세요.Transaction request processing failed
10012Unverified wallet. Please contact customer support.미인증 지갑입니다. 고객센터에 문의해 주세요.The wallet is not verified
90001Transaction timed out. Please wait a moment and try again.트랜잭션 제한 시간입니다. 잠시 후 다시 시도해 주세요.Mainly used when an in-game token cooldown applies
{
  "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 console3.3 · 4-2.2
  • 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)

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

FieldTypeDescription
session_idstringSession identifier managed by the client and server
uuidstring (UUID)Unique identifier for a single request — recommended as the idempotency key
tx_hashstringBlockchain transaction hash — recommended to retain
receiptobjectTransaction execution receipt
receipt.statusstring0x1 = success · 0x0 = failure
receipt.typestringTransaction type (EIP-1559 or a chain-specific type; 0x7 = gas sponsored)
receipt.rootstringState root (usually empty)
receipt.cumulativeGasUsedstringCumulative gas used within the block
receipt.logsBloomstringEvent log bloom filter
receipt.logsarrayArray of event logs emitted during the transaction
receipt.logs[].addressstringAddress of the contract that emitted the event
receipt.logs[].topicsstring[]Event signature and indexed parameters
receipt.logs[].datastringNon-indexed event data
receipt.logs[].blockNumberstringNumber of the block containing the event
receipt.logs[].transactionHashstringHash of the transaction containing the event
receipt.logs[].transactionIndexstringTransaction index within the block
receipt.logs[].blockHashstringBlock hash
receipt.logs[].blockTimestampnumberBlock timestamp (0 on some chains)
receipt.logs[].logIndexstringEvent index within the transaction
receipt.logs[].removedbooleanWhether it was removed by a chain reorganization
receipt.transactionHashstringTransaction hash
receipt.contractAddressstringAddress of the created contract (0x0 if not a creation TX)
receipt.gasUsedstringActual gas used
receipt.effectiveGasPricestringEffective gas price applied
receipt.blockHashstringBlock hash
receipt.blockNumberstringBlock number
receipt.transactionIndexstringTransaction index within the block
intentobjectIn-game Mint/Burn information
intent.methodstringThe method that ran (mint / burn-permit)
intent.typestringRAMP internal processing type (assemble / disassemble)
intent.fromarrayAssets consumed (type · id · amount)
intent.toarrayTokens issued (type · id · amount)
intent.target_candidateobjectTarget candidate information (currently an empty object)
intent.fee_ratenumberFee rate in bps (2000 = 20%)
intent.actualstringThe amount the user actually receives after the fee

If receipt.status is not 0x1, the blockchain transaction failed and you must restore the game assets.

Request example (abbreviated)

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

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

5.7 Constants

ItemValue
UUID validity5 minutes
intent.mint_methodfixed at mint
intent.burn_methodfixed at burn-permit
intent.typeassemble = Mint · disassemble = Burn
intent.from[].type / to[].typeasset = game asset · ERC20 = game token
intent.burn_fee_bpsfixed at 0
Fee unitbps100 = 1% · 1000 = 10% · 2000 = 20%
Minimum fee rate10% or higher
Where the fee rate is setintent.mint_fee_bps in the initialize request (not the console)
Where the fee address is setConsole Ramp SettingsFee Settings
Transaction success checkreceipt.status === "0x1"
Gas-sponsored transaction typereceipt.type === "0x7"
Webhook retriesUp to 20 times within 12 hours — 5 min × 2 · 15 min × 7 · 60 min × 10
Expected Webhook responseHTTP 200
Issuance limit reset basisUTC-0
Contract type coveredERC-20

5.8 Error Reference

initialize failure responses

codemessagedataCauseWhat to do
400Bad RequestX-HMAC-Signature is requiredMissing headerAdd the header
500Internal Server Errorinvalid macHMAC signature mismatchSign the raw body and refresh the secret → 5.2
500Internal Server Errorramp not foundproject_id missing or wrongCheck the Project ID in the console → 4-1.5
500Internal Server Errorramp not existsInvalid network valueCheck mainnet / testnet

Validate Order response error codes

The four errorCode values your server returns → 5.5 Failure response

By symptom

SymptomCheck first
Mint/Burn does not work at allThe token contract ↔ project link status4-2.3
Assets show as 0 in the F/EWhether assets[].id in the Get Assets response matches the asset ID in the initialize request → 3.8
Cannot enter the F/EUUID expired after 5 minutes · network mismatch
Signature validation keeps failingThe HMAC secret · the registered Validator address
The result webhook keeps arrivingWhether you return HTTP 200 · whether uuid idempotency is implemented
The user receives a different amount than expectedCheck intent.fee_rate and intent.actual4-3.4
Issuance suddenly stopsIssuance Limit per Period or Max Supply exhausted
RAMP itself is not exposedThe Enable Ramp toggle · contract link status → 4-3.1
Settings are not savedSave Changes was not clicked in Ramp Settings

Incident triage order

  • ① Console link status — is the token ↔ project link still in place? → 4-2.3
  • ② 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

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 Testnet4-3.1
  • Get test coins from the TESTNET Faucet1.5
  • Make sure the test account has enough game assets

Happy paths

#ScenarioWhat to check
1Mint succeedsGame assets deducted · tokens received · receipt.status = 0x1
2Fee calculationThe user's received amount matches intent.actual · fee_rate matches the requested value
3Burn succeedsTokens burned · game assets granted · intent.type = disassemble
4Asset displayThe balance shown in RAMP F/E matches the Get Assets response

Failure and edge paths — must be verified

#ScenarioHow to reproduceExpected behavior
5Mint transaction failsForce an issuance-limit overflow or insufficient gasReceive status != 0x1restore the deducted assets
6Burn transaction failsSameDo not grant the game assets
7Request exceeds balanceSet amount in intent higher than the actual balanceRejected in Validate Order · no signature produced
8Asset ID mismatchUse an ID different from your server's responseThe request is rejected
9Forged HMACSign with the wrong secretinitializeinvalid mac · rejected when the webhook arrives
10Missing headerRemove X-HMAC-SIGNATUREX-HMAC-Signature is required (400)
11Expired accessTokenCall with an expired tokenRejected in Get Assets and Validate Order
12Expired UUIDEnter the F/E more than 5 minutes after issuanceEntry fails → call initialize again
13Webhook resendTemporarily make your result API return 500Confirm the resend after 5 minutes (up to 20 times in 12 hours)
14Duplicate uuidReceive the same result webhook twiceNo duplicate application
15Contract not linkedAttempt a Mint before linkingIt does not work → 4-2.3

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 invalid mac 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

What is the method 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.