Skip to content

Authentication ​

HashNut uses HMAC-SHA256 signatures to authenticate API requests. Some endpoints use header-based signing, while others use an accessSign field in the request body.

HMAC-SHA256 Signing Process ​

The signing process consists of four steps:

Step 1: Generate Request Metadata ​

Generate a unique string and a Unix timestamp in milliseconds for each request.

Step 2: Build the Signing String ​

Concatenate the three components without any separator:

signString = uuid + timestamp + requestBody

Where requestBody is the JSON string of the request body.

Step 3: Compute the Signature ​

Compute HMAC-SHA256 using your Secret Key, then Base64-encode the result:

signature = base64( hmac_sha256( secretKey, signString ) )

Step 4: Set Request Headers ​

Include the following headers in your HTTP request:

HeaderDescription
hashnut-request-uuidThe unique string from Step 1; must differ on every request (replays are rejected)
hashnut-request-timestampThe Unix timestamp in milliseconds from Step 1; must be within ±5 minutes of server time
hashnut-request-signThe Base64-encoded HMAC-SHA256 signature
Content-TypeMust be application/json

Code Examples ​

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.UUID;

public class HashNutSign {
    public static String[] sign(String secretKey, String body) throws Exception {
        String reqUUID = UUID.randomUUID().toString();
        String timestamp = String.valueOf(System.currentTimeMillis()); // milliseconds

        String signString = reqUUID + timestamp + body;

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"));
        String signature = Base64.getEncoder().encodeToString(
                mac.doFinal(signString.getBytes()));

        return new String[]{reqUUID, timestamp, signature};
    }
}
go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"time"

	"github.com/google/uuid"
)

func sign(secretKey, body string) (reqUUID, timestamp, signature string) {
	reqUUID = uuid.New().String()
	timestamp = fmt.Sprintf("%d", time.Now().UnixMilli()) // milliseconds

	signString := reqUUID + timestamp + body

	mac := hmac.New(sha256.New, []byte(secretKey))
	mac.Write([]byte(signString))
	signature = base64.StdEncoding.EncodeToString(mac.Sum(nil))

	return reqUUID, timestamp, signature
}
js
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');

function sign(secretKey, body) {
  const reqUUID = uuidv4();
  const timestamp = Date.now().toString(); // milliseconds

  const signString = reqUUID + timestamp + body;

  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(signString)
    .digest('base64');

  return { reqUUID, timestamp, signature };
}
python
import hmac
import hashlib
import base64
import uuid
import time

def sign(secret_key: str, body: str):
    req_uuid = str(uuid.uuid4())
    timestamp = str(int(time.time() * 1000))  # milliseconds

    sign_string = req_uuid + timestamp + body

    signature = base64.b64encode(
        hmac.new(
            secret_key.encode(),
            sign_string.encode(),
            hashlib.sha256
        ).digest()
    ).decode()

    return req_uuid, timestamp, signature

Access Sign (Body-Level Signing) ​

Query Order and Confirm Payment do not use header signing. They carry accessSign in the request body instead, and it is computed completely differently from the header signature above:

accessSign = HMACSHA256(secretKey, payOrderId and merchantOrderId sorted case-insensitively, then concatenated)

The result is an uppercase hexadecimal string (64 characters), not base64.

IMPORTANT

You never need to compute it — Create Order returns it as data.accessSign. Persist it alongside payOrderId and echo it back verbatim on later query and confirm calls. Because it depends only on those two IDs and not on the rest of the body, an order's accessSign is a fixed value.

Endpoint Authentication Methods ​

EndpointAuth MethodDescription
POST /v4.0.0/api/orders/createHeader signingCreate order
POST /v4.0.0/api/orders/cancelHeader signingCancel order
POST /v4.0.0/api/orders/queryBody accessSignQuery order
POST /v4.0.0/pay/orders/confirmBody accessSignConfirm payment
POST /v4.0.0/api/orders/supplementsNoneQuery supplements; only payOrderId format is checked
POST /v4.0.0/api/orders/supplements/latestNoneSame, most recent record only
POST /v4.0.0/config/*NonePublic config endpoints

WARNING

The supplement endpoints perform no authentication — a valid payOrderId is enough to read the data. Do not expose payOrderId on your own public pages or anywhere reachable from your frontend.

TIP

With the Go SDK or Java SDK, signing is handled for you: the client takes only your secretKey, and accessKeyId is a per-request parameter on Create Order.