Skip to main content

Overview

Every request to the Coinut Partner Ramp API must be cryptographically signed using HMAC-SHA256. The server verifies the signature against your API Secret stored on the Coinut side. Requests without a valid signature are rejected with 401 Unauthorized or 422 Unprocessable Entity.

Required Headers

Include these four headers in every authenticated request:
HeaderRequiredDescription
X-API-KeyYesYour API key
X-TimestampYesUnix timestamp in seconds (as string)
X-NonceYesA unique random string per request (e.g., UUID)
X-SignatureYesHMAC-SHA256 signature (lowercase hex string)
The latest machine-readable contract also declares an X-API-Secret security scheme, but the narrative signing instructions only require X-API-Key, X-Timestamp, X-Nonce, and X-Signature. Confirm the expected header set in sandbox before production rollout.

How to Generate the Signature

Signature Generation Code Examples

import crypto from 'crypto';
import fetch from 'node-fetch';

const API_KEY = 'YOUR_API_KEY';
const API_SECRET = 'YOUR_API_SECRET';

async function signAndRequest() {
  const method = 'POST';
  const host = 'ramp.hexarails.ai'; // or ramp-sandbox.hexarails.ai
  const path = '/payment/estimate';
  const query = ''; // e.g. 'currency=USDT&network=TRX'

  const bodyObject = {
    // your request body here
  };
  const body = JSON.stringify(bodyObject);

  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  const bodyHash = crypto.createHash('sha256').update(body).digest('hex');

  const canonical = [
    method.toUpperCase(),
    host,
    path,
    query,
    bodyHash,
    timestamp,
    nonce,
  ].join('\n');

  const signature = crypto
    .createHmac('sha256', API_SECRET)
    .update(canonical)
    .digest('hex');

  const url = `https://${host}${path}${query ? '?' + query : ''}`;

  const res = await fetch(url, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY,
      'X-Timestamp': timestamp,
      'X-Nonce': nonce,
      'X-Signature': signature,
    },
    body,
  });

  const data = await res.json();
  console.log(data);
}

signAndRequest().catch(console.error);

Complete Authenticated Request Example

The following shows a full GET /balance request with all headers and a sample response.

Request

curl https://ramp-sandbox.hexarails.ai/balance \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-Timestamp: 1717900800" \
  -H "X-Nonce: 550e8400-e29b-41d4-a716-446655440000" \
  -H "X-Signature: a3f7c9...e2d1b8"

Response (New API — 200 OK)

status
string
Always SUCCESS for successful requests.
data
array
List of balance objects, one per currency.
{
  "status": "SUCCESS",
  "data": [
    {
      "balance": "1000.00",
      "currency": "USD"
    }
  ]
}

Error Responses

Status CodeMeaningResolution
422Invalid parameters, authentication failure, or failed operationVerify the canonical string, headers, and request body
401Possible gateway or middleware authentication failureHandle defensively even though the latest contract primarily documents 422

IP Whitelisting

Restrict API access to your server IP addresses for additional security.

Get Current IP Whitelist

curl https://ramp-sandbox.hexarails.ai/ip-white-list \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Update IP Whitelist

curl -X POST https://ramp-sandbox.hexarails.ai/ip-white-list \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -d '{
    "ipWhiteList": ["115.236.134.118", "47.89.58.120"]
  }'
FieldRequiredDescription
ipWhiteListYesArray of IP addresses. Empty array [] means no restriction.

Best Practices

Use Unique Nonces

Never reuse a nonce. Each request must have a unique X-Nonce value.
Correct
const nonce = crypto.randomUUID();  // always unique
Incorrect
const nonce = "1234567890";  // never reuse hardcoded nonces

Use Timestamp in Seconds

The X-Timestamp must be a Unix timestamp in seconds (not milliseconds or microseconds).
LanguageExpressionOutput
JavaScriptMath.floor(Date.now() / 1000)1717900800
Pythonstr(int(time.time()))1717900800
JavaString.valueOf(Instant.now().getEpochSecond())1717900800

Store Secrets Server-Side

DoDon’t
Store in environment variables (process.env.API_SECRET)Hardcode in source files
Use a secrets manager (AWS Secrets Manager, HashiCorp Vault)Log to console or debug output
Load at runtime from secure storagePass in URL query parameters
Restrict access via IAM rolesCommit to Git repositories

Rotate Secrets Regularly

  1. Generate a new key pair from the partner dashboard
  2. Update your server environment with the new secret
  3. Test the new credentials in sandbox
  4. Revoke the old key pair
You can have multiple active key pairs during rotation. Revoke the old key only after confirming the new one works in production.