Authorisation

All Pay.io API requests must be authenticated with three headers:

Header
Description

X-API-Key

Your unique merchant API key from Merchant Console.

X-API-Nonce

Unique identifier (UUID) per request.

X-API-Signature

RSA-SHA256 signature generated using your private key.


Creating a Public and Private Key

Merchants must provide Pay.io with a public key during onboarding.

Requirements:

  • At least 2048 bits

  • PEM format

You can use the following code sample to generate the public key and private key.

Example in Python
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# Generate 2048-bit private key
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

# Serialize keys to PEM
pem_private = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncryption()
)

pem_public = private_key.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

Creating a Merchant Signature

Every API request must be signed.

Steps

  1. Generate a nonce — Secure random string, at least 16 chars

    # Example in Python def generate_nonce(): return str(uuid.uuid4())

  2. Build canonical string using the rules:

    1. The request’s signature is calculated over this exact concatenation (no delimiters):

      METHOD + PATH + NONCE + QUERY + BODY

  3. Sign with your merchant’s private key using RSA-SHA256.

  4. Base64-encode the signature.

  5. Send it in the X-API-Signature header

Example Python

def generate_auth_headers(method, path, query_string, body, api_signing_secret):
    # 1. Generate unique nonce (UUID4 string)
    nonce = str(uuid.uuid4())

    # 2. Build the canonical signing data
    signing_data = method + path + nonce + query_string + (body or "")

    # 3. Create the HMAC-SHA256 signature
    signature = hmac.new(
        key=api_signing_secret.encode("utf-8"),
        msg=signing_data.encode("utf-8"),
        digestmod=hashlib.sha256
    ).hexdigest()

    # 4. Return headers
    return {
        "X-API-Nonce": nonce,
        "X-API-Signature": signature
    }

# Example usage
headers = generate_auth_headers(
    method="POST",
    path="/v1/payments",
    query_string="order_id=123",
    body='{"amount":100,"currency":"USD"}',
    api_signing_secret="my_secret_key"
)

Example Authenticated Request

curl --location 'https://pgw.stage.pay.io/v1/user/withdraw' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'X-API-Nonce: 123e4567-e89b-12d3-a456-426614174000' \
--header 'X-API-Signature: BASE64_SIGNATURE' \
--data '{"amount":"100.50","currency_id":"c872e749-fd56-533e-b01f-de87ae38e7f1","wallet_address":"0x123...","user_reference_id":"hub_player_2"}'

Error Codes

Error Type: missing signature

Status Code: 401

Response Body:

{ "message": "missing signature" }

Error Type: missing api key

Status Code: 401

Response Body:

{ "message": "missing api key" }

Error Type: invalid api key

Status Code: 401

Response Body:

{ "message": "invalid api key" }

Error Type: nonce too short

Status Code: 400

Response Body:

{ "message": "nonce too short" }

Error Type: nonce already used

Status Code: 401

Response Body:

{ "message": "invalid request signature" }

Error Type: invalid nonce

  • Status Code: 400

  • Response Body:

{ "message": "invalid nonce" }

Error Type: missing nonce

Status Code: 401

Response Body:

{ "message": "missing nonce" }

Error Type: timestamp expired

Status Code: 401

Response Body:

{ "message": "timestamp expired" }

Error Type: multiple nonces

Status Code: 401

Response Body:

{ "message": "multiple nonces" }

Last updated