REST API Authentication
All REST API requests must include authentication headers. ZenPays uses API Key + HMAC Signature authentication for secure request verification.
Required Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer {api_key} - Your API key |
X-Signature | Yes | HMAC-SHA256 signature of the request |
X-Timestamp | Yes | ISO 8601 timestamp (UTC) |
X-Secret-Salt | Yes | Your secret salt for HMAC signature validation |
Content-Type | Yes* | application/json for POST/PUT/PATCH requests |
X-Merchant-ID | No | Optional merchant ID for multi-merchant setups |
API Keys
ZenPays uses prefixed API keys to identify the environment:
| Prefix | Environment | Usage |
|---|---|---|
zp_live_ | Production | Real payments |
zp_test_ | Sandbox | Testing and development |
Getting Your API Key
- Log in to your ZenPays Dashboard
- Navigate to Settings → API Keys
- Copy your API key and Secret Salt
Keep your API keys and Secret Salt secure. Never expose them in client-side code or commit them to version control.
Signature Generation
Every request must include an HMAC-SHA256 signature to verify its integrity.
Signature Formula
signature = HMAC-SHA256(timestamp + body, secret_salt)
Where:
timestamp- The value of theX-Timestampheaderbody- JSON stringified request body (empty string for GET requests)secret_salt- Your secret salt from the dashboard
Implementation Examples
- JavaScript
- Python
- Go
- PHP
const crypto = require('crypto');
function generateSignature(timestamp, body, secretSalt) {
const data = timestamp + JSON.stringify(body);
return crypto
.createHmac('sha256', secretSalt)
.update(data)
.digest('hex');
}
// Example usage
const timestamp = new Date().toISOString();
const body = { amount: 1000, currency: 'USD' };
const signature = generateSignature(
timestamp,
body,
process.env.ZENPAYS_SECRET_SALT
);
console.log('Signature:', signature);
// Output: a1b2c3d4e5f6...
import hmac
import hashlib
import json
import os
from datetime import datetime
def generate_signature(timestamp: str, body: dict, secret_salt: str) -> str:
data = timestamp + json.dumps(body, separators=(",", ":"))
return hmac.new(
secret_salt.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
# Example usage
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
body = {"amount": 1000, "currency": "USD"}
signature = generate_signature(
timestamp,
body,
os.environ["ZENPAYS_SECRET_SALT"]
)
print(f"Signature: {signature}")
# Output: a1b2c3d4e5f6...
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"time"
)
func generateSignature(timestamp string, body interface{}, secretSalt string) string {
bodyBytes, _ := json.Marshal(body)
data := timestamp + string(bodyBytes)
h := hmac.New(sha256.New, []byte(secretSalt))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// Example usage
func main() {
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
body := map[string]interface{}{
"amount": 1000,
"currency": "USD",
}
signature := generateSignature(timestamp, body, "your_secret_salt")
// Use signature in X-Signature header
}
<?php
function generateSignature(string $timestamp, array $body, string $secretSalt): string {
$data = $timestamp . json_encode($body, JSON_UNESCAPED_SLASHES);
return hash_hmac('sha256', $data, $secretSalt);
}
// Example usage
$timestamp = gmdate('Y-m-d\TH:i:s.000\Z');
$body = ['amount' => 1000, 'currency' => 'USD'];
$signature = generateSignature(
$timestamp,
$body,
getenv('ZENPAYS_SECRET_SALT')
);
echo "Signature: $signature\n";
Complete Request Example
- cURL
- JavaScript
- Python
#!/bin/bash
API_KEY="zp_test_xxxxx"
SECRET_SALT="your_secret_salt"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
BODY='{"amount":1000,"currency":"USD","description":"Order #123"}'
# Generate signature (requires openssl)
SIGNATURE=$(echo -n "${TIMESTAMP}${BODY}" | openssl dgst -sha256 -hmac "$SECRET_SALT" | cut -d' ' -f2)
curl -X POST https://api.zenpayz.com/api/v1/payment-intents \
-H "Authorization: Bearer $API_KEY" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-H "X-Secret-Salt: $SECRET_SALT" \
-H "Content-Type: application/json" \
-d "$BODY"
const crypto = require('crypto');
async function createPaymentIntent() {
const apiKey = process.env.ZENPAYS_API_KEY;
const secretSalt = process.env.ZENPAYS_SECRET_SALT;
const timestamp = new Date().toISOString();
const body = {
amount: 1000,
currency: 'USD',
description: 'Order #123',
};
// Generate signature
const data = timestamp + JSON.stringify(body);
const signature = crypto
.createHmac('sha256', secretSalt)
.update(data)
.digest('hex');
const response = await fetch('https://api.zenpayz.com/api/v1/payment-intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'X-Timestamp': timestamp,
'X-Signature': signature,
'X-Secret-Salt': secretSalt,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
return response.json();
}
import hmac
import hashlib
import json
import os
from datetime import datetime
import requests
def create_payment_intent():
api_key = os.environ["ZENPAYS_API_KEY"]
secret_salt = os.environ["ZENPAYS_SECRET_SALT"]
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
body = {
"amount": 1000,
"currency": "USD",
"description": "Order #123",
}
# Generate signature
data = timestamp + json.dumps(body, separators=(",", ":"))
signature = hmac.new(
secret_salt.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
response = requests.post(
"https://api.zenpayz.com/api/v1/payment-intents",
headers={
"Authorization": f"Bearer {api_key}",
"X-Timestamp": timestamp,
"X-Signature": signature,
"X-Secret-Salt": secret_salt,
"Content-Type": "application/json",
},
json=body,
)
return response.json()
Timestamp Validation
The X-Timestamp header must be within ±5 minutes of the server time. Requests with timestamps outside this window are rejected to prevent replay attacks.
{
"success": false,
"error": {
"code": "TIMESTAMP_EXPIRED",
"message": "Request timestamp is outside the allowed window"
}
}
Always use UTC timestamps and ensure your server clock is synchronized with NTP.
GET Request Authentication
For GET requests without a body, use an empty object {} when calculating the signature:
- JavaScript
- Python
// For GET requests
const timestamp = new Date().toISOString();
const data = timestamp + '{}';
const signature = crypto
.createHmac('sha256', secretSalt)
.update(data)
.digest('hex');
const response = await fetch('https://api.zenpayz.com/api/v1/payment-intents/pi_xxx', {
method: 'GET',
headers: {
'Authorization': `Bearer ${apiKey}`,
'X-Timestamp': timestamp,
'X-Signature': signature,
'X-Secret-Salt': secretSalt,
},
});
# For GET requests
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
data = timestamp + "{}"
signature = hmac.new(
secret_salt.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
response = requests.get(
"https://api.zenpayz.com/api/v1/payment-intents/pi_xxx",
headers={
"Authorization": f"Bearer {api_key}",
"X-Timestamp": timestamp,
"X-Signature": signature,
"X-Secret-Salt": secret_salt,
},
)
IP Whitelisting
For enhanced security, you can restrict API access to specific IP addresses:
- Go to Dashboard → Settings → API Security
- Add your server IP addresses to the whitelist
- Enable IP restriction for your API keys
Requests from non-whitelisted IPs will receive:
{
"success": false,
"error": {
"code": "IP_NOT_WHITELISTED",
"message": "Request from unauthorized IP address"
}
}
Authentication Errors
| Error Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key |
INVALID_SIGNATURE | 401 | Signature verification failed |
TIMESTAMP_EXPIRED | 401 | Timestamp outside allowed window |
API_KEY_REVOKED | 401 | API key has been revoked |
IP_NOT_WHITELISTED | 403 | Request from unauthorized IP |
INSUFFICIENT_PERMISSIONS | 403 | API key lacks required scope |
Best Practices
- Use environment variables - Never hardcode API keys or secrets
- Rotate keys regularly - Create new keys and revoke old ones periodically
- Use minimal scopes - Only grant necessary permissions to each key
- Enable IP whitelisting - Restrict access to known server IPs
- Monitor API usage - Check the dashboard for unusual activity
- Use HTTPS only - Never send credentials over unencrypted connections
Next Steps
- Error Handling - Learn about error responses
- SDK Authentication - Simpler auth with the SDK
- Webhooks - Verify webhook signatures