Integration Examples

Real-world examples of payroll reporting and employee sync with Audit1

Integration Examples

Complete, production-ready code examples for submitting payroll data, syncing employees, and handling webhook events.

🚀 Quick Start Examples

1. Submit Payroll (cURL)

Copy-paste ready example:

export AUDIT1_API_KEY="audit1_test_sk_your_key_here"

curl -X POST https://api.audit1.info/api/v1/payroll/reports \
  -H "Authorization: Bearer $AUDIT1_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "employer_id": "EMP_12345",
    "period_start": "2024-01-01",
    "period_end": "2024-03-31",
    "employees": [
      {
        "employee_id": "EE_001",
        "name": "John Smith",
        "classification_code": "8810",
        "hours_worked": 520,
        "gross_wages": 26000
      }
    ],
    "payroll_data": {
      "total_payroll": 26000,
      "total_hours": 520,
      "officer_payroll": 0,
      "subcontractor_payments": 0
    }
  }'

2. Sync Employee (cURL)

curl -X POST https://api.audit1.info/api/v1/employees/sync \
  -H "Authorization: Bearer $AUDIT1_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "employer_id": "EMP_12345",
    "employees": [
      {
        "employee_id": "EE_NEW_001",
        "action": "hire",
        "name": "Jane Doe",
        "hire_date": "2024-01-15",
        "classification_code": "8810",
        "hourly_rate": 30.00
      }
    ]
  }'

3. Check File Status (cURL)

curl -X GET https://api.audit1.info/api/v1/files/status/FILE_123 \
  -H "Authorization: Bearer $AUDIT1_API_KEY"

Complete Working Examples

Submit Quarterly Payroll Report

curl -X POST https://api.audit1.info/api/v1/payroll/reports \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "employer_id": "EMP_12345",
    "period_start": "2024-01-01",
    "period_end": "2024-03-31",
    "employees": [
      {
        "employee_id": "EE_001",
        "name": "John Smith",
        "classification_code": "8810",
        "hours_worked": 520,
        "gross_wages": 26000,
        "overtime_hours": 20,
        "overtime_pay": 1500
      },
      {
        "employee_id": "EE_002",
        "name": "Jane Doe",
        "classification_code": "5403",
        "hours_worked": 480,
        "gross_wages": 38400
      }
    ],
    "payroll_data": {
      "total_payroll": 64400,
      "total_hours": 1000,
      "officer_payroll": 0,
      "subcontractor_payments": 0
    }
  }'

Response:

{
  "message": "Payroll report submitted successfully",
  "report_id": "PR_1705932000123",
  "status": "processing",
  "employee_count": 2,
  "estimated_processing_time": "5-10 minutes"
}

Node.js Example

Production-Ready Implementation

const axios = require('axios');

// Configure client with retry logic
class Audit1Client {
  constructor(apiKey) {
    this.client = axios.create({
      baseURL: 'https://api.audit1.info/api/v1',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      timeout: 30000 // 30 second timeout
    });
  }

  async submitPayrollReport(employerId, periodStart, periodEnd, employees) {
    // Validate inputs
    if (!employerId || !periodStart || !periodEnd || !employees?.length) {
      throw new Error('Missing required parameters');
    }

    // Calculate totals
    const payrollData = {
      total_payroll: employees.reduce((sum, e) => sum + (e.gross_wages || 0), 0),
      total_hours: employees.reduce((sum, e) => sum + (e.hours_worked || 0), 0),
      officer_payroll: employees
        .filter(e => e.is_officer)
        .reduce((sum, e) => sum + (e.gross_wages || 0), 0),
      subcontractor_payments: 0
    };

    const payload = {
      employer_id: employerId,
      period_start: periodStart,
      period_end: periodEnd,
      employees: employees,
      payroll_data: payrollData
    };

    try {
      const response = await this.retryRequest(() =>
        this.client.post('/payroll/reports', payload)
      );

      console.log('Payroll report submitted:', {
        report_id: response.data.report_id,
        status: response.data.status,
        environment: response.data.environment,
        employee_count: response.data.employee_count
      });

      return response.data;
    } catch (error) {
      console.error('Failed to submit payroll:', {
        error: error.message,
        status: error.response?.status,
        details: error.response?.data
      });
      throw error;
    }
  }

