The Aslan Partner API is a RESTful API designed for payroll providers to integrate with Aslan's earned wage access platform. This API enables seamless management of company registrations, employee onboarding, and payroll deduction reporting.
Base URL: https://partner.aslan.io/api/v1
Note: All endpoints in this documentation are relative to the base URL above.
- Getting Started
- Authentication
- What You Can Do
- Webhooks
- Request & Response Format
- Error Handling
- Rate Limiting
- Support
Before you begin, you will need:
- API Key - Provided by Aslan
- API Secret - Provided by Aslan
- Company ID(s) - The UUID(s) of the companies you manage
- Obtain your API credentials from Aslan (API Key and API Secret will be provided securely)
- Implement the authentication flow using the code examples provided in this documentation
- Test your integration by making a GET request to
/companiesto retrieve your company list - Review the API reference documentation for detailed endpoint specifications, schemas, and examples
- Integrate the endpoints relevant to your payroll workflow (company sync, employee onboarding, deduction reporting)
All API requests must be authenticated using your API Key and API Secret.
The API uses Bearer Token authentication with HMAC-SHA256 signing to ensure request authenticity and prevent replay attacks.
- Create a signature string in the format:
METHOD\nPATH\nTIMESTAMP(note the trailing newline) - Generate an HMAC-SHA256 hash of the signature string using your API Secret as the key
- Base64 encode the resulting hash
- Include the base64-encoded signature in the
Authorizationheader
Authorization: Bearer {YOUR_API_KEY}:{BASE64_ENCODED_SIGNATURE}
X-Aslan-Timestamp: {UNIX_TIMESTAMP_IN_SECONDS}
Content-Type: application/json- Timestamp Validation: Requests must be sent within 5 minutes of the timestamp to prevent replay attacks
- Signature Components: The signature string must be exactly
METHOD\nPATH\nTIMESTAMPwith literal newline characters (\n) - Path Format: Use the full request path including query parameters (e.g.,
/api/v1/companies?page=0) - Case Sensitivity: HTTP method must be uppercase (e.g.,
GET,POST,PUT)
const crypto = require('crypto');
const axios = require('axios');
const API_KEY = 'your-api-key';
const API_SECRET = 'your-api-secret';
const BASE_URL = 'https://partner.aslan.io';
/**
* Generate HMAC-SHA256 signature for authentication
* @param {string} method - HTTP method (GET, POST, PUT, etc.)
* @param {string} path - Request path including query parameters
* @param {number} timestamp - Unix timestamp in seconds
* @returns {string} Base64-encoded signature
*/
function generateSignature(method, path, timestamp) {
// Signature format: METHOD\nPATH\nTIMESTAMP
const signatureString = `${method}\n${path}\n${timestamp}`;
const hmac = crypto.createHmac('sha256', API_SECRET);
hmac.update(signatureString);
const signature = hmac.digest('base64');
return signature;
}
/**
* Make an authenticated request to the Aslan Partner API
*/
async function makeAuthenticatedRequest(method, path, data = null) {
const timestamp = Math.floor(Date.now() / 1000);
const signature = generateSignature(method, path, timestamp);
const headers = {
'Authorization': `Bearer ${API_KEY}:${signature}`,
'X-Aslan-Timestamp': timestamp.toString(),
'Content-Type': 'application/json'
};
const config = {
method,
url: `${BASE_URL}${path}`,
headers,
...(data && {data})
};
return await axios(config);
}
// Example: Get all companies
makeAuthenticatedRequest('GET', '/api/v1/companies')
.then(response => console.log(response.data))
.catch(error => console.error(error.response?.data || error.message));
// Example: Create a new user
const newUser = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
salaried: true
};
makeAuthenticatedRequest('POST', '/api/v1/companies/{companyId}/users', newUser)
.then(response => console.log('User created:', response.data))
.catch(error => console.error(error.response?.data || error.message));import hmac
import hashlib
import base64
import time
import requests
import json
# Configuration
API_KEY = 'your-api-key'
API_SECRET = 'your-api-secret'
BASE_URL = 'https://partner.aslan.io'
def generate_signature(method, path, timestamp):
"""
Generate HMAC-SHA256 signature for authentication
Args:
method: HTTP method (GET, POST, PUT, etc.)
path: Request path including query parameters
timestamp: Unix timestamp in seconds (as string)
Returns:
Base64-encoded signature
"""
# Signature format: METHOD\nPATH\nTIMESTAMP (note trailing newline)
signature_string = f"{method}\n{path}\n{timestamp}"
# Generate HMAC-SHA256 signature
hmac_signature = hmac.new(
key=API_SECRET.encode('utf-8'),
msg=signature_string.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
# Encode to base64
signature = base64.b64encode(hmac_signature).decode('utf-8')
return signature
def make_authenticated_request(method, path, data=None):
"""Make an authenticated request to the Aslan Partner API"""
timestamp = str(int(time.time()))
signature = generate_signature(method, path, timestamp)
url = f"{BASE_URL}{path}"
headers = {
'Authorization': f'Bearer {API_KEY}:{signature}',
'X-Aslan-Timestamp': timestamp,
'Content-Type': 'application/json'
}
if method == 'GET':
response = requests.get(url, headers=headers)
elif method == 'POST':
response = requests.post(url, headers=headers, json=data)
elif method == 'PUT':
response = requests.put(url, headers=headers, json=data)
else:
raise ValueError(f"Unsupported method: {method}")
return response
# Example: Get all companies
try:
response = make_authenticated_request('GET', '/api/v1/companies')
print(f"Status: {response.status_code}")
print(f"Response: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
# Example: Create a new user
new_user = {
'firstName': 'John',
'lastName': 'Doe',
'email': 'john.doe@example.com',
'salaried': True
}
try:
response = make_authenticated_request(
'POST',
'/api/v1/companies/{companyId}/users',
data=new_user
)
print(f"User created: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"Error: {e}")The Aslan Partner API enables payroll providers to seamlessly integrate with Aslan's earned wage access platform. The API provides the following capabilities:
Manage companies registered with Aslan:
- Retrieve your company portfolio with flexible pagination and filtering
- Access detailed company information and registration status
- Update company details as they change in your payroll system
Manage employees enrolled in Aslan:
- Create employee records for workers eligible for earned wage access
- Retrieve employee lists with pagination, search, and filtering capabilities
- Access detailed employee information including employment status and verification details
- Update employee information to keep records synchronized with your payroll system
- Send invitation emails to employees to join the Aslan platform
Access payroll deduction information:
- Retrieve company-level deduction reports for payroll processing
- Access individual employee deduction details for specific reporting periods
- Flexible Pagination: All list endpoints support configurable page sizes and navigation
- Powerful Filtering: Filter data by status, dates, and other relevant criteria
- Search Capabilities: Search for employees by name or email
- Sorting Options: Sort results in ascending or descending order
- Comprehensive Error Handling: Receive detailed validation errors to quickly resolve issues
Company Setup: Companies are typically pre-registered by Aslan. Use the API to retrieve company information and verify registration status.
Employee Onboarding: Employee creation is a two-step process:
- First, create the employee record with their employment details
- Then, send an invitation when you're ready for them to join Aslan
- This separation gives you control over the invitation timing
Ongoing Synchronization: Keep employee records up-to-date by updating information as it changes in your payroll system.
Deduction Processing: Retrieve deduction reports during your payroll cycles to process earned wage access repayments.
- Employee Roles: The Partner API is designed for managing standard employees. Administrative roles are managed separately by Aslan.
- Status Lifecycle: Employee status transitions (e.g., invited → active) are managed by Aslan based on employee actions. You can view status but not directly modify it.
- Data Validation: All requests are validated against strict schemas. Refer to error responses for detailed validation feedback.
For complete endpoint specifications, request/response schemas, and field-level documentation, please refer to the API Reference.
Aslan uses webhooks to notify your system about important events in real-time. When significant events occur (such as deductions reports being generated), Aslan will send an HTTP POST request to your configured webhook endpoint(s).
Webhooks enable you to:
- Receive real-time notifications when deductions reports are ready for download
- Automate your payroll processing workflows
- Reduce the need for frequent polling of the API
- Ensure timely processing of employee deductions
To receive webhooks, you need to:
- Provide Webhook URL(s): Contact your Aslan account manager to configure one or more webhook endpoints
- Implement Webhook Handler: Create an HTTPS endpoint that can receive POST requests
- Verify Signatures: Validate the HMAC signature on each webhook to ensure authenticity
- Return 200 OK: Respond with HTTP 200 within 5 seconds to acknowledge receipt
Important: Your webhook endpoint must:
- Be publicly accessible over HTTPS (not HTTP)
- Respond within 5 seconds to avoid timeout
- Return HTTP status code 200 to indicate successful receipt
- Be idempotent (handle duplicate deliveries gracefully)
All webhooks are signed using HMAC-SHA256 to ensure they originated from Aslan and haven't been tampered with.
Each webhook request includes two headers for verification:
X-Aslan-Signature: {BASE64_ENCODED_HMAC_SIGNATURE}
X-Aslan-Timestamp: {UNIX_TIMESTAMP_IN_SECONDS}Signature Calculation:
The signature is calculated using the same API Secret used for authenticating your API requests.
Signature String Format: "METHOD\nPATH\nTIMESTAMP\nJSON_BODY\n"Key differences from API authentication:
- Includes the full JSON body in the signature (not present in API request signatures)
- Uses POST as the HTTP method
- Path is the endpoint path of your webhook URL (e.g.,
/webhooks/aslan)
const crypto = require('crypto');
const express = require('express');
const API_SECRET = 'your-api-secret'; // Same secret used for API authentication
function verifyWebhookSignature(req) {
const receivedSignature = req.headers['x-aslan-signature'];
const timestamp = req.headers['x-aslan-timestamp'];
const jsonBody = JSON.stringify(req.body);
// Extract path from request URL
const path = new URL(req.url, `http://${req.headers.host}`).pathname;
// Build signature string: "POST\n/path\n1234567890\n{json}\n"
const signatureString = `POST\n${path}\n${timestamp}\n${jsonBody}\n`;
// Calculate expected signature
const hmac = crypto.createHmac('sha256', API_SECRET);
hmac.update(signatureString);
const expectedSignature = hmac.digest('base64');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
}
// Webhook endpoint handler
app.post('/webhooks/aslan', express.json(), (req, res) => {
// 1. Verify signature
if (!verifyWebhookSignature(req)) {
console.error('Invalid webhook signature');
return res.status(401).json({error: 'Invalid signature'});
}
// 2. Verify timestamp (prevent replay attacks)
const timestamp = parseInt(req.headers['x-aslan-timestamp']);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) { // 5 minutes tolerance
console.error('Webhook timestamp too old');
return res.status(401).json({error: 'Timestamp expired'});
}
// 3. Process webhook event
const event = req.body;
console.log('Received webhook:', event.eventType);
// Process event asynchronously (don't block response)
processWebhookEvent(event).catch(err => {
console.error('Error processing webhook:', err);
});
// 4. Return 200 OK immediately
res.status(200).json({received: true});
});import hmac
import hashlib
import base64
import time
import json
from flask import Flask, request, jsonify
API_SECRET = 'your-api-secret' # Same secret used for API authentication
app = Flask(__name__)
def verify_webhook_signature(request):
"""Verify HMAC signature on webhook request"""
received_signature = request.headers.get('X-Aslan-Signature')
timestamp = request.headers.get('X-Aslan-Timestamp')
if not received_signature or not timestamp:
return False
# Get JSON body as string
json_body = request.get_data(as_text=True)
# Extract path from request
path = request.path
# Build signature string: "POST\n/path\n1234567890\n{json}\n"
signature_string = f"POST\n{path}\n{timestamp}\n{json_body}\n"
# Calculate expected signature
hmac_signature = hmac.new(
key=API_SECRET.encode('utf-8'),
msg=signature_string.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
expected_signature = base64.b64encode(hmac_signature).decode('utf-8')
# Timing-safe comparison
return hmac.compare_digest(received_signature, expected_signature)
@app.route('/webhooks/aslan', methods=['POST'])
def handle_webhook():
# 1. Verify signature
if not verify_webhook_signature(request):
return jsonify({'error': 'Invalid signature'}), 401
# 2. Verify timestamp (prevent replay attacks)
timestamp = int(request.headers.get('X-Aslan-Timestamp'))
now = int(time.time())
if abs(now - timestamp) > 300: # 5 minutes tolerance
return jsonify({'error': 'Timestamp expired'}), 401
# 3. Process webhook event
event = request.get_json()
print(f"Received webhook: {event['eventType']}")
# Process event asynchronously (don't block response)
# You might want to queue this for background processing
process_webhook_event(event)
# 4. Return 200 OK immediately
return jsonify({'received': True}), 200All webhooks follow a consistent structure:
{
"eventId": "a1b2c3d4-e5f6-4a8b-9c0d-e1f2a3b4c5d6",
"eventType": "deductions_report_generated",
"timestamp": 1736611200,
"body": {
// Event-specific payload (see below)
}
}Common Fields:
| Field | Type | Description |
|---|---|---|
eventId | UUID | Unique identifier for this webhook event (use for deduplication) |
eventType | String | Type of event (see event types below) |
timestamp | Integer | Unix timestamp (seconds) when the event occurred |
body | Object | Event-specific payload containing details |
Sent when a deductions report has been generated and is ready for processing. This webhook provides a summary of all employee deductions for the pay period.
Event Body:
{
"companyId": "a3f2e1d0-5c4b-4a3e-9f8e-7d6c5b4a3e2f",
"reportId": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"employeeDeductions": [
{
"employeeId": "f7e6d5c4-b3a2-4918-8e7d-6c5b4a3f2e1d",
"reportId": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"from": "2024-01-01",
"to": "2024-01-31",
"deductionAmount": 250.00
},
{
"employeeId": "a9b8c7d6-e5f4-4a3b-2c1d-0e9f8a7b6c5d",
"reportId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"from": "2024-01-01",
"to": "2024-01-31",
"deductionAmount": 175.50
}
]
}Root Level Fields:
| Field | Type | Description |
|---|---|---|
companyId | UUID | Unique identifier for the company |
reportId | UUID | Unique identifier for this deductions report |
employeeDeductions | Array | List of individual employee deduction records |
Employee Deduction Fields:
| Field | Type | Description |
|---|---|---|
employeeId | UUID | Unique identifier for the employee |
reportId | UUID | Unique identifier for this employee's deduction report |
from | Date (YYYY-MM-DD) | Start date of the deduction period |
to | Date (YYYY-MM-DD) | End date of the deduction period |
deductionAmount | Decimal | Amount to deduct from this employee's salary (in GBP) |
Important Notes:
- Each employee in the
employeeDeductionsarray has their ownreportId(different from the top-levelreportId) - The
fromandtodates represent the pay period covered by the deductions - All amounts are in GBP (Great British Pounds)
- The
deductionAmountis the total to deduct for that employee for the entire period - Employees with zero deductions may not appear in the array
Use Case:
When you receive this webhook:
- Iterate through the
employeeDeductionsarray - For each employee, record the
deductionAmountfor the pay period (fromtoto) - Process the deductions in your payroll system during the next payroll run
- Store the top-level
eventIdfrom the webhook envelope to avoid processing duplicates - Optionally store each employee's
reportIdfor audit trail purposes
Aslan implements automatic retries for failed webhook deliveries:
- Maximum Attempts: 3
- Retry Schedule:
- Attempt 1: Immediate
- Attempt 2: 1 second after first failure
- Attempt 3: 3 seconds after second failure (2s backoff × 1.5 multiplier)
- Timeout: 5 seconds per attempt
- Success Criteria: HTTP 200 OK status code
What triggers a retry:
- Connection timeout or network error
- HTTP status codes other than 200 (including 201, 202, etc.)
- Response time exceeds 5 seconds
What does NOT trigger a retry:
- HTTP 200 OK (even with error message in body - always return 200 for success)
If all retry attempts fail, the webhook will be marked as failed and will not be retried automatically. You should:
- Monitor your webhook endpoint health
- Implement proper error logging
- Contact Aslan support if you're experiencing delivery issues
- Have a fallback mechanism to periodically check for new reports via the API
Webhooks may be delivered more than once due to:
- Network issues during response
- Retries after temporary failures
- System failovers
Best Practice: Use the eventId to deduplicate webhook events:
// Example: Track processed events in database
async function processWebhookEvent(event) {
const {eventId, eventType, body} = event;
// Check if already processed
const existing = await db.webhookEvents.findOne({eventId});
if (existing) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
// Process the event
await processDeductionsReport(body);
// Mark as processed
await db.webhookEvents.insert({
eventId,
eventType,
processedAt: new Date()
});
}During integration:
- Request Test Events: Contact Aslan support to trigger test webhook events
- Verify Signature: Ensure your signature verification works correctly
- Test Error Scenarios: Temporarily return non-200 status codes to verify retry behavior
- Check Idempotency: Process the same event twice to ensure proper deduplication
- Monitor Response Time: Ensure your endpoint responds within 5 seconds
- Respond Quickly: Return 200 OK within 5 seconds, then process asynchronously
- Verify Signatures: Always validate the HMAC signature before processing
- Check Timestamps: Reject webhooks with timestamps older than 5 minutes
- Handle Duplicates: Use
eventIdfor deduplication - Log Everything: Log all webhook receipts and processing outcomes
- Monitor Health: Set up alerts for webhook delivery failures
- Use HTTPS: Never accept webhooks over plain HTTP
- Implement Fallbacks: Have a backup mechanism to check for reports if webhooks fail
Webhooks not being received:
- Verify your webhook URL is configured correctly (contact Aslan support)
- Ensure your endpoint is publicly accessible over HTTPS
- Check firewall/security group rules aren't blocking Aslan's IP ranges
Signature verification failing:
- Ensure you're using the correct API Secret
- Verify you're including the full JSON body in the signature string
- Check the signature string format:
"POST\n{PATH}\n{TIMESTAMP}\n{JSON_BODY}\n" - Make sure you're using the exact JSON body as received (don't re-serialize)
Retries exhausted:
- Check your endpoint response time (must be < 5 seconds)
- Ensure you're returning HTTP 200 (not 201, 202, or other 2xx codes)
- Review application logs for errors during webhook processing
Need Help? Contact partner-support@aslan.io with:
- Your webhook URL
- The
eventIdof failed webhook(s) - Timestamp of the issue
- Any error logs from your application
- Content-Type:
application/json - Character Encoding: UTF-8
- Date Format: ISO 8601 (e.g.,
2024-10-10for dates,2024-10-10T15:30:00Zfor timestamps)
All successful responses return JSON with appropriate HTTP status codes:
200 OK- Request succeeded201 Created- Resource successfully created400 Bad Request- Invalid request data401 Unauthorized- Authentication failed403 Forbidden- Insufficient permissions404 Not Found- Resource not found409 Conflict- Resource already exists (e.g., duplicate email)429 Too Many Requests- Rate limit exceeded500 Internal Server Error- Server error
All error responses follow a consistent structure:
{
"timestamp": "2024-10-10T15:45:30Z",
"status": 400,
"error": "Bad Request",
"message": "Validation failed",
"path": "/api/v1/companies/{companyId}/users",
"errors": [
{
"field": "email",
"message": "Email address is already in use",
"rejectedValue": "john.doe@example.com"
}
],
"traceId": "a1b2c3d4-e5f6-4a8b-9c0d-e1f2a3b4c5d6"
}| Field | Description |
|---|---|
timestamp | When the error occurred (ISO 8601) |
status | HTTP status code |
error | HTTP status text |
message | Human-readable error description |
path | API endpoint where the error occurred |
errors | Detailed validation errors (for 400 responses) |
traceId | Unique identifier for debugging - provide this to support |
401 Unauthorized - Invalid or expired authentication credentials
404 Not Found - Resource doesn't exist
409 Conflict - Resource already exists (e.g., employee with duplicate email)
429 Too Many Requests - Rate limit exceeded, retry after specified time
To ensure fair usage and system stability, the API implements rate limiting:
- Rate Limit: 1000 requests per hour per API Key
- Burst Limit: 100 requests per minute
Response headers include rate limit information:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1697127056When you exceed the rate limit, you'll receive a 429 Too Many Requests response. Implement exponential backoff when receiving 429 responses.
For technical support, integration assistance, or to report issues:
- Email: partner-support@aslan.io
- Documentation: This comprehensive guide includes authentication, endpoint reference, and integration examples
- Response Time: We aim to respond to all inquiries within 1 business day
When reporting an issue, please include the following information to help us resolve it quickly:
- Trace ID: The
traceIdfrom the error response (this is critical for debugging) - Request Details: HTTP method, endpoint path, and timestamp
- Request Payload: Sanitized request body (remove sensitive data like actual employee names/emails)
- Response Received: Full error response including status code
- Environment: Production or testing environment
- Integration Details: Your programming language and library versions
- Error Handling: Always handle errors gracefully and implement retry logic with exponential backoff
- Logging: Log the
traceIdfrom all requests for troubleshooting purposes - Testing: Test your integration thoroughly in a non-production environment before going live
- Security: Never log or expose API secrets in your application code or logs
- Rate Limits: Implement rate limit handling to avoid service disruptions
- API Reference: Detailed endpoint documentation, schemas, and examples are available
- Change Log: API changes and version updates will be communicated via email to registered partners
- Status Updates: System status and planned maintenance notifications will be sent to partner contacts
For onboarding assistance or partnership inquiries, please contact your Aslan account manager.