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
| Item | Direction | Implemented by |
|---|---|---|
5.3 initialize | Your server → RAMP | Provided by RAMP |
5.4 Get Assets | RAMP B/E → your server | You |
5.5 Validate Order | RAMP B/E → your server | You |
5.6 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; 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
- 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-SIGNATUREfor theinitializerequest - RAMP → you: verify
X-HMAC-SIGNATUREonValidate OrderandHandle Order Resultrequests
- You → RAMP: generate
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.stringifychanges 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);
}timingSafeEqualthrows aRangeErrorwhen 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
base64urlsupport, convert manually.- Replace
-with+and_with/, pad with=until the length is a multiple of 4, then Base64-decode.
- Replace
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
initializePOST 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 |
Request Body
- Basic information (all optional)
player_idstring — the unique in-game player IDnamestring — character namewallet_addressstring — the player's wallet address (an empty string is allowed)serverstring — identifier of the game server they are connected to
intentobject — requirednetworkstring —mainnet/testnetproject_idstring — the ONE RAMP Project ID, a 32-character hex value. → 4-1.5tokenstring — the token contract addressmint_fee_bpsnumber — Mint fee rate in bps (1000= 10%) → 4-3.4burn_fee_bpsnumber — Burn fee — fixed at0mint_methodstring — fixed atmintburn_methodstring — fixed atburn-permitmaterials[]— the game assets consumed on Mintidstring — item IDamountnumber — quantity consumedicon_urlstring — icon image URLis_non_fungibleboolean — asset kind.true= individually identified item (sword, arrow, etc.) /false= fungible amount-based asset (gold, etc.)
outputs[]— the game assets granted on Burnid·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.uuidstring — 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
| 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
Get AssetsYou 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 Assetsendpoint 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}}| 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 initialize request | Y |
data.v1.assets[].balance | string | The user's actual balance | Y |
{
"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
Validate OrderYou 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 Orderendpoint 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
digestwith 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 / 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
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.methodisburn-permit,intent.typeisdisassemble, and thefrom/todirection 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
| 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 digest signed with the Validator Key (ECDSA) | Y |
{
"success": true,
"errorCode": null,
"data": {
"userSig": "0x58ea88cc20a571d2bc4f4a3ab687158e1924887c005a8a2cdce9a7c8f669bdbb222932f9e760b923b6f359870169d58a171d47516ee71167313e5068dbd84c631c",
"validatorSig": "0xfa7c12023378170c615bdd64be3e7aa195ff98b42fe84dad34348017fc1150db157e077dd5053328040b476479edebfe5d773bb8602de6bc088951de7c597fd31b"
}
}The Validator address that produced
validatorSigmust be registered in the console for RAMP's verification (step 14) to pass.
Failure response — errorCode
errorCodeIf validation fails, return success: false with an errorCode and do not sign the 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 |
{
"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, ④ to10011, and wallet-related rejections to10012is 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-SIGNATUREmatch? - ② Authentication — is
X-Dapp-Authorization(accessToken) valid and unexpired? - ③ Ownership — does the character in
X-Dapp-SessionIDbelong to that account? - ④ Asset and amount policy — for the asset IDs and amounts in
intent.fromandintent.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
uuidalready been processed? - ⑥ Signing — sign the
digestonly if everything above passed
Validator Signature (ECDSA)
- Key — the Validator private key you generated, whose address is registered in the console → 3.3 · 4-2.2
- Payload — the
digestdelivered in theValidate Orderrequest (a 32-byte hash) - Curve — secp256k1 (
signingKey.signinethers) - Return value — a 65-byte signature serialized as
r + s + v, returned asdata.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
Handle Order ResultYou 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 = success · 0x0 = 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
receipt.statusis not0x1, 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
| Item | Value |
|---|---|
| UUID validity | 5 minutes |
intent.mint_method | fixed at mint |
intent.burn_method | fixed at 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 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 in the initialize 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
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 |
| 500 | Internal Server Error | ramp not found | project_id missing or wrong | Check the Project ID in the console → 4-1.5 |
| 500 | Internal Server Error | ramp not exists | Invalid network value | Check mainnet / testnet |
Validate Order response error codes
Validate Order response error codesThe four errorCode values your server returns → 5.5 Failure response
By symptom
| Symptom | Check first |
|---|---|
| Mint/Burn does not work at all | The token contract ↔ project link status → 4-2.3 |
| 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 |
| 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 |
| 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 |
| 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
- ② Endpoint responses — do all three URLs return 200 from outside your network?
- ③ HMAC — is the secret current? (check the reissue history)
- ④ Network — does
intent.networkin the request match the console project's network? - ⑤ Limits — have you hit an issuance limit?
- ⑥ Transaction — look up the
tx_hashin 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 Testnet → 4-3.1
- Get test coins from the TESTNET Faucet → 1.5
- 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 |
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.
Updated 4 days ago