Webhooks

Receive real-time notifications about events in your account

Webhook Integration Tutorial

Learn how to receive real-time notifications from Audit1 when important events occur in your account.

What are Webhooks?

Webhooks are automated messages sent from Audit1 to your application when specific events happen. Instead of constantly checking for updates (polling), webhooks push notifications to you instantly.

Think of webhooks like text message notifications from your bank - you get notified immediately when something important happens, rather than having to log in and check repeatedly.

Why Use Webhooks?

  • ⚡ Real-Time: Get instant notifications when events occur
  • 🔄 Efficient: No need to constantly poll our API for updates
  • 💰 Cost-Effective: Reduces API calls and bandwidth usage
  • 🎯 Targeted: Only receive notifications for events you care about
  • 🔒 Secure: Cryptographically signed to verify authenticity

Who Should Use Webhooks?

Webhooks are perfect for:

  • Software Platforms integrating Audit1 into their applications
  • Payroll Companies wanting real-time processing status updates
  • Insurance Carriers tracking policy events and changes
  • Employers automating workflows based on payroll processing
  • Developers building custom integrations and automations

Common Use Cases

For Payroll Companies

  • Get notified when payroll files finish processing
  • Receive alerts for validation errors requiring attention
  • Track when audits are completed

For Insurance Carriers

  • Monitor policy status changes (renewals, cancellations)
  • Get notified of premium adjustments
  • Track audit completion events

For Employers

  • Receive confirmation when payroll is submitted
  • Get alerts for missing employee data
  • Track payment processing status

For Software Platforms

  • Sync data when employees are added or updated
  • Update UI when background processes complete
  • Trigger workflows based on Audit1 events

How Webhooks Work

┌─────────────────┐                          ┌─────────────────┐
│                 │  1. Event Occurs         │                 │
│    Audit1       │  (e.g., File Processed)  │  Your Server    │
│                 │                          │                 │
└─────────────────┘                          └─────────────────┘
         │                                            ▲
         │                                            │
         │  2. Webhook Notification Sent              │
         └────────────────────────────────────────────┘
                    (HTTPS POST Request)

The Process:

  1. Event Occurs - Something happens in Audit1 (file uploaded, audit completed, etc.)
  2. Webhook Triggered - Audit1 prepares a notification about the event
  3. HTTP Request Sent - A secure POST request is sent to your endpoint
  4. Your Code Responds - Your server receives and processes the notification
  5. Acknowledgment - Your server responds with 200 OK

Getting Started

Step 1: Create a Webhook Endpoint

First, you need to set up a URL endpoint on your server that can receive webhook notifications.

Simple Node.js Example

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

// Use raw body parser for signature verification
app.use(express.json());

// Webhook endpoint
app.post('/webhooks/audit1', (req, res) => {
  const event = req.body;
  
  console.log('Received webhook:', event.type);
  
  // Process the event
  switch (event.type) {
    case 'file.processed':
      console.log(`File ${event.data.fileId} processed successfully`);
      // Your business logic here
      break;
      
    case 'file.failed':
      console.log(`File ${event.data.fileId} failed processing`);
      // Handle error
      break;
      
    case 'audit.completed':
      console.log(`Audit ${event.data.auditId} completed`);
      // Update your system
      break;
  }
  
  // Always respond with 200 OK
  res.status(200).send('OK');
});

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Simple Python Example

from flask import Flask, request
import json

app = Flask(__name__)

@app.route('/webhooks/audit1', methods=['POST'])
def webhook_handler():
    event = request.get_json()
    
    print(f"Received webhook: {event['type']}")
    
    # Process the event
    if event['type'] == 'file.processed':
        print(f"File {event['data']['fileId']} processed successfully")
        # Your business logic here
        
    elif event['type'] == 'file.failed':
        print(f"File {event['data']['fileId']} failed processing")
        # Handle error
        
    elif event['type'] == 'audit.completed':
        print(f"Audit {event['data']['auditId']} completed")
        # Update your system
    
    # Always respond with 200 OK
    return 'OK', 200

if __name__ == '__main__':
    app.run(port=3000)

Important Requirements:

  • Your endpoint must be publicly accessible via HTTPS
  • Must respond with HTTP 200 OK status
  • Should respond within 10 seconds
  • Must use HTTPS (not HTTP) in production

Step 2: Register Your Webhook

Use the Audit1 API to register your webhook endpoint.

Via API Request

