Authentication
HashNut API v3.0.0 uses HMAC-SHA256 signature-based authentication for all API requests. This ensures secure, tamper-proof API communication.
Required Credentials
Before making API requests, you need:
accessKeyId: Your merchant access key ID (public identifier)apiKey: Your API key (private secret for request signing)
Request Signature Generation
All authenticated API requests must include the following headers:
| Header | Description | Example |
|---|---|---|
hashnut-request-uuid | Unique UUID for the request | 550e8400-e29b-41d4-a716-446655440000 |
hashnut-request-timestamp | Timestamp (nonce) in milliseconds | 1704067200000 |
hashnut-request-sign | HMAC-SHA256 signature (Base64 encoded) | <base64-encoded-signature> |
Content-Type | Request content type | application/json |
Signature Algorithm
Step-by-Step Process
Algorithm Steps:
- Generate UUID: Create a unique UUID (e.g.,
550e8400-e29b-41d4-a716-446655440000) - Get Timestamp: Current timestamp in milliseconds (e.g.,
1704067200000) - Stringify Body: Convert request body to JSON string (no whitespace, exact format)
- Concatenate: Combine
uuid + timestamp + body(no separators) - Compute HMAC: HMAC-SHA256 using
apiKeyas secret - Base64 Encode: Encode the HMAC result in Base64
- Set Header: Use Base64 string as
hashnut-request-signheader value
Code Examples
- JavaScript/TypeScript
- Python
- Java
import crypto from 'crypto';
function generateSignature(apiKey: string, body: object): {
uuid: string;
timestamp: string;
signature: string;
} {
// Step 1: Generate UUID
const uuid = crypto.randomUUID();
// Step 2: Get timestamp
const timestamp = Date.now().toString();
// Step 3: Stringify body (no whitespace)
const bodyString = JSON.stringify(body);
// Step 4: Concatenate
const signBody = uuid + timestamp + bodyString;
// Step 5 & 6: HMAC-SHA256 + Base64
const signature = crypto
.createHmac('sha256', apiKey)
.update(signBody)
.digest('base64');
return {
uuid,
timestamp,
signature
};
}
// Usage
const { uuid, timestamp, signature } = generateSignature(
'your-api-key',
{
accessKeyId: 'your-access-key-id',
merchantOrderId: 'order-123',
chainCode: 'erc20',
coinCode: 'usdt',
amount: 0.01
}
);
// Set headers
const headers = {
'hashnut-request-uuid': uuid,
'hashnut-request-timestamp': timestamp,
'hashnut-request-sign': signature,
'Content-Type': 'application/json'
};
import hmac
import hashlib
import base64
import json
import uuid
import time
def generate_signature(api_key: str, body: dict) -> dict:
# Step 1: Generate UUID
request_uuid = str(uuid.uuid4())
# Step 2: Get timestamp
timestamp = str(int(time.time() * 1000))
# Step 3: Stringify body (no whitespace)
body_string = json.dumps(body, separators=(',', ':'))
# Step 4: Concatenate
sign_body = request_uuid + timestamp + body_string
# Step 5 & 6: HMAC-SHA256 + Base64
signature = base64.b64encode(
hmac.new(
api_key.encode('utf-8'),
sign_body.encode('utf-8'),
hashlib.sha256
).digest()
).decode('utf-8')
return {
'uuid': request_uuid,
'timestamp': timestamp,
'signature': signature
}
# Usage
result = generate_signature(
'your-api-key',
{
'accessKeyId': 'your-access-key-id',
'merchantOrderId': 'order-123',
'chainCode': 'erc20',
'coinCode': 'usdt',
'amount': 0.01
}
)
# Set headers
headers = {
'hashnut-request-uuid': result['uuid'],
'hashnut-request-timestamp': result['timestamp'],
'hashnut-request-sign': result['signature'],
'Content-Type': 'application/json'
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HashnutAuth {
private static final String HMAC_SHA256 = "HmacSHA256";
public static AuthHeaders generateSignature(String apiKey, Object body) throws Exception {
// Step 1: Generate UUID
String uuid = UUID.randomUUID().toString();
// Step 2: Get timestamp
String timestamp = String.valueOf(System.currentTimeMillis());
// Step 3: Stringify body (no whitespace)
ObjectMapper mapper = new ObjectMapper();
String bodyString = mapper.writeValueAsString(body);
// Step 4: Concatenate
String signBody = uuid + timestamp + bodyString;
// Step 5 & 6: HMAC-SHA256 + Base64
Mac mac = Mac.getInstance(HMAC_SHA256);
SecretKeySpec secretKey = new SecretKeySpec(
apiKey.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256
);
mac.init(secretKey);
byte[] hash = mac.doFinal(signBody.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getEncoder().encodeToString(hash);
return new AuthHeaders(uuid, timestamp, signature);
}
}
// Usage
AuthHeaders headers = HashnutAuth.generateSignature(
"your-api-key",
new CreateOrderRequest(
"your-access-key-id",
"order-123",
"erc20",
"usdt",
new BigDecimal("0.01")
)
);
Important Notes
Body Stringification
⚠️ Critical: The request body must be stringified exactly as it will be sent:
- No extra whitespace: Use
JSON.stringify(obj)or equivalent without formatting - Consistent ordering: Use the same key order (or use sorted keys)
- Exact format: Match the exact JSON format that will be sent in the request
Timestamp Requirements
- Format: Milliseconds since Unix epoch (e.g.,
1704067200000) - Freshness: Timestamps should be recent (within 5 minutes)
- Uniqueness: Combine with UUID to prevent replay attacks
UUID Requirements
- Format: Standard UUID v4 format
- Uniqueness: Each request must have a unique UUID
- Generation: Use cryptographically secure random UUID generation
Complete Request Example
curl -X POST 'https://testnet.hashnut.io/api/v3.0.0/pay/createPayOrderOnSplitWalletWithApiKey' \
-H 'hashnut-request-uuid: 550e8400-e29b-41d4-a716-446655440000' \
-H 'hashnut-request-timestamp: 1704067200000' \
-H 'hashnut-request-sign: <base64-encoded-hmac-sha256>' \
-H 'Content-Type: application/json' \
-d '{
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"merchantOrderId": "order-123",
"chainCode": "erc20",
"coinCode": "usdt",
"amount": 0.01
}'
Error Responses
Invalid Signature
{
"code": -2,
"msg": "Invalid signature or credentials",
"data": null
}
Common Causes:
- Incorrect
apiKeyused for signing - Body stringified incorrectly (whitespace, key order)
- Missing or incorrect headers
- Timestamp format incorrect
Missing Headers
{
"code": -2,
"msg": "Missing required headers",
"data": null
}
Required Headers:
hashnut-request-uuidhashnut-request-timestamphashnut-request-signContent-Type: application/json
Security Best Practices
-
Secure Key Storage:
- Never commit API keys to version control
- Use environment variables or secrets management
- Rotate keys regularly
-
Signature Verification:
- Always verify signatures match expected format
- Use constant-time comparison for signature verification
- Log authentication failures for monitoring
-
Request Security:
- Use HTTPS for all API requests
- Implement request timeout handling
- Validate all request parameters
-
Key Management:
- Use separate keys for testnet and production
- Implement key rotation strategy
- Monitor key usage for anomalies
Testing Authentication
Test Signature Generation
// Test with known values
const testBody = { accessKeyId: "test", amount: 1.0 };
const { uuid, timestamp, signature } = generateSignature("test-key", testBody);
console.log("UUID:", uuid);
console.log("Timestamp:", timestamp);
console.log("Signature:", signature);
Verify Signature Locally
// Verify your signature generation
function verifySignature(apiKey, uuid, timestamp, body, expectedSignature) {
const signBody = uuid + timestamp + JSON.stringify(body);
const computed = crypto
.createHmac('sha256', apiKey)
.update(signBody)
.digest('base64');
return computed === expectedSignature;
}
Next Steps
- Learn about API Endpoints
- Set up Webhooks
- Review Error Codes
Ready to make API calls? Check out Order Endpoints →