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:
- Event Occurs - Something happens in Audit1 (file uploaded, audit completed, etc.)
- Webhook Triggered - Audit1 prepares a notification about the event
- HTTP Request Sent - A secure POST request is sent to your endpoint
- Your Code Responds - Your server receives and processes the notification
- 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 thesecretvalue 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 Type | Description | When It Fires |
|---|---|---|
file.uploaded | File uploaded to system | File received via any method |
file.validated | File passed validation | Structure and format verified |
file.processed | File processing completed | Data extracted and ready |
file.failed | File processing failed | Errors prevented processing |
Audit Events
| Event Type | Description | When It Fires |
|---|---|---|
audit.started | Audit calculation begins | Audit period opens |
audit.completed | Audit calculation finished | Final audit results ready |
audit.adjusted | Audit manually adjusted | Admin makes corrections |
Employee Events
| Event Type | Description | When It Fires |
|---|---|---|
employee.created | New employee added | First payroll record received |
employee.updated | Employee data changed | Name, class code, etc. updated |
employee.terminated | Employee marked inactive | Final payroll processed |
Policy Events
| Event Type | Description | When It Fires |
|---|---|---|
policy.created | New policy issued | Policy setup completed |
policy.renewed | Policy renewal processed | New term begins |
policy.cancelled | Policy cancelled | Cancellation effective |
policy.premium_adjusted | Premium amount changed | Audit adjustments applied |
Payment Events
| Event Type | Description | When It Fires |
|---|---|---|
payment.processed | Payment completed | Premium payment cleared |
payment.failed | Payment failed | Payment declined or rejected |
payment.refunded | Payment refunded | Refund 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 3000ngrok 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
- Documentation: developer.audit1.info
- Email Support: [email protected]
- API Status: status.audit1.info
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
- Explore API Integration Guide for direct API access
- Review SFTP Connection Guide for file-based integrations
- Check out Connector Guides for payroll system integrations
Updated about 2 months ago