curl -X POST https://developer-api.audit1.info/api/v1/webhooks \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/webhooks/audit1",
    "events": [
      "file.uploaded",
      "file.processed",
      "file.failed",
      "audit.completed",
      "employee.created"
    ],
    "description": "Production webhook for payroll notifications"
  }'

Response

{
  "success": true,
  "webhook": {
    "id": "wh_1234567890",
    "url": "https://your-domain.com/webhooks/audit1",
    "events": [
      "file.uploaded",
      "file.processed",
      "file.failed",
      "audit.completed",
      "employee.created"
    ],
    "status": "active",
    "createdAt": "2024-01-15T10:30:00.000Z"
  },
  "secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}
🔒

Important: Save the secret value securely. You'll use this to verify webhook signatures.

Step 3: Test Your Webhook

Send a test event to verify your endpoint is working:

curl -X POST https://developer-api.audit1.info/api/v1/webhooks/wh_1234567890/test \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret"

You should receive a test event at your endpoint:

{
  "id": "evt_test_1234567890",
  "type": "webhook.test",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "message": "This is a test webhook event"
  }
}

Available Webhook Events

File Processing Events

Event TypeDescriptionWhen It Fires
file.uploadedFile uploaded to systemFile received via any method
file.validatedFile passed validationStructure and format verified
file.processedFile processing completedData extracted and ready
file.failedFile processing failedErrors prevented processing

Audit Events

Event TypeDescriptionWhen It Fires
audit.startedAudit calculation beginsAudit period opens
audit.completedAudit calculation finishedFinal audit results ready
audit.adjustedAudit manually adjustedAdmin makes corrections

Employee Events

Event TypeDescriptionWhen It Fires
employee.createdNew employee addedFirst payroll record received
employee.updatedEmployee data changedName, class code, etc. updated
employee.terminatedEmployee marked inactiveFinal payroll processed

Policy Events

Event TypeDescriptionWhen It Fires
policy.createdNew policy issuedPolicy setup completed
policy.renewedPolicy renewal processedNew term begins
policy.cancelledPolicy cancelledCancellation effective
policy.premium_adjustedPremium amount changedAudit adjustments applied

Payment Events

Event TypeDescriptionWhen It Fires
payment.processedPayment completedPremium payment cleared
payment.failedPayment failedPayment declined or rejected
payment.refundedPayment refundedRefund issued to customer

Webhook Payload Structure

Every webhook follows this structure:

{
  "id": "evt_1234567890",
  "type": "file.processed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    // Event-specific data here
  }
}

Example: File Processed Event

{
  "id": "evt_abc123xyz",
  "type": "file.processed",
  "timestamp": "2024-01-15T14:30:00.000Z",
  "data": {
    "fileId": "file_789xyz",
    "fileName": "AcmeCorp_Payroll_2024-01-15.csv",
    "employerId": "emp_acme123",
    "recordsProcessed": 150,
    "employeeCount": 150,
    "totalGrossPay": 375000.00,
    "payPeriodStart": "2024-01-01",
    "payPeriodEnd": "2024-01-15",
    "status": "completed"
  }
}

Example: File Failed Event

{
  "id": "evt_def456uvw",
  "type": "file.failed",
  "timestamp": "2024-01-15T14:35:00.000Z",
  "data": {
    "fileId": "file_456abc",
    "fileName": "ClientB_Payroll.csv",
    "employerId": "emp_clientb456",
    "status": "failed",
    "error": {
      "code": "MISSING_REQUIRED_COLUMNS",
      "message": "File is missing required columns: EmployeeID, GrossPay",
      "details": [
        "Column 'EmployeeID' not found",
        "Column 'GrossPay' not found"
      ]
    }
  }
}

Example: Audit Completed Event

{
  "id": "evt_ghi789rst",
  "type": "audit.completed",
  "timestamp": "2024-12-31T23:59:00.000Z",
  "data": {
    "auditId": "audit_2024_123",
    "policyId": "pol_xyz789",
    "employerId": "emp_acme123",
    "policyPeriod": {
      "start": "2024-01-01",
      "end": "2024-12-31"
    },
    "results": {
      "estimatedPremium": 50000.00,
      "actualPremium": 54500.00,
      "adjustment": 4500.00,
      "adjustmentType": "additional_premium"
    }
  }
}

Securing Webhooks

Why Verify Signatures?

Anyone can send HTTP requests to your webhook endpoint. Signature verification ensures the webhook actually came from Audit1 and wasn't tampered with.

