Authentication Overview

All Bitnob API requests must include an HMAC-SHA256 signature. This ensures that each request is:

1.

Authentic: sent by you and not forged

2.

Integrity-protected: payload cannot be altered in transit

3.

Replay-resistant: each request can only be used once

Even if you’ve never integrated an API before, following these steps will get you signing requests in under 10 minutes.


1. Get your credentials

Before you begin, log in to the Bitnob Dashboard. Under API Settings, you will see:

Client ID

Secret (keep this private—never commit it to source control)

These two values uniquely identify your application and allow you to sign requests.


2. How it works

Every time you send an HTTP request:

You assemble a signing string from request details.

You compute an HMAC-SHA256 digest of that string using your secret.

You Base64-encode the digest and send it along with metadata in HTTP headers.

Bitnob validates the signature before processing your request.


3. Required HTTP headers

Include all four headers on every signed request:

HeaderValue
x-auth-clientYour Client ID
x-auth-timestampUnix epoch time in milliseconds (e.g. 1700000000000)
x-auth-nonceA new UUID v4 per request (e.g. 550e8400-…)
x-auth-signatureBase64-encoded HMAC-SHA256 signature
Note

Note The timestamp and nonce prevent replay attacks. If a request arrives with the same nonce or an old timestamp, it is rejected.


4. Building the signing string

Concatenate, in this exact order and with no separators:

clientId + HTTP_METHOD + request_path + timestamp + request_body

clientId: your Client ID string.

HTTP_METHOD: uppercase method, for example POST or GET.

request_path: the path and query (for example /v1/utilities/airtime)

timestamp: the same value sent in x-auth-timestamp

request_body: the raw JSON payload, or empty string ('') for no body


5. Computing the signature

Create an HMAC-SHA256 instance using your secret.

Feed the signing string into the HMAC.

Compute the digest (binary).

Base64-encode the digest.

This final Base64 string is your x-auth-signature.


6. Example: Node.js

Generate Signature

const signature = generateSignature(clientId, secret, method, path, timestamp, body);

console.log('x-auth-signature:', signature);


7. Full Example Request

cURL Request - Airtime Purchase

Content-Type must be application/json.

Ensure your clock is accurate within ±5 minutes of UTC.

Generate Signature (Java)

8. Error responses

HTTP StatusError CodeMeaning
401AUTH_INVALID_SIGNATURESignature missing or does not match expected
403AUTH_EXPIREDx-auth-timestamp is too old or too far in the future
403AUTH_REPLAYED_NONCEx-auth-nonce was already used
429RATE_LIMIT_EXCEEDEDToo many requests for this client

Each error returns a JSON body:

🔴 Error Response

9. Troubleshooting

Invalid signature

Confirm your secret matches the one in the Dashboard

Verify you included the raw JSON body—no added whitespace or line breaks

Ensure your timestamp and nonce headers match exactly the values used to compute the signature

Expired timestamp

Check your system clock

Sync via NTP or use a time-service library in your language

Replayed nonce

Generate a new UUID for every request

Do not reuse or hard-code the nonce value


When you’re new to APIs, terms like “HMAC” and “signatures” can feel like magic—so let’s demystify each piece of what happens under the hood when you call a Bitnob endpoint.

1. “Shared Secret” vs. “Client ID”

Client ID is like your username: it tells Bitnob who is calling.

Secret is like your password—but instead of sending it over the wire, you use it to generate a signature that proves you sent the request.

Why not just send the secret? Sending raw passwords is risky. Signatures let us verify you know the secret without exposing it.

2. What is HMAC-SHA256?

HMACstands for “Hash-based Message Authentication Code.”

It’s a cryptographic way to turn any message into a fixed-length digest that only someone with the secret key can reproduce.

SHA256 is the hashing function under the hood.

Together, HMAC-SHA256 gives us a fingerprint of your request that can’t be forged or tampered with.

3. Building the “Signing String”

Every time you make a request, you create a single text string by simply gluing these values together (in this exact order, with no extra characters):

clientId + HTTP_METHOD + request_path + timestamp + request_body

clientId your public identifier

HTTP_METHOD e.g. GET or POST

request_path the path on the server, e.g. /v1/payouts

timestamp a millisecond-precision Unix time

request_body the exact JSON payload, or an empty string if you’re not sending any data

Key point: the signing string must match exactly on both sides. Even a stray space or line break in the JSON will change the fingerprint.

4. Why Timestamp and Nonce Matter

1. Timestamp (x-auth-timestamp)

Prevents someone from capturing your signed request and replaying it hours later.

Bitnob rejects any timestamp more than ±5 minutes from the server clock.

2. Nonce (x-auth-timestamp)

A unique random ID (UUID v4) you generate for each request.

Ensures that even if two requests happen at the same millisecond with identical bodies, they can’t be replayed or mistaken for one another.

Together, timestamp + nonce give you one-time, short-lived tokens—just like a physical boarding pass expires once you’ve flown through the gate.

5. How Verification Works

