Authentication
EasySlip API uses HMAC-SHA256 Signature Authentication for secure API access. Every request must be signed with a Secret Key to verify authenticity and data integrity.
Required Credentials
When you create an application, you will receive 3 credentials:
| Credential | Format | Purpose | Where to get it |
|---|---|---|---|
| Partners API Key | 64-char hex string | Identifies your client, sent as X-API-Key on every request | Created in application settings |
| Secret Key | 64-char hex string | Signs every request with HMAC-SHA256 | Shown once when creating a Partners Client |
| Branch Key | UUID v4 | Identifies the Branch, sent as X-Branch-Key for endpoints that require a branch | Auto-generated when creating a new Branch |
Quick Summary
- Every request -> Send Partners API Key as
X-API-Keyand sign with Secret Key - Verify slips / manage a specific branch? -> Also send Branch Key as
X-Branch-Key
DANGER
Never expose your Secret Key in client-side code, public repositories, or logs. The Secret Key should only be used in server-side applications.
Getting Your API Key
- Log in to the EasySlip Developer Portal
- Go to application settings
- Create a Partners API Key and Secret Key pair
- Note the Branch Key (UUID) from branch settings
- Store the Secret Key securely — it will only be shown once
Diagram
┌──────────────────────────────────────────────────────┐
│ Application │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Partners Client │ │
│ │ Partners API Key ──→ X-API-Key (all requests) │ │
│ │ Secret Key ──→ Signs all requests │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Branch A │ │ Branch B │ ... │
│ │ Branch Key │ │ Branch Key │ │
│ │ ──→ X-Branch- │ │ ──→ X-Branch- │ │
│ │ Key │ │ Key │ │
│ └───────────────┘ └───────────────┘ │
└──────────────────────────────────────────────────────┘Choosing Which Headers to Send
X-API-Key with the Partners API Key is always required. X-Branch-Key is only required for endpoints that need a specific branch:
| Use Case | X-API-Key | X-Branch-Key | Endpoint |
|---|---|---|---|
| Verify slips / view data | Partners API Key (hex 64) | Branch Key (UUID) | /verify/bank, /verify/truewallet, /info |
| Manage specific branch / quota / bank accounts | Partners API Key (hex 64) | Branch Key (UUID) | /b2b/branch, /b2b/branch/* |
| List branches / create branch / create bank account | Partners API Key (hex 64) | Not required | /b2b/branches, /b2b/bank-accounts |
Required Headers
| Header | Format | Description |
|---|---|---|
X-API-Key | string | Partners API Key (hex 64) — sent on every request |
X-Branch-Key | string | Branch Key (UUID) — sent when endpoint requires a branch |
X-Timestamp | string | Unix timestamp in seconds |
X-Nonce | string | UUID v4, unique per request |
X-Signature | string | HMAC-SHA256 hex signature |
Computing the Signature
Step 1: Build the String-to-Sign
{METHOD}\n{PATH}\n{TIMESTAMP}\n{NONCE}\n{BODY_SHA256}| Component | Description |
|---|---|
METHOD | HTTP method in uppercase (GET, POST, PATCH, PUT, DELETE) |
PATH | Request path including the leading / (e.g., /b2b/branches) |
TIMESTAMP | Same value as the X-Timestamp header |
NONCE | Same value as the X-Nonce header |
BODY_SHA256 | SHA-256 hex digest of the request body |
Step 2: Compute the Body Hash
- For requests with a body (POST, PUT, PATCH): compute the SHA-256 of the raw JSON body string
- For requests without a body (GET, DELETE): compute the SHA-256 of an empty string (
"")
TIP
The SHA-256 of an empty string is always: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Step 3: Sign with HMAC-SHA256
HMAC-SHA256(secretKey, stringToSign)The result is a lowercase hex string.
Timestamp Tolerance
The server accepts timestamps within ±5 minutes of the current server time. Requests outside this window will be rejected with INVALID_TIMESTAMP.
Nonce Uniqueness
Each nonce must be a UUID v4 and must be unique. The server tracks recent nonces and will reject duplicates with DUPLICATE_NONCE. You must generate a new UUID for every request.
Code Examples
signRequest Function (works with any endpoint)
import crypto from 'crypto'
function signRequest({ method, path, body, apiKey, secretKey }) {
const timestamp = Math.floor(Date.now() / 1000).toString()
const nonce = crypto.randomUUID()
const bodyString = body ? JSON.stringify(body) : ''
const bodyHash = crypto.createHash('sha256').update(bodyString).digest('hex')
const stringToSign = `${method}\n${path}\n${timestamp}\n${nonce}\n${bodyHash}`
const signature = crypto.createHmac('sha256', secretKey).update(stringToSign).digest('hex')
return {
'X-API-Key': apiKey,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
}
}import hashlib
import hmac
import json
import time
import uuid
def sign_request(method, path, body, api_key, secret_key):
timestamp = str(int(time.time()))
nonce = str(uuid.uuid4())
body_string = json.dumps(body, separators=(',', ':')) if body else ''
body_hash = hashlib.sha256(body_string.encode()).hexdigest()
string_to_sign = f'{method}\n{path}\n{timestamp}\n{nonce}\n{body_hash}'
signature = hmac.new(
secret_key.encode(),
string_to_sign.encode(),
hashlib.sha256
).hexdigest()
return {
'X-API-Key': api_key,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature,
}<?php
function signRequest(string $method, string $path, ?array $body, string $apiKey, string $secretKey): array
{
$timestamp = (string) time();
$nonce = sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
$bodyString = $body ? json_encode($body, JSON_UNESCAPED_UNICODE) : '';
$bodyHash = hash('sha256', $bodyString);
$stringToSign = implode("\n", [$method, $path, $timestamp, $nonce, $bodyHash]);
$signature = hash_hmac('sha256', $stringToSign, $secretKey);
return [
'X-API-Key' => $apiKey,
'X-Timestamp' => $timestamp,
'X-Nonce' => $nonce,
'X-Signature' => $signature,
];
}#!/bin/bash
API_KEY="your_partners_api_key"
SECRET_KEY="your_secret_key"
METHOD="GET"
PATH_URL="/info"
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
# For GET/DELETE, hash an empty string
BODY_HASH=$(printf '' | shasum -a 256 | cut -d' ' -f1)
STRING_TO_SIGN="${METHOD}\n${PATH_URL}\n${TIMESTAMP}\n${NONCE}\n${BODY_HASH}"
SIGNATURE=$(printf "${STRING_TO_SIGN}" | openssl dgst -sha256 -hmac "${SECRET_KEY}" | cut -d' ' -f2)Example: Verify a Slip (requires X-Branch-Key)
const PARTNERS_API_KEY = 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789'
const SECRET_KEY = 'your_secret_key'
const BRANCH_KEY = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
const body = { payload: '00020101021230...' }
const headers = signRequest({
method: 'POST',
path: '/verify/bank',
body,
apiKey: PARTNERS_API_KEY,
secretKey: SECRET_KEY,
})
const response = await fetch('https://api.easyslip.com/v2/verify/bank', {
method: 'POST',
headers: { ...headers, 'X-Branch-Key': BRANCH_KEY },
body: JSON.stringify(body),
})
const result = await response.json()
console.log(result.data)import requests
PARTNERS_API_KEY = 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789'
SECRET_KEY = 'your_secret_key'
BRANCH_KEY = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
body = {'payload': '00020101021230...'}
headers = sign_request('POST', '/verify/bank', body, PARTNERS_API_KEY, SECRET_KEY)
headers['X-Branch-Key'] = BRANCH_KEY
headers['Content-Type'] = 'application/json'
response = requests.post(
'https://api.easyslip.com/v2/verify/bank',
headers=headers,
json=body,
)
result = response.json()
print(result['data'])$partnersApiKey = 'abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789';
$secretKey = 'your_secret_key';
$branchKey = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
$body = ['payload' => '00020101021230...'];
$headers = signRequest('POST', '/verify/bank', $body, $partnersApiKey, $secretKey);
$headers['X-Branch-Key'] = $branchKey;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.easyslip.com/v2/verify/bank',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => array_merge(
array_map(fn($k, $v) => "$k: $v", array_keys($headers), array_values($headers)),
['Content-Type: application/json']
),
CURLOPT_POSTFIELDS => json_encode($body),
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result['data']);curl -X POST "https://api.easyslip.com/v2/verify/bank" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Branch-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}" \
-H "Content-Type: application/json" \
-d '{"payload": "00020101021230..."}'Example: List Branches (no X-Branch-Key required)
const headers = signRequest({
method: 'GET',
path: '/b2b/branches',
body: null,
apiKey: PARTNERS_API_KEY,
secretKey: SECRET_KEY,
})
const response = await fetch('https://api.easyslip.com/b2b/branches', { headers })
const result = await response.json()
console.log(result.data)headers = sign_request('GET', '/b2b/branches', None, PARTNERS_API_KEY, SECRET_KEY)
response = requests.get('https://api.easyslip.com/b2b/branches', headers=headers)
result = response.json()
print(result['data'])$headers = signRequest('GET', '/b2b/branches', null, $partnersApiKey, $secretKey);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.easyslip.com/b2b/branches',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array_map(fn($k, $v) => "$k: $v", array_keys($headers), array_values($headers)),
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result['data']);curl -X GET "https://api.easyslip.com/b2b/branches" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Example: View Branch Quota (requires X-Branch-Key)
const headers = signRequest({
method: 'GET',
path: '/b2b/branch/quota',
body: null,
apiKey: PARTNERS_API_KEY,
secretKey: SECRET_KEY,
})
const response = await fetch('https://api.easyslip.com/b2b/branch/quota', {
headers: { ...headers, 'X-Branch-Key': BRANCH_KEY },
})
const result = await response.json()
console.log(result.data)headers = sign_request('GET', '/b2b/branch/quota', None, PARTNERS_API_KEY, SECRET_KEY)
headers['X-Branch-Key'] = BRANCH_KEY
response = requests.get('https://api.easyslip.com/b2b/branch/quota', headers=headers)
result = response.json()
print(result['data'])$headers = signRequest('GET', '/b2b/branch/quota', null, $partnersApiKey, $secretKey);
$headers['X-Branch-Key'] = $branchKey;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.easyslip.com/b2b/branch/quota',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array_map(fn($k, $v) => "$k: $v", array_keys($headers), array_values($headers)),
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result['data']);curl -X GET "https://api.easyslip.com/b2b/branch/quota" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Branch-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Permissions
Each Partners API key has a set of permissions that determine what actions it can perform:
| Permission | Description |
|---|---|
branch:read | View branch list and details |
branch:write | Create, edit, and delete branches |
quota:read | View quota information |
quota:write | Set, adjust, and reset quotas |
bank-account:read | View bank accounts linked to branches |
bank-account:write | Create, link, and unlink bank accounts |
WARNING
If your API key does not have the required permission for an endpoint, the request will be rejected with 403 Forbidden.
Security Best Practices
1. Never Expose Keys in Client-Side Code
// ❌ BAD - Don't do this
const API_KEY = 'your-api-key-here';
// ✅ GOOD - Use environment variables
const API_KEY = process.env.EASYSLIP_API_KEY;2. Use Environment Variables
EASYSLIP_PARTNERS_API_KEY=your-partners-api-key
EASYSLIP_SECRET_KEY=your-secret-key
EASYSLIP_BRANCH_KEY=your-branch-uuidimport 'dotenv/config';
const partnersApiKey = process.env.EASYSLIP_PARTNERS_API_KEY;
const secretKey = process.env.EASYSLIP_SECRET_KEY;
const branchKey = process.env.EASYSLIP_BRANCH_KEY;$partnersApiKey = getenv('EASYSLIP_PARTNERS_API_KEY');
$secretKey = getenv('EASYSLIP_SECRET_KEY');
$branchKey = getenv('EASYSLIP_BRANCH_KEY');import os
partners_api_key = os.environ.get('EASYSLIP_PARTNERS_API_KEY')
secret_key = os.environ.get('EASYSLIP_SECRET_KEY')
branch_key = os.environ.get('EASYSLIP_BRANCH_KEY')3. Make Requests from Server Only
// ❌ BAD - Client-side request exposes API key and secret
fetch('https://api.easyslip.com/v2/verify/bank', {
headers: { 'X-API-Key': 'your-key', 'X-Signature': '...' }
});
// ✅ GOOD - Make request through your own server
fetch('/api/verify-slip', {
method: 'POST',
body: JSON.stringify({ image: slipImage })
});4. IP Whitelisting
Configure IP whitelisting to restrict API access to specific IP addresses:
- Go to Application Settings
- Add your server's IP addresses to the whitelist
- Use
*to allow all IPs (not recommended for production)
Authentication Errors
| Error Code | HTTP Status | Description | Solution |
|---|---|---|---|
INVALID_AUTH_HEADERS | 401 | Headers are invalid or incomplete | Ensure X-API-Key, X-Timestamp, X-Nonce, X-Signature are all present |
INVALID_API_KEY | 401 | Invalid API Key | Verify your Partners API Key |
MISSING_BRANCH_KEY | 401 | X-Branch-Key not provided | Add X-Branch-Key header for endpoints that require a branch |
INVALID_BRANCH_KEY | 401 | Invalid Branch Key | Verify your Branch Key (UUID) |
INVALID_TIMESTAMP | 401 | Timestamp outside the ±5 minute window | Check your system clock |
DUPLICATE_NONCE | 401 | Duplicate nonce | Generate a new UUID for every request |
INVALID_SIGNATURE | 401 | Signature mismatch | Verify your Secret Key and signature computation |
SERVICE_SUSPENDED | 403 | Application has been suspended | Contact support |
BRANCH_INACTIVE | 403 | Branch has been deactivated | Reactivate the branch |
IP_NOT_ALLOWED | 403 | IP not in whitelist | Add the IP to the whitelist |
BRANCH_QUOTA_EXCEEDED | 403 | Branch quota exceeded | Upgrade your plan or wait for quota reset |
PERMISSION_DENIED | 403 | Access denied | Check the Partners Client permissions |
Multi-Branch Support
EasySlip supports multiple branches per application. Each branch has its own Branch Key:
- Main Branch: Primary Branch Key with full quota
- Sub-Branches: Additional Branch Keys with separate quota tracking
This allows you to:
- Track usage per integration point
- Apply different IP restrictions per branch
- Manage quota allocation across teams
Next Steps
- Partners API Overview - View all endpoints
- API v2 Reference - Explore v2 endpoints
- Error Codes - Full error code reference