  async retryRequest(fn, maxRetries = 3) {
    let lastError;

    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error;

        // Don't retry client errors (except rate limits)
        const status = error.response?.status;
        if (status >= 400 && status < 500 && status !== 429) {
          throw error;
        }

        // Calculate exponential backoff
        const backoffMs = Math.min(Math.pow(2, i) * 1000, 30000);
        console.log(`Retry ${i + 1}/${maxRetries} after ${backoffMs}ms`);
        await new Promise(resolve => setTimeout(resolve, backoffMs));
      }
    }

    throw lastError;
  }
}

// Usage Example
const client = new Audit1Client(process.env.AUDIT1_API_KEY);

const employees = [
  {
    employee_id: 'EE_001',
    name: 'John Smith',
    classification_code: '8810',
    hours_worked: 520,
    gross_wages: 26000,
    overtime_hours: 20,
    overtime_pay: 1500
  },
  {
    employee_id: 'EE_002',
    name: 'Jane Doe',
    classification_code: '5403',
    hours_worked: 480,
    gross_wages: 38400,
    is_officer: true
  }
];

// Submit payroll
client.submitPayrollReport(
  'EMP_12345',
  '2024-01-01',
  '2024-03-31',
  employees
).then(result => {
  console.log('Success:', result.report_id);
}).catch(error => {
  console.error('Failed:', error.message);
  process.exit(1);
});

Environment Configuration

// config.js
module.exports = {
  development: {
    apiKey: process.env.AUDIT1_TEST_KEY,
    environment: 'sandbox'
  },
  production: {
    apiKey: process.env.AUDIT1_LIVE_KEY,
    environment: 'production'
  }
};

// app.js
const config = require('./config');
const env = process.env.NODE_ENV || 'development';
const client = new Audit1Client(config[env].apiKey);

Python Example

Production-Ready Implementation

import os
import time
import requests
from typing import List, Dict, Optional
from datetime import datetime

class Audit1Client:
    def __init__(self, api_key: str):
        self.base_url = 'https://api.audit1.info/api/v1'
        self.headers = {
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json'
        }
        self.session = requests.Session()
        self.session.headers.update(self.headers)
    
    def submit_payroll_report(
        self,
        employer_id: str,
        period_start: str,
        period_end: str,
        employees: List[Dict]
    ) -> Dict:
        """Submit payroll report with automatic retry logic."""
        
        # Calculate totals
        total_payroll = sum(e.get('gross_wages', 0) for e in employees)
        total_hours = sum(e.get('hours_worked', 0) for e in employees)
        officer_payroll = sum(
            e.get('gross_wages', 0) 
            for e in employees 
            if e.get('is_officer', False)
        )
        
        payload = {
            'employer_id': employer_id,
            'period_start': period_start,
            'period_end': period_end,
            'employees': employees,
            'payroll_data': {
                'total_payroll': total_payroll,
                'total_hours': total_hours,
                'officer_payroll': officer_payroll,
                'subcontractor_payments': 0
            }
        }
        
        return self._retry_request(
            lambda: self.session.post(
                f'{self.base_url}/payroll/reports',
                json=payload
            )
        )
    
    def _retry_request(self, fn, max_retries: int = 3) -> Dict:
        """Execute request with exponential backoff retry."""
        last_error = None
        
        for attempt in range(max_retries):
            try:
                response = fn()
                response.raise_for_status()
                return response.json()
            except requests.exceptions.HTTPError as e:
                last_error = e
                status = e.response.status_code
                
                # Don't retry client errors (except rate limits)
                if 400 <= status < 500 and status != 429:
                    raise
                
                # Calculate backoff
                backoff = min(2 ** attempt, 30)
                print(f'Retry {attempt + 1}/{max_retries} after {backoff}s')
                time.sleep(backoff)
        
        raise last_error

# Usage
if __name__ == '__main__':
    client = Audit1Client(os.getenv('AUDIT1_API_KEY'))
    
    employees = [
        {
            'employee_id': 'EE_001',
            'name': 'John Smith',
            'classification_code': '8810',
            'hours_worked': 520,
            'gross_wages': 26000
        }
    ]
    
    try:
        result = client.submit_payroll_report(
            employer_id='EMP_12345',
            period_start='2024-01-01',
            period_end='2024-03-31',
            employees=employees
        )
        print(f"Success: {result['report_id']}")
    except Exception as e:
        print(f"Failed: {str(e)}")
        exit(1)

Common Integration Patterns

Pattern 1: Batch Processing

