Create Order
Create a new payment order. This endpoint uses header-based HMAC signing.
Endpoint
POST /v4.0.0/api/orders/createRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
accessKeyId | string | Yes | Your merchant Access Key ID |
merchantOrderId | string | Yes | Your order identifier, unique within (chain + merchant + channel) |
merchantChannel | string | No | Merchant channel; defaults to default |
blockChain | string | Yes | Blockchain name (e.g. ETH, TRON, BSC, POLYGON); case-insensitive |
tokenSymbol | string | Yes | Token symbol (e.g. usdt, usdc) |
amount | string | Yes | Payment amount as a decimal string (e.g. "10.50"); at most 2 decimal places |
splitterAddress | string | Yes | Your split contract address; must be active and belong to this merchant |
subject | string | No | Order subject / description |
expireDuration | integer | No | Expiration in seconds; defaults to 172800 (48 hours), capped at 259200 (72 hours) |
callbackUrl | string | No | Per-order frontend redirect URL; falls back to the api key's callbackUrl when omitted |
remark | string | No | Order remark |
extra | string | No | Extra order information |
param1 | string | No | Extra parameter 1 |
param2 | string | No | Extra parameter 2 |
WARNING
There is no notifyUrl in the request body. The backend notification URL is configured on the api key and cannot be overridden per order; only callbackUrl (the frontend redirect) can. The two point in opposite directions — see Notify URL vs Callback URL.
NOTE
merchantOrderId uniqueness is scoped to (chain + merchant address + merchant channel), so the same merchantOrderId may exist as one order per chain.
Example Request
{
"accessKeyId" : "01K...BB",
"merchantOrderId" : "aaf..-8",
"merchantChannel" : null,
"blockChain" : "ETH",
"tokenSymbol" : "usdt",
"amount" : "0.05",
"splitterAddress" : "0xfd5...f8b",
"subject" : "Test Order - API Doc",
"remark" : null,
"param1" : null,
"param2" : null,
"callbackUrl" : null,
"extra" : null,
"expireDuration" : 60000,
"payWebType" : null
}Example Response
{
"code" : 0,
"msg" : "success",
"ui" : null,
"version" : null,
"count" : 0,
"data" : {
"merchantAddress" : "0x17a...42",
"merchantChannel" : "default",
"blockChain" : "ETH",
"tokenSymbol" : "usdt",
"createChannel" : 1,
"merchantOrderId" : "aaf..-8",
"payOrderId" : "01K...H8",
"tokenAddress" : "0xdac17f958d2ee523a2206206994597c13d831ec7",
"receiptAddress" : "0x5...c4",
"amount" : 0.05,
"state" : 0,
"accessSign" : "AA377....F5",
"rate" : 80,
"obtainAmount" : 0.0496,
"platformFee" : 4.0E-4,
"expireDuration" : "60000",
"underPaid" : false,
"paidAmount" : 0.0,
"createTime" : 1785753243948
}
}NOTE
Fields in data that were never populated come back as null (e.g. payTxId, param1, errorMsg); the example omits them for readability. Also note that expireDuration, confirmCount, chainId, and eip712ChainId are JSON strings, not numbers — the server serializes Java long / Long as strings so JavaScript does not lose precision above 2^53.
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.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.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.createTime | date | Order creation time |
TIP
Store payOrderId, merchantOrderId, and accessSign on your side. accessSign is an HMAC the server computes over payOrderId + merchantOrderId with your secretKey — clients cannot recompute it — and both Query Order and Confirm Payment require it.
Redirect to the payment page
Once the order is created, build the payment page URL like this:
https://defi.hashnut.io/pay?accessSign=${accessSign}&merchantOrderId=${merchantOrderId}&payOrderId=${payOrderId}&blockChain=${blockChain}&payApiVersion=v4Result Codes
Business errors do not use a dedicated error-code range; everything comes back through code plus msg, and msg carries the actual reason:
code | Description |
|---|---|
0 | Success |
-1 | Failed |
-2 | Exception (parameter validation failure, unmet business rule, …; see msg) |
-3 | Unauthorized |
-4 | Unauthenticated |
Common msg values when creating an order:
msg | Meaning |
|---|---|
can not find api key by accessKeyId[...] | accessKeyId does not exist, or is not an api key |
invalid request sign | Signature mismatch — see Authentication |
request timestamp out of allowed window, ... | Timestamp outside the ±5 minute window; usually a skewed clock |
duplicate request uuid, replay rejected | hashnut-request-uuid was reused |
client ip [...] not allowed | Your egress IP is not in the api key's bindIp allowlist |
coin not support / chain not support | That chain or token is not enabled |
merchant order id already exist | Duplicate merchantOrderId for the same chain + merchant + channel |
eoa splitter address not active | The split contract is not active, or does not belong to this merchant |
the minimum unit of the amount is 0.01 | amount has more than 2 decimal places |
max expire duration is 259200 | expireDuration exceeds 72 hours |