All endpoints require ECDSA signature-based authentication using required headers:
- X-API-SUBSCRIPTION-KEY: Your unique API key identifier
- X-API-SIGNATURE: ECDSA-SHA256 signature (base64-encoded)
- X-API-TIMESTAMP: Unix timestamp (in milliseconds)
- X-API-NONCE: Unique UUID to prevent replay attacks
Contact INDX support to obtain sandbox API credentials (API Subscription Key). Keep your private key secure and never commit it to version control.
Generate Your Key Pair
After making contact with INDX Support, generate an ECDSA key pair using the secp256k1 elliptic curve and send the public key to INDX. You may use OpenSSL or other libraries to generate the pair. See OpenSSL example scripts below.
openssl ecparam -name secp256k1 -genkey -noout -out test-private-key.pem
openssl ec -in test-private-key.pem -pubout > test-public-key.pem
How Signature Authentication Works
The signature is generated using ECDSA (Elliptic Curve Digital Signature Algorithm) with your private key:
- Create a message string:
TIMESTAMP + NONCE + METHOD + ENDPOINT
- Base64 encode the message
- Sign the encoded message with your private key using ECDSA-SHA256
- Base64 encode the signature
Important: URL vs Endpoint
- URL: The full address including the domain (e.g.,
https://api.indx.com/customer/accounts)
- Endpoint: Only the path portion (e.g.,
/customer/accounts)
When creating the signature, use only the endpoint (path), not the full URL. The signature is computed using the path that starts with /.
This signature ensures:
- Requests cannot be tampered with in transit
- Requests are authenticated to your account with public-key cryptography
- Replay attacks are prevented through timestamp and nonce validation
Here’s how to generate the required authentication headers:
import base64
import time
import uuid
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key
# Load your private key
with open("private-key.pem", "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
# Your API key
api_key = "your_api_key"
# Generate timestamp and nonce
timestamp = str(int(time.time() * 1000))
nonce = str(uuid.uuid4())
# Request details
method = "GET"
endpoint = "/customer/accounts"
# Create signature payload (note the order: timestamp, nonce, method, endpoint)
raw_message = f"{timestamp}{nonce}{method.upper()}{endpoint}"
encoded_message = base64.b64encode(raw_message.encode("utf-8"))
# Sign the message with ECDSA
signature = private_key.sign(encoded_message, ec.ECDSA(hashes.SHA256()))
signature_b64 = base64.b64encode(signature).decode()
Making an API Request
Use the generated signature to make your API request:
import requests
url = "https://api.indx.com/customer/accounts"
headers = {
"X-API-SUBSCRIPTION-KEY": api_key,
"X-API-SIGNATURE": signature_b64,
"X-API-TIMESTAMP": timestamp,
"X-API-NONCE": nonce,
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response.json())
Complete Working Example
import requests
import base64
import time
import uuid
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key
# Configuration
API_BASE_URL = "https://api.indx.com"
API_SUBSCRIPTION_KEY = "your_api_key"
ENDPOINT = "/customer/accounts"
METHOD = "GET"
# Load private key
with open("private-key.pem", "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
# Generate authentication headers
timestamp = str(int(time.time() * 1000))
nonce = str(uuid.uuid4())
# Create and sign message
raw_message = f"{timestamp}{nonce}{METHOD.upper()}{ENDPOINT}"
encoded_message = base64.b64encode(raw_message.encode("utf-8"))
signature = private_key.sign(encoded_message, ec.ECDSA(hashes.SHA256()))
signature_b64 = base64.b64encode(signature).decode()
# Make request
headers = {
"X-API-SUBSCRIPTION-KEY": API_KEY,
"X-API-SIGNATURE": signature_b64,
"X-API-TIMESTAMP": timestamp,
"X-API-NONCE": nonce,
"Content-Type": "application/json"
}
response = requests.get(f"{API_BASE_URL}{ENDPOINT}", headers=headers)
print(response.json())
POST Requests with Body Data
When making POST requests with a JSON body, the signature process is identical - you do NOT include the request body in the signature:
import requests
import base64
import time
import uuid
import json
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key
# Configuration
API_BASE_URL = "https://api.indx.com"
API_SUBSCRIPTION_KEY = "your_api_key"
ENDPOINT = "/customer/transactions"
METHOD = "POST"
# Load private key
with open("private-key.pem", "rb") as f:
private_key = load_pem_private_key(f.read(), password=None)
# Generate authentication headers
timestamp = str(int(time.time() * 1000))
nonce = str(uuid.uuid4())
# Create signature - NOTE: Body is NOT included in the signature
raw_message = f"{timestamp}{nonce}{METHOD.upper()}{ENDPOINT}"
encoded_message = base64.b64encode(raw_message.encode("utf-8"))
signature = private_key.sign(encoded_message, ec.ECDSA(hashes.SHA256()))
signature_b64 = base64.b64encode(signature).decode()
# Prepare request body
body = {
"account_id": "acc_123456",
"amount": 100.00,
"currency": "USD"
}
# Make POST request
headers = {
"X-API-SUBSCRIPTION-KEY": API_KEY,
"X-API-SIGNATURE": signature_b64,
"X-API-TIMESTAMP": timestamp,
"X-API-NONCE": nonce,
"Content-Type": "application/json"
}
response = requests.post(
f"{API_BASE_URL}{ENDPOINT}",
headers=headers,
json=body # Body is sent separately, not included in signature
)
print(response.json())
Critical for POST/PUT/PATCH requests:
- The request body is NOT included in the signature calculation
- The signature only uses:
TIMESTAMP + NONCE + METHOD + ENDPOINT
- The body is sent separately in the request payload
- Always use
METHOD.upper() to ensure the method is uppercase (e.g., “POST”, not “post”)
Your private key should be in PEM format (PKCS#8):
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg...
-----END PRIVATE KEY-----
Response Example
A successful response will return your customer accounts:
{
"status": "success",
"data": {
"accounts": [
{
"account_id": "acc_123456",
"account_number": "****1234",
"account_type": "checking",
"status": "active"
}
]
}
}
Required Dependencies
pip install requests cryptography python-dotenv