// Process payroll reports in batches
async function processBatchPayroll(reports, batchSize = 10) {
  const results = [];
  
  for (let i = 0; i < reports.length; i += batchSize) {
    const batch = reports.slice(i, i + batchSize);
    
    // Process batch in parallel
    const batchResults = await Promise.allSettled(
      batch.map(report => client.submitPayrollReport(
        report.employer_id,
        report.period_start,
        report.period_end,
        report.employees
      ))
    );
    
    results.push(...batchResults);
    
    // Rate limit: wait between batches
    if (i + batchSize < reports.length) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  
  return results;
}

Pattern 2: Webhook Verification

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express endpoint
app.post('/webhooks/audit1', (req, res) => {
  const signature = req.headers['x-audit1-signature'];
  const isValid = verifyWebhookSignature(
    req.body,
    signature,
    process.env.WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process webhook
  console.log('Webhook received:', req.body.event_type);
  res.status(200).json({ received: true });
});

Pattern 3: Idempotency

// Track processed reports to prevent duplicates
const processedReports = new Set();

async function submitPayrollIdempotent(data) {
  const idempotencyKey = `${data.employer_id}-${data.period_start}-${data.period_end}`;
  
  if (processedReports.has(idempotencyKey)) {
    console.log('Report already processed:', idempotencyKey);
    return { skipped: true };
  }
  
  const result = await client.submitPayrollReport(
    data.employer_id,
    data.period_start,
    data.period_end,
    data.employees
  );
  
  processedReports.add(idempotencyKey);
  return result;
}

Node.js Example

const submitPayrollReport = async (employerId, periodStart, periodEnd, employees) => {
  const apiKey = process.env.AUDIT1_API_KEY;
  
  const payload = {
    employer_id: employerId,
    period_start: periodStart,
    period_end: periodEnd,
    employees: employees,
    payroll_data: {
      total_payroll: employees.reduce((sum, e) => sum + e.gross_wages, 0),
      total_hours: employees.reduce((sum, e) => sum + e.hours_worked, 0),
      officer_payroll: 0,
      subcontractor_payments: 0
    }
  };

  const response = await fetch('https://api.audit1.info/api/v1/payroll/reports', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }

  return await response.json();
};

// Usage
const employees = [
  {
    employee_id: 'EE_001',
    name: 'John Smith',
    classification_code: '8810',
    hours_worked: 520,
    gross_wages: 26000
  }
];

const result = await submitPayrollReport('EMP_12345', '2024-01-01', '2024-03-31', employees);
console.log('Report ID:', result.report_id);

Python Example

import requests
import os

def submit_payroll_report(employer_id, period_start, period_end, employees):
    api_key = os.environ['AUDIT1_API_KEY']
    
    total_payroll = sum(e['gross_wages'] for e in employees)
    total_hours = sum(e['hours_worked'] for e in employees)
    
    payload = {
        'employer_id': employer_id,
        'period_start': period_start,
        'period_end': period_end,
        'employees': employees,
        'payroll_data': {
            'total_payroll': total_payroll,
            'total_hours': total_hours,
            'officer_payroll': 0,
            'subcontractor_payments': 0
        }
    }
    
    headers = {
        'Authorization': f'Bearer {api_key}',
        'Content-Type': 'application/json'
    }
    
    response = requests.post(
        'https://api.audit1.info/api/v1/payroll/reports',
        headers=headers,
        json=payload
    )
    
    response.raise_for_status()
    return response.json()

# Usage
employees = [
    {
        'employee_id': 'EE_001',
        'name': 'John Smith',
        'classification_code': '8810',
        'hours_worked': 520,
        'gross_wages': 26000
    }
]

result = submit_payroll_report('EMP_12345', '2024-01-01', '2024-03-31', employees)
print(f"Report ID: {result['report_id']}")

Sync Employee Changes

New Hire

curl -X POST https://api.audit1.info/api/v1/employees/sync \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "employer_id": "EMP_12345",
    "employees": [
      {
        "employee_id": "EE_NEW_003",
        "action": "hire",
        "name": "Alice Williams",
        "hire_date": "2024-02-15",
        "classification_code": "8810",
        "hourly_rate": 28.50
      }
    ]
  }'

Termination

curl -X POST https://api.audit1.info/api/v1/employees/sync \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "employer_id": "EMP_12345",
    "employees": [
      {
        "employee_id": "EE_002",
        "action": "terminate",
        "termination_date": "2024-03-31",
        "reason": "voluntary"
      }
    ]
  }'