When Bitnob receives your request, it:

Fetches your secret from its database (using your clientId).

Rebuilds the signing string from the incoming method, path, timestamp, and body.

Computes its own HMAC-SHA256 over that string.

Compares its computed signature to the one you sent (x-auth-signature).

Checks that the timestamp is fresh and the nonce hasn’t been seen before.

Only when all checks pass does the request move on to the payment logic.

6. Common Pitfalls & Tips

Clock drift: If your machine’s clock is off by more than a few minutes, authentication will fail. Always sync via NTP or use a reliable time-library.

JSON formatting: Your signing must use the exact bytes you send. Don’t pretty-print or minify in a way that changes whitespace.

Reusing nonces: Every request needs a brand-new UUID. Never hard-code a nonce in your code examples.

URL encoding: The signing string uses the raw path (including any query parameters), not a URL-encoded form.

7. Why HMAC vs. Other Methods?

Simplicity You don’t need OAuth flows, token refresh, or cookie jars—just two values (clientId + secret).

Performance HMACs are fast; there’s no extra round-trip to fetch tokens.

Security Replay protection is built-in via timestamp/nonce, and the secret never travels on the wire.


More on Nonce (One-Time Request Identifier)

A nonce is a unique, randomly generated value included with each API request to prevent replay attacks. In Bitnob’s HMAC-SHA256 authentication scheme, you supply a nonce in the x-auth-nonce header alongside your timestamp. Here’s how it works and why it matters.

1. Purpose of the Nonce

Replay Protection Even if an attacker captures a fully signed request (including valid signature and timestamp), they cannot resend it later because the nonce will already have been used. Any request containing an already-seen nonce is rejected.

Uniqueness Within Time Window Combined with a short timestamp window (±5 minutes), the nonce guarantees that no two valid requests share the same signature inputs.

2. Generation Requirements

Format Use a UUID v4 (128-bit random value) or an equally collision-resistant identifier. Example 550e8400-e29b-41d4-a716-446655440000

Per-Request Generate a new nonce for every single API call. Never reuse nonces across requests, even if the payload is identical.

Collision Risk UUID v4 has a 1 in 2¹²² chance of collision. For practical purposes, randomly generated UUIDs are collision-free.

3. Server-Side Handling

Store on Receipt When Bitnob’s gateway validates your request, it records the nonce in a fast in-memory store (Redis) with a short time-to-live (e.g. 5 minutes).

Uniqueness Check If a request arrives with a nonce that already exists in the store, Bitnob returns:

403 Forbidden

Automatic Expiry After the TTL expires, the nonce record is purged. This keeps storage bounded and ensures that old nonces cannot be replayed after the timestamp window closes.

4 Best Practices for Clients

Use a Standard UUID Library Most languages have built-in or well-tested UUID v4 generators. For example, in JavaScript:

import { v4 as uuidv4 } from 'uuid';

const nonce = uuidv4();

Generate Immediately Before Signing Create the nonce only after you assemble your request body and just before you compute the signature. This ensures the nonce matches the exact payload and timestamp.

Log Nonces for Debugging When troubleshooting authentication errors, log the nonce you generated. This helps correlate your client logs with gateway logs.

Clock Synchronization Even with a fresh nonce, if your timestamp is outside the allowed window, the request will still fail. Always sync your system clock via NTP or use a trusted time service.

5 Example Flow with Nonce

1. Generate timestamp and nonce

TIMESTAMP=$(date +%s%3N)
e.g. 1700000000000

NONCE=$(uuidgen)
e.g. 550e8400-e29b-41d4-a716-446655440000

2. Build request body

BODY='{"asset":"USDT","amount":50,"destination":{"type":"onchain","address":"0xAbC..."},"reference":"order_0001"}'

3. Construct signing string

SIGNING="${CLIENT_ID}POST/v1/payouts${TIMESTAMP}${BODY}${NONCE}"

4. Compute signature (including nonce in string)

SIGNATURE=$(printf '%s' "$SIGNING" \

| openssl dgst -binary -sha256 -hmac "$SECRET" \

| openssl base64)

5. Send request with nonce header
Payout Request
Note

Bitnob’s signing algorithm concatenates clientId + method + path + timestamp + body. The x-auth-nonce is checked in addition to the signature—do not include it in the signing string unless explicitly specified in future updates.

6. Troubleshooting Nonce Errors

AUTH_REPLAYED_NONCE – You reused a nonce. Generate a new one per request.

AUTH_EXPIRED – Even with a fresh nonce, the timestamp was too old or too far in the future. Sync your clock.

No Nonce Provided – Missing x-auth-nonce header results in a 401 Unauthorized. Always include all four headers.

By understanding these building blocks—shared secret, signing string, HMAC, timestamp, and nonce—you’ll be able to integrate with confidence. As you write your first scripts or SDK calls, keep this flow in mind, and you’ll see how each piece prevents a real-world attack or mistake. Welcome to secure, production-grade API integration!

Did you find this page useful?