Authorisation
Authorisation
All Pay.io API requests must be authenticated with three headers:
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.
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 with your merchant's RSA private key. Pay.io verifies the signature using the public key you provided during onboarding. This is an asymmetric scheme — there is no shared secret.
Steps:
Generate a nonce - a secure random string of at least 16 characters. A UUID4 works well.
Normalize the body - strip all whitespace from the request body before signing. The server silently normalizes the body the same way, so signing pretty-printed JSON without stripping whitespace will fail signature verification.
Build the canonical string using the rule below (no delimiters): METHOD + PATH + NONCE + QUERY + BODY
Sign the canonical string with your merchant's private key using RSA-SHA256 (PKCS#1 v1.5).
Base64-encode the signature (standard Base64, not URL-safe).
Send the request with all three headers: X-API-Key, X-API-Nonce, and X-API-Signature.
Example in Python:
Example Authenticated Request
Error Codes
missing signature — Status 401
missing api key — Status 401
invalid api key — Status 401
nonce too short — Status 400
nonce already used — Status 401
invalid nonce — Status 400
missing nonce — Status 401
timestamp expired — Status 401
multiple nonces — Status 401
Last updated