Query Order
Query the current state of a payment order and its supplement records.
Authentication
Query endpoints do not use header-based HMAC signing. They authenticate with the accessSign field in the request body:
accessSign = HMACSHA256(secretKey, payOrderId and merchantOrderId sorted case-insensitively, then concatenated)The result is an uppercase hexadecimal string (64 characters).
IMPORTANT
You never need to compute accessSign yourself — the Create Order response returns it as data.accessSign. Persist it alongside payOrderId and echo it back on later query and confirm calls. The official SDKs work exactly this way.
Query Order Status
Endpoint
POST /v4.0.0/api/orders/queryRequest
{
"payOrderId" : "01K...H8",
"merchantOrderId" : "aaf...-8",
"accessSign" : "AA377...F5"
}| Field | Type | Required | Description |
|---|---|---|---|
payOrderId | string | Yes | HashNut-generated order ID (26-character ULID) |
merchantOrderId | string | Yes | Your order identifier |
accessSign | string | Yes | The accessSign returned by Create Order, echoed verbatim |
WARNING
All three fields are required — the server reads all three keys directly and errors if any is missing.
Response
{
"code" : 0,
"msg" : "success",
"ui" : null,
"version" : null,
"count" : 0,
"data" : {
"merchantAddress" : "0x17a7...042",
"merchantChannel" : "default",
"blockChain" : "ETH",
"tokenSymbol" : "usdt",
"createChannel" : 1,
"accessChannel" : 0,
"merchantOrderId" : "aaff...-8",
"payOrderId" : "01KZ...H8",
"tokenAddress" : "0xdac17f958d2ee523a2206206994597c13d831ec7",
"receiptAddress" : "0x58b1...2c4",
"amount" : 0.05,
"state" : 1,
"accessSign" : "AA3777...6F5",
"payTxId" : "0x4a96...2a0",
"rate" : 80,
"obtainAmount" : 0.0496,
"platformFee" : 4.0E-4,
"expireDuration" : "60000",
"underPaid" : true,
"paidAmount" : 0.03,
"supplementCount" : 1,
"errorCode" : "failed",
"errorMsg" : "amount not enough",
"createTime" : 1785753243948,
"supplements" : [ {
"supplementId" : "01KZ...3",
"payOrderId" : "01K..H8",
"supplementAmount" : 0.02,
"paidAmount" : 0.0,
"underPaid" : false,
"receiptAddress" : "0x58...c4",
"state" : 0,
"reason" : "amount not enough",
"createTime" : 1785753770316
}, {
"supplementId" : "01KZ..BWT",
"payOrderId" : "01K..8",
"supplementAmount" : 0.04,
"paidAmount" : 0.02,
"underPaid" : true,
"payTxId" : "0x269...f7",
"receiptAddress" : "0x58b..c4",
"state" : 1,
"reason" : "amount not enough",
"errorCode" : "failed",
"errorMsg" : "amount not enough",
"createTime" : 1785753722283
} ]
}
}Response Fields
| Field | Type | Description |
|---|---|---|
code | integer | 0 for success, non-zero for error |
msg | string | Human-readable message |
data.merchantAddress | string | Merchant address that deployed the split contract |
data.merchantChannel | string | Merchant order channel; returns the default value default when the request leaves it empty |
data.blockChain | string | Blockchain name (e.g. ETH, TRON, BSC, POLYGON) |
data.tokenSymbol | string | Token symbol (e.g. usdt, usdc) |
data.createChannel | integer | Creation channel; 0 = created from the merchant dashboard, 1 = created via api key |
data.accessChannel | integer | Defaults to 0 |
data.merchantOrderId | string | The merchant order ID you supplied when creating the order |
data.payOrderId | string | Unique order ID generated by HashNut (26-character ULID) |
data.tokenAddress | string | Contract address of the order's payment token, e.g. the USDT contract address |
data.receiptAddress | string | Receipt address of the order; the customer sends the token here to pay |
data.amount | number | Amount due on the order |
data.state | integer | Order state (0 = INIT). See Order States |
data.accessSign | string | Credential required by later query / confirm calls — persist it |
data.payTxId | string | Transaction ID of the first payment against this order — persist it |
data.rate | integer | Fee rate; divide by rateBase for the actual ratio |
data.obtainAmount | number | Amount the merchant receives after the platform fee |
data.platformFee | number | Platform fee; only charged when the merchant withdraws |
data.expireDuration | string | Expiration in seconds |
data.underPaid | bool | Whether the order was underpaid. false while the customer has not paid; true once they pay less than the amount due, and it stays true after they top the order up in full |
data.paidAmount | number | Amount the customer has paid so far |
data.supplementCount | integer | Number of unpaid supplement records |
data.errorCode | string | Error code |
data.errorMsg | string | Error message |
data.createTime | date | Order creation time |
data.supplements | objectArray | Supplement records. Empty when the customer has not paid at all, or paid in full |
NOTE
The example above is an underpaid order, shown so the supplement records are visible. When the customer has not paid, or has paid in full, supplements is an empty array.
Supplement Record
| Field | Type | Description |
|---|---|---|
supplementId | string | Supplement record ID |
payOrderId | string | ID of the order that needs topping up |
supplementAmount | number | Amount still to be topped up |
paidAmount | number | Amount already topped up |
underPaid | bool | Whether the top-up covered the full amount |
payTxId | string | Transaction ID that paid this supplement record |
receiptAddress | string | Receipt address of this supplement record |
state | integer | Supplement state (0 = INIT). See Order States |
reason | string | Why the top-up is needed |
errorCode | string | Error code |
errorMsg | string | Error message |
createTime | date | Supplement record creation time |
NOTE
confirmCount, expireDuration, chainId, and eip712ChainId come back as JSON strings, not numbers — the server serializes Java long / Long as strings. The Go SDK reads them as json.Number, which accepts either form.
Query Supplements
Underpaid orders let the customer top up the difference; each top-up is a separate record.
Endpoint
POST /v4.0.0/api/orders/supplements # all records, returns an array
POST /v4.0.0/api/orders/supplements/latest # most recent only, returns a single object (null if none)Request
Both endpoints take the same body — only payOrderId:
{
"payOrderId" : "01KZ...H8"
}WARNING
These two endpoints verify neither accessSign nor a header signature — a well-formed payOrderId is enough to read the data. Do not expose payOrderId on your own public pages or anywhere reachable from your frontend.
Response
{
"code" : 0,
"msg" : "success",
"data" : [ {
"supplementId" : "01KZ...3",
"payOrderId" : "01K..H8",
"supplementAmount" : 0.02,
"paidAmount" : 0.0,
"underPaid" : false,
"receiptAddress" : "0x58...c4",
"state" : 0,
"reason" : "amount not enough",
"createTime" : 1785753770316
} ]
}| Field | Type | Description |
|---|---|---|
supplementId | string | Supplement record ID |
payOrderId | string | ID of the order that needs topping up |
supplementAmount | number | Amount still to be topped up |
paidAmount | number | Amount already topped up |
underPaid | bool | Whether the top-up covered the full amount |
payTxId | string | Transaction ID that paid this supplement record |
receiptAddress | string | Receipt address of this supplement record |
state | integer | Supplement state (0 = INIT). See Order States |
reason | string | Why the top-up is needed |
errorCode | string | Error code |
errorMsg | string | Error message |
createTime | date | Supplement record creation time |
TIP
Most integrations only need to poll Query Order Status and fetch supplement detail when supplementCount is greater than zero. Better still, subscribe to Notifications instead of polling.