Bulk Changes

const changes = [
  {
    employee_id: 'EE_NEW_004',
    action: 'hire',
    name: 'Charlie Brown',
    hire_date: '2024-03-01',
    classification_code: '5403',
    hourly_rate: 45.00
  },
  {
    employee_id: 'EE_001',
    action: 'update',
    hourly_rate: 32.00  // Wage increase
  },
  {
    employee_id: 'EE_002',
    action: 'terminate',
    termination_date: '2024-03-15',
    reason: 'retirement'
  }
];

const response = await fetch('https://api.audit1.info/api/v1/employees/sync', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.AUDIT1_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    employer_id: 'EMP_12345',
    employees: changes
  })
});

const result = await response.json();
console.log('Sync ID:', result.sync_id);

Check File Processing Status

curl -X GET https://api.audit1.info/api/v1/files/status/FILE_67890 \
  -H "Authorization: Bearer YOUR_API_KEY"

Response:

{
  "file_id": "FILE_67890",
  "status": "completed",
  "uploaded_at": "2024-01-15T14:30:00Z",
  "processed_at": "2024-01-15T14:35:22Z",
  "records_total": 150,
  "records_processed": 150,
  "records_failed": 0,
  "validation_errors": []
}

Polling Implementation

const pollFileStatus = async (fileId, maxAttempts = 30) => {
  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(
      `https://api.audit1.info/api/v1/files/status/${fileId}`,
      {
        headers: {
          'Authorization': `Bearer ${process.env.AUDIT1_API_KEY}`
        }
      }
    );
    
    const status = await response.json();
    
    if (status.status === 'completed') {
      return status;
    }
    
    if (status.status === 'failed') {
      throw new Error('File processing failed');
    }
    
    // Wait 10 seconds before next poll
    await new Promise(resolve => setTimeout(resolve, 10000));
  }
  
  throw new Error('Timeout waiting for file processing');
};

Handle Webhook Events

Configure webhooks in your portal dashboard (Webhooks section) to receive events from Audit1.

Node.js/Express Webhook Handler

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/audit1', (req, res) => {
  const { event, data } = req.body;
  
  switch (event) {
    case 'audit.completed':
      console.log('Audit completed:', data.audit_id);
      console.log('Premium:', data.premium_calculated);
      break;
      
    case 'payroll.processed':
      console.log('Payroll processed:', data.report_id);
      console.log('Records:', data.records_processed);
      break;
      
    case 'policy.updated':
      console.log('Policy updated:', data.policy_id);
      break;
  }
  
  // Respond quickly (< 5 seconds)
  res.status(200).send('OK');
});

app.listen(3000);

Example Webhook Payloads

audit.completed:

{
  "event": "audit.completed",
  "timestamp": "2024-01-15T16:45:30Z",
  "data": {
    "audit_id": "AUD_123456",
    "employer_id": "EMP_12345",
    "status": "completed",
    "premium_calculated": 15234.50
  }
}

payroll.processed:

{
  "event": "payroll.processed",
  "timestamp": "2024-01-15T14:35:22Z",
  "data": {
    "report_id": "PR_1705932000123",
    "employer_id": "EMP_12345",
    "status": "completed",
    "records_processed": 150,
    "records_failed": 0
  }
}

Error Handling

Common Errors

400 Bad Request:

{
  "error": "Missing required fields",
  "required": ["employer_id", "period_start", "period_end", "employees", "payroll_data"]
}

401 Unauthorized:

{
  "error": "Unauthorized",
  "message": "Invalid API key. Get yours from your portal dashboard (API Keys section)"
}

Retry Logic

async function submitWithRetry(payload, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.audit1.info/api/v1/payroll/reports', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.AUDIT1_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
      });
      
      if (response.ok) {
        return await response.json();
      }
      
      // Don't retry client errors
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Retry server errors with backoff
      console.log(`Attempt ${attempt} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
      
    } catch (error) {
      if (attempt === maxRetries) throw error;
    }
  }
}

Best Practices

  1. Secure API Keys: Use environment variables, never commit to version control
  2. Test with Sandbox Keys: Use audit1_test_sk_... keys for development (same URL, test data)
  3. Validate Data: Check classification codes, wage amounts before submitting
  4. Handle Errors: Implement retry logic with exponential backoff
  5. Use Webhooks: Don't poll - configure webhooks for real-time events

Next Steps

Need help? Email [email protected]