Skip to content
Last updated

Aslan Partner API Documentation

Overview

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.


Table of Contents

  1. Getting Started
  2. Authentication
  3. What You Can Do
  4. Webhooks
  5. Request & Response Format
  6. Error Handling
  7. Rate Limiting
  8. Support

Getting Started

Prerequisites

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

Quick Start

  1. Obtain your API credentials from Aslan (API Key and API Secret will be provided securely)
  2. Implement the authentication flow using the code examples provided in this documentation
  3. Test your integration by making a GET request to /companies to retrieve your company list
  4. Review the API reference documentation for detailed endpoint specifications, schemas, and examples
  5. Integrate the endpoints relevant to your payroll workflow (company sync, employee onboarding, deduction reporting)

Authentication

All API requests must be authenticated using your API Key and API Secret.

Authentication Method

The API uses Bearer Token authentication with HMAC-SHA256 signing to ensure request authenticity and prevent replay attacks.

How to Authenticate

  1. Create a signature string in the format: METHOD\nPATH\nTIMESTAMP (note the trailing newline)
  2. Generate an HMAC-SHA256 hash of the signature string using your API Secret as the key
  3. Base64 encode the resulting hash
  4. Include the base64-encoded signature in the Authorization header

Required Headers

Authorization: Bearer {YOUR_API_KEY}:{BASE64_ENCODED_SIGNATURE}
X-Aslan-Timestamp: {UNIX_TIMESTAMP_IN_SECONDS}
Content-Type: application/json

Important Security Notes

  • 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\nTIMESTAMP with 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)

Example (Node.js)

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));

Example (Python)

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}")

What You Can Do

The Aslan Partner API enables payroll providers to seamlessly integrate with Aslan's earned wage access platform. The API provides the following capabilities:

Company Management

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

User Management

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

Deduction Reports

Access payroll deduction information:

  • Retrieve company-level deduction reports for payroll processing
  • Access individual employee deduction details for specific reporting periods

Key Features

  • 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

Integration Workflow

  1. Company Setup: Companies are typically pre-registered by Aslan. Use the API to retrieve company information and verify registration status.

  2. 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
  3. Ongoing Synchronization: Keep employee records up-to-date by updating information as it changes in your payroll system.

  4. Deduction Processing: Retrieve deduction reports during your payroll cycles to process earned wage access repayments.

Important Considerations

  • 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.


Webhooks

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).

Overview

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

Webhook Setup

To receive webhooks, you need to:

  1. Provide Webhook URL(s): Contact your Aslan account manager to configure one or more webhook endpoints
  2. Implement Webhook Handler: Create an HTTPS endpoint that can receive POST requests
  3. Verify Signatures: Validate the HMAC signature on each webhook to ensure authenticity
  4. 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)

Webhook Security

All webhooks are signed using HMAC-SHA256 to ensure they originated from Aslan and haven't been tampered with.

Signature Verification

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)

Verification Example (Node.js)

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});
});

Verification Example (Python)

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}), 200

Webhook Events

Event Structure

All 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:

FieldTypeDescription
eventIdUUIDUnique identifier for this webhook event (use for deduplication)
eventTypeStringType of event (see event types below)
timestampIntegerUnix timestamp (seconds) when the event occurred
bodyObjectEvent-specific payload containing details

Event Types

deductions_report_generated

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:

FieldTypeDescription
companyIdUUIDUnique identifier for the company
reportIdUUIDUnique identifier for this deductions report
employeeDeductionsArrayList of individual employee deduction records

Employee Deduction Fields:

FieldTypeDescription
employeeIdUUIDUnique identifier for the employee
reportIdUUIDUnique identifier for this employee's deduction report
fromDate (YYYY-MM-DD)Start date of the deduction period
toDate (YYYY-MM-DD)End date of the deduction period
deductionAmountDecimalAmount to deduct from this employee's salary (in GBP)

