Authentication

The Merchant API authenticates every request with an API key pair and an HMAC-SHA256 signature. There are no bearer tokens to refresh and no cookies — each request carries its own signature, computed from the request itself, so it cannot be replayed against a different route or after it has expired.

API keys

Create an API key pair in the merchant portal. A pair has two parts:

  • Name
    key id
    Type
    string
    Description

    The public identifier, prefixed pk_live_ or pk_test_. Sent in the X-Api-Key header. Safe to log.

  • Name
    secret
    Type
    string
    Description

    The shared HMAC secret, prefixed sk_. Used to compute the signature and never sent over the wire. Shown only once at creation — store it securely.

The signing scheme

For each request, build a canonical string from five components joined by newlines (\n):

METHOD\nPATH\nCANONICAL_QUERY\nTIMESTAMP\nSHA256_HEX(BODY)
  • METHOD — the uppercase HTTP method, e.g. POST.
  • PATH — the request path without the query string, e.g. /api/v1/charges.
  • CANONICAL_QUERY — the query string's key=value pairs sorted lexicographically and rejoined with & (an empty string when the request has no query). Sorting makes the signature independent of the order you send the parameters in.
  • TIMESTAMP — the current Unix time in seconds, as a string. It must be within 5 minutes of the server clock or the request is rejected.
  • SHA256_HEX(BODY) — the lowercase hex SHA-256 of the raw request body bytes. For a request with no body (most GETs), this is the SHA-256 of the empty string.

The signature is the lowercase hex HMAC-SHA256(secret, canonical). Send these three headers with every request:

  • Name
    X-Api-Key
    Type
    string
    Description

    Your public key id, e.g. pk_live_8f3aK2x9.

  • Name
    X-Timestamp
    Type
    string
    Description

    The same Unix-seconds timestamp used in the canonical string.

  • Name
    X-Signature
    Type
    string
    Description

    The lowercase hex HMAC-SHA256 signature.

Signing a request

The examples below sign and send a POST /api/v1/charges. The same helper works for any endpoint: pass the method, path, and (for write requests) the exact JSON body you will send.

Because the body is hashed, you must sign the exact bytes you transmit — serialize the JSON once and reuse that string for both the hash and the request body.

Signed request

#!/usr/bin/env bash
KEY_ID="pk_live_8f3aK2x9"
SECRET="sk_live_your_secret"
BASE="https://api.fluxa.example"

METHOD="POST"
PATH="/api/v1/charges"
QUERY=""   # raw query for list endpoints, e.g. "limit=10&status=paid"
BODY='{"merchant_order_id":"order-1001","amount":"49.90","currency":"USDT","network":"tron"}'

TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
# CANONICAL_QUERY: split on &, sort, rejoin (empty stays empty).
CANON_QUERY=$(printf '%s' "$QUERY" | tr '&' '\n' | sort | paste -sd '&' -)
CANON=$(printf '%s\n%s\n%s\n%s\n%s' "$METHOD" "$PATH" "$CANON_QUERY" "$TS" "$BODY_HASH")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl "$BASE$PATH${QUERY:+?$QUERY}" \
  -H "X-Api-Key: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Authentication errors

A request that fails authentication returns 401 Unauthorized with one of these codes in the error envelope:

  • Name
    missing_auth
    Type
    401
    Description

    One or more of X-Api-Key, X-Timestamp, X-Signature is absent.

  • Name
    bad_timestamp
    Type
    401
    Description

    X-Timestamp is not a valid Unix-seconds integer.

  • Name
    stale_request
    Type
    401
    Description

    The timestamp is more than 5 minutes from the server clock.

  • Name
    invalid_key
    Type
    401
    Description

    The key id is unknown or has been revoked.

  • Name
    bad_signature
    Type
    401
    Description

    The signature does not match — usually a body that differs from the bytes you hashed, the wrong path, or the wrong secret.

Test mode

A pk_test_ key authenticates exactly like a live key, but the orders it creates are flagged is_test: true and never settle to your live wallet. Test orders run the complete payment flow — instructions, confirmations, webhooks — so you can build and verify your integration end to end before going live. Switch to a pk_live_ key when you are ready for real money to move.