Authentication¶
Every request is authenticated with a signed API key (HMAC-SHA256). Your tenant administrator creates the key in the admin dashboard and gives it to your backend, which uses it to sign every request.
Overview¶
Every API request must include three headers computed from your API Key ID and Secret:
| Header | Description |
|---|---|
X-API-Key |
Your API Key ID (public identifier) |
X-Timestamp |
Current Unix timestamp in seconds |
X-Signature |
HMAC-SHA256 signature of the canonical request string |
Getting Your Credentials¶
Your tenant administrator creates the key in the admin dashboard (Settings → API Keys → Create API Key) and shares the Key ID and Secret with you. The secret is shown only once at creation.
The response contains:
| Field | Description |
|---|---|
keyId |
Public identifier -- use as X-API-Key header |
secret |
HMAC signing key -- never expose this publicly |
Store the secret securely
The secret is displayed only once at creation time. Store it in a secrets manager, environment variable, or secure vault. It cannot be retrieved later.
Signing a Request¶
Step 1: Build the Canonical String¶
The canonical string has four components separated by newlines:
| Component | Description | Example |
|---|---|---|
timestamp |
Unix epoch seconds (same as X-Timestamp) |
1708600000 |
METHOD |
HTTP method in uppercase | POST |
path |
Request path with leading slash, no host | /vaults |
bodyHash |
SHA-256 hex digest of the request body (empty string hash if no body) | e3b0c44298fc1c14... |
Step 2: Compute the HMAC Signature¶
Sign the canonical string with your secret using HMAC-SHA256, then encode the result as a lowercase hex string.
Step 3: Send the Request¶
Include all three headers:
Timestamp window
The server rejects requests with timestamps older than 30 seconds. Ensure your server clock is synchronized (NTP).
Code Examples¶
Python¶
import hashlib
import hmac
import time
import requests
API_KEY_ID = "your-key-id"
API_SECRET = "your-secret"
BASE_URL = "https://api.example.com"
def sign_request(method: str, path: str, body: str = "") -> dict:
timestamp = str(int(time.time()))
body_hash = hashlib.sha256(body.encode()).hexdigest()
canonical = f"{timestamp}\n{method}\n{path}\n{body_hash}"
signature = hmac.new(
API_SECRET.encode(), canonical.encode(), hashlib.sha256
).hexdigest()
return {
"X-API-Key": API_KEY_ID,
"X-Timestamp": timestamp,
"X-Signature": signature,
}
# GET request
path = "/vaults"
headers = sign_request("GET", path)
response = requests.get(f"{BASE_URL.rstrip('/')}{path}", headers=headers)
# POST request
path = "/vaults"
body = '{"externalId":"cust_123","name":"Alice"}'
headers = sign_request("POST", path, body)
headers["Content-Type"] = "application/json"
response = requests.post(f"{BASE_URL.rstrip('/')}{path}", headers=headers, data=body)
Node.js¶
const crypto = require("crypto");
const API_KEY_ID = "your-key-id";
const API_SECRET = "your-secret";
function signRequest(method, path, body = "") {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const canonical = `${timestamp}\n${method}\n${path}\n${bodyHash}`;
const signature = crypto
.createHmac("sha256", API_SECRET)
.update(canonical)
.digest("hex");
return {
"X-API-Key": API_KEY_ID,
"X-Timestamp": timestamp,
"X-Signature": signature,
};
}
// GET example
const headers = signRequest("GET", "/vaults");
fetch("https://api.example.com/vaults", { headers });
// POST example
const body = JSON.stringify({ externalId: "cust_123", name: "Alice" });
const postHeaders = signRequest("POST", "/vaults", body);
postHeaders["Content-Type"] = "application/json";
fetch("https://api.example.com/vaults", {
method: "POST",
headers: postHeaders,
body,
});
C¶
using System.Security.Cryptography;
using System.Text;
var apiKeyId = "your-key-id";
var apiSecret = "your-secret";
string SignRequest(string method, string path, string body = "")
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var bodyHash = Convert.ToHexStringLower(
SHA256.HashData(Encoding.UTF8.GetBytes(body)));
var canonical = $"{timestamp}\n{method}\n{path}\n{bodyHash}";
var signature = Convert.ToHexStringLower(
HMACSHA256.HashData(
Encoding.UTF8.GetBytes(apiSecret),
Encoding.UTF8.GetBytes(canonical)));
return signature; // Set X-API-Key, X-Timestamp, X-Signature headers
}
cURL (Bash)¶
API_KEY_ID="your-key-id"
API_SECRET="your-secret"
TIMESTAMP=$(date +%s)
METHOD="GET"
PATH_URL="/vaults"
BODY=""
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')
CANONICAL="${TIMESTAMP}\n${METHOD}\n${PATH_URL}\n${BODY_HASH}"
SIGNATURE=$(echo -ne "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | awk '{print $NF}')
curl "https://api.example.com${PATH_URL}" \
-H "X-API-Key: $API_KEY_ID" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE"
IP Allowlist¶
API keys can optionally be restricted to specific IP addresses. If an allowlist is configured, requests from non-allowed IPs receive 401 Unauthorized.
Configure IP allowlists from the dashboard under Settings > API Keys > Edit.
Error Responses¶
| Status | Meaning |
|---|---|
401 Unauthorized |
Missing, invalid, or expired API key. Signature mismatch. IP not allowed. Timestamp drift > 30s. Replay — the same (keyId, timestamp, signature) tuple was already accepted within the drift window; re-sign with a fresh timestamp. |
403 Forbidden |
Valid credentials but insufficient scope for the requested operation. |
429 Too Many Requests |
Rate limit exceeded (120 req/min/key by default). Honour Retry-After. |
Beyond authentication¶
Three runtime behaviors are part of the partner contract — read them before you ship:
- Idempotency-Key is required on
POST /transactions/withdrawandPOST /transactions/transfer. Generate a UUID per logical operation; replays return the original response. - Replay protection. Each accepted signature is single-use within the 30-second drift window — your retry MUST regenerate the signature.
- Rate limit. 120 authenticated requests / minute / key, sliding
window.
429includesRetry-After.
Full reference: Reliability & Limits.
Next Steps¶
- Quick Start -- Make your first API calls with signed requests.
- Reliability & Limits -- Idempotency, rate limits, retries.
- API Reference -- Full interactive endpoint documentation.