Important Notes:

  • Each employee in the employeeDeductions array has their own reportId (different from the top-level reportId)
  • The from and to dates represent the pay period covered by the deductions
  • All amounts are in GBP (Great British Pounds)
  • The deductionAmount is 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:

  1. Iterate through the employeeDeductions array
  2. For each employee, record the deductionAmount for the pay period (from to to)
  3. Process the deductions in your payroll system during the next payroll run
  4. Store the top-level eventId from the webhook envelope to avoid processing duplicates
  5. Optionally store each employee's reportId for audit trail purposes

Webhook Delivery

Retry Behavior

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)

Handling Failures

If all retry attempts fail, the webhook will be marked as failed and will not be retried automatically. You should:

  1. Monitor your webhook endpoint health
  2. Implement proper error logging
  3. Contact Aslan support if you're experiencing delivery issues
  4. Have a fallback mechanism to periodically check for new reports via the API

Idempotency

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()
    });
}

Testing Webhooks

During integration:

  1. Request Test Events: Contact Aslan support to trigger test webhook events
  2. Verify Signature: Ensure your signature verification works correctly
  3. Test Error Scenarios: Temporarily return non-200 status codes to verify retry behavior
  4. Check Idempotency: Process the same event twice to ensure proper deduplication
  5. Monitor Response Time: Ensure your endpoint responds within 5 seconds

Best Practices

  1. Respond Quickly: Return 200 OK within 5 seconds, then process asynchronously
  2. Verify Signatures: Always validate the HMAC signature before processing
  3. Check Timestamps: Reject webhooks with timestamps older than 5 minutes
  4. Handle Duplicates: Use eventId for deduplication
  5. Log Everything: Log all webhook receipts and processing outcomes
  6. Monitor Health: Set up alerts for webhook delivery failures
  7. Use HTTPS: Never accept webhooks over plain HTTP
  8. Implement Fallbacks: Have a backup mechanism to check for reports if webhooks fail

Troubleshooting

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 eventId of failed webhook(s)
  • Timestamp of the issue
  • Any error logs from your application

Request & Response Format

Request Format

  • Content-Type: application/json
  • Character Encoding: UTF-8
  • Date Format: ISO 8601 (e.g., 2024-10-10 for dates, 2024-10-10T15:30:00Z for timestamps)

Response Format

All successful responses return JSON with appropriate HTTP status codes:

  • 200 OK - Request succeeded
  • 201 Created - Resource successfully created
  • 400 Bad Request - Invalid request data
  • 401 Unauthorized - Authentication failed
  • 403 Forbidden - Insufficient permissions
  • 404 Not Found - Resource not found
  • 409 Conflict - Resource already exists (e.g., duplicate email)
  • 429 Too Many Requests - Rate limit exceeded
  • 500 Internal Server Error - Server error

Error Handling

Error Response Structure

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"
}

Error Fields

FieldDescription
timestampWhen the error occurred (ISO 8601)
statusHTTP status code
errorHTTP status text
messageHuman-readable error description
pathAPI endpoint where the error occurred
errorsDetailed validation errors (for 400 responses)
traceIdUnique identifier for debugging - provide this to support

Common Error Scenarios

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


Rate Limiting

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

Rate Limit Headers

Response headers include rate limit information:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1697127056

Handling Rate Limits

When you exceed the rate limit, you'll receive a 429 Too Many Requests response. Implement exponential backoff when receiving 429 responses.


Support

Getting Help

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

Reporting Issues

When reporting an issue, please include the following information to help us resolve it quickly:

  1. Trace ID: The traceId from the error response (this is critical for debugging)
  2. Request Details: HTTP method, endpoint path, and timestamp
  3. Request Payload: Sanitized request body (remove sensitive data like actual employee names/emails)
  4. Response Received: Full error response including status code
  5. Environment: Production or testing environment
  6. Integration Details: Your programming language and library versions

Best Practices for Integration

  • Error Handling: Always handle errors gracefully and implement retry logic with exponential backoff
  • Logging: Log the traceId from 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

Additional Resources

  • 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.