How Signature Verification Works

Every webhook includes an Audit1-Signature header:

Audit1-Signature: sha256=a1b2c3d4e5f6...

This is an HMAC-SHA256 hash of the webhook payload, computed using your webhook secret.

Implementing Verification (Node.js)

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

const WEBHOOK_SECRET = 'whsec_your_secret_here';

app.post('/webhooks/audit1', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['audit1-signature'];
  const body = req.body;
  
  // Verify signature
  if (!verifySignature(body, signature, WEBHOOK_SECRET)) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Invalid signature');
  }
  
  // Signature is valid, process the webhook
  const event = JSON.parse(body);
  
  // Your processing logic
  handleWebhookEvent(event);
  
  res.status(200).send('OK');
});

function verifySignature(body, signature, secret) {
  const expectedSignature = 'sha256=' + 
    crypto.createHmac('sha256', secret)
          .update(body)
          .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Implementing Verification (Python)

import hmac
import hashlib
from flask import Flask, request, abort

WEBHOOK_SECRET = 'whsec_your_secret_here'

@app.route('/webhooks/audit1', methods=['POST'])
def webhook_handler():
    signature = request.headers.get('Audit1-Signature')
    body = request.get_data()
    
    # Verify signature
    if not verify_signature(body, signature, WEBHOOK_SECRET):
        abort(401, 'Invalid signature')
    
    # Signature is valid, process the webhook
    event = request.get_json()
    
    # Your processing logic
    handle_webhook_event(event)
    
    return 'OK', 200

def verify_signature(body, signature, secret):
    expected_signature = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        body,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected_signature)

Best Practices

✅ Do's

Design & Implementation:

  • Verify signatures on every webhook request
  • Respond quickly (within 10 seconds) with 200 OK
  • Process asynchronously - acknowledge receipt, then process in background
  • Handle duplicates - same event might be sent multiple times
  • Log all webhooks for debugging and audit trails

Reliability:

  • Implement retry logic in your handler (in case of failures)
  • Use a queue for high-volume webhook processing
  • Monitor your endpoint to ensure it's always available
  • Test thoroughly before going to production

Security:

  • Use HTTPS only (never HTTP)
  • Keep secrets secure (environment variables, secret managers)
  • Validate event structure before processing
  • Rate limit your endpoint if needed

⛔ Don'ts

  • Don't process synchronously - webhook delivery will timeout
  • Don't skip signature verification - your endpoint could be exploited
  • Don't expose secrets in logs or error messages
  • Don't return error details to the webhook sender
  • Don't rely on order - events might arrive out of sequence
  • Don't make assumptions - always check event type and data structure

Handling Webhook Failures

Retry Policy

If your endpoint doesn't respond with 200 OK, Audit1 will retry:

  • Immediately after failure
  • 1 minute later
  • 5 minutes later
  • 30 minutes later
  • 2 hours later
  • 6 hours later

After 6 failed attempts, the webhook is marked as failed, and you'll receive an email notification.

Debugging Failed Webhooks

Check your webhook logs in the Audit1 dashboard:

# View recent webhook deliveries
curl https://developer-api.audit1.info/api/v1/webhooks/wh_1234567890/deliveries \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret"

Response:

{
  "deliveries": [
    {
      "id": "del_abc123",
      "eventType": "file.processed",
      "timestamp": "2024-01-15T10:30:00.000Z",
      "status": "delivered",
      "responseCode": 200,
      "attempts": 1
    },
    {
      "id": "del_def456",
      "eventType": "audit.completed",
      "timestamp": "2024-01-15T10:25:00.000Z",
      "status": "failed",
      "responseCode": 500,
      "attempts": 3,
      "lastError": "Connection timeout"
    }
  ]
}

Managing Webhooks

List All Webhooks

curl https://developer-api.audit1.info/api/v1/webhooks \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret"

Update Webhook Events

curl -X PUT https://developer-api.audit1.info/api/v1/webhooks/wh_1234567890 \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      "file.processed",
      "file.failed",
      "audit.completed"
    ]
  }'

Disable Webhook

curl -X PUT https://developer-api.audit1.info/api/v1/webhooks/wh_1234567890 \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "disabled"
  }'

Delete Webhook

curl -X DELETE https://developer-api.audit1.info/api/v1/webhooks/wh_1234567890 \
  -H "X-Client-ID: your_client_id" \
  -H "X-Client-Secret: your_client_secret"

Testing & Development

Local Development with ngrok

You can't receive webhooks on localhost directly. Use ngrok to create a public URL:

# Install ngrok
npm install -g ngrok

# Start your local server
node webhook-server.js  # running on http://localhost:3000

# In another terminal, create a tunnel
ngrok http 3000

ngrok will give you a public URL like: https://abc123.ngrok.io

Register this URL as your webhook endpoint (with /webhooks/audit1 path).

Webhook Testing Tools

  • Webhook.site - Inspect and debug webhooks
  • Postman - Manual webhook testing
  • ngrok - Local development tunneling
  • Request Bin - Webhook request collection

Complete Example

Here's a complete production-ready example:

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

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Queue for async processing
const eventQueue = [];

// Middleware to capture raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));

// Webhook endpoint
app.post('/webhooks/audit1', async (req, res) => {
  try {
    // 1. Verify signature
    const signature = req.headers['audit1-signature'];
    const body = req.body;
    
    if (!verifySignature(body, signature, WEBHOOK_SECRET)) {
      console.error('Invalid webhook signature');
      return res.status(401).send('Unauthorized');
    }
    
    // 2. Parse event
    const event = JSON.parse(body.toString());
    
    // 3. Check for duplicate (using event ID)
    if (await isDuplicate(event.id)) {
      console.log(`Duplicate event ${event.id}, ignoring`);
      return res.status(200).send('OK');
    }
    
    // 4. Add to queue for async processing
    eventQueue.push(event);
    
    // 5. Acknowledge receipt immediately
    res.status(200).send('OK');
    
    // 6. Process in background
    processEventAsync(event);
    
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).send('Internal error');
  }
});

function verifySignature(body, signature, secret) {
  const expectedSignature = 'sha256=' + 
    crypto.createHmac('sha256', secret)
          .update(body)
          .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

async function isDuplicate(eventId) {
  // Check your database or cache
  // Return true if event was already processed
  return false;
}

async function processEventAsync(event) {
  try {
    console.log(`Processing event: ${event.type}`);
    
    switch (event.type) {
      case 'file.processed':
        await handleFileProcessed(event.data);
        break;
        
      case 'file.failed':
        await handleFileFailed(event.data);
        break;
        
      case 'audit.completed':
        await handleAuditCompleted(event.data);
        break;
        
      default:
        console.log(`Unhandled event type: ${event.type}`);
    }
    
    // Mark event as processed
    await markEventProcessed(event.id);
    
  } catch (error) {
    console.error(`Error processing event ${event.id}:`, error);
    // Implement your error handling/retry logic
  }
}

async function handleFileProcessed(data) {
  console.log(`File ${data.fileName} processed: ${data.recordsProcessed} records`);
  // Your business logic
}

async function handleFileFailed(data) {
  console.log(`File ${data.fileName} failed: ${data.error.message}`);
  // Send alert, create ticket, etc.
}

async function handleAuditCompleted(data) {
  console.log(`Audit completed with adjustment: $${data.results.adjustment}`);
  // Update your system, notify customer, etc.
}

async function markEventProcessed(eventId) {
  // Save to database that this event was processed
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Troubleshooting

Webhook Not Received

Check:

  • Is your endpoint publicly accessible?
  • Is it using HTTPS (not HTTP)?
  • Is your firewall blocking incoming requests?
  • Check webhook status in Audit1 dashboard

Signature Verification Failing

Check:

  • Are you using the correct webhook secret?
  • Are you computing HMAC on the raw body (not parsed JSON)?
  • Are you using SHA256 algorithm?
  • Is the signature header being read correctly?

Timeouts

Check:

  • Are you responding within 10 seconds?
  • Move processing to background/queue
  • Return 200 OK immediately upon receipt

Getting Help

Support Resources

Common Questions

Q: Can I test webhooks without deploying to production?
A: Yes! Use ngrok or similar tools to tunnel to your local development environment.

Q: What happens if my server is down?
A: Audit1 will retry delivery according to our retry policy. You can also fetch missed events via the API.

Q: Can I receive webhooks for multiple organizations?
A: Yes, create separate webhook endpoints for each organization, or use a single endpoint and filter by data.

Q: How do I debug webhook issues?
A: Check the webhook delivery logs in your Audit1 dashboard, and monitor your server logs for errors.

Q: Can I change my webhook URL?
A: Yes, update your webhook configuration via the API or contact support.

Next Steps