Webhook Methods
Register webhooks and verify signatures with the VertaaUX SDK
Webhook Methods
The SDK provides methods to manage webhooks and verify webhook signatures.
Overview
Webhooks notify your server when audits complete, eliminating the need for polling. When an audit finishes, VertaaUX sends a POST request to your registered endpoint with the results.
Webhook Management
webhooks.create()
Register a new webhook endpoint.
client.webhooks.create(params: WebhookRequest): Promise<WebhookResponse>| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | required | HTTPS endpoint URL for webhook delivery |
| secret | string | required | Secret for HMAC-SHA256 signature verification |
Example:
import { VertaaUX } from '@vertaaux/sdk';
const client = new VertaaUX({
apiKey: process.env.VERTAA_API_KEY!,
});
const webhook = await client.webhooks.create({
url: 'https://api.example.com/webhooks/vertaaux',
secret: 'wh_sec_' + crypto.randomUUID(),
});
console.log('Webhook ID:', webhook.id);
console.log('URL:', webhook.url);
console.log('Created:', webhook.created_at);Store your webhook secret securely. You'll need it to verify incoming webhook signatures.
webhooks.list()
List all registered webhooks.
client.webhooks.list(): Promise<{ webhooks: WebhookResponse[] }>Example:
const { webhooks } = await client.webhooks.list();
webhooks.forEach((wh) => {
console.log(`${wh.id}: ${wh.url}`);
});webhooks.delete()
Remove a webhook endpoint.
client.webhooks.delete(webhookId: string): Promise<void>Example:
await client.webhooks.delete('wh_def456ghi012');
console.log('Webhook deleted');Signature Verification
Webhooks include cryptographic signatures for security. Always verify signatures before processing webhook payloads.
verifyWebhookSignature()
Import the helper function to verify webhook signatures:
import { verifyWebhookSignature } from '@vertaaux/sdk';Signature Format
Webhook requests include these headers:
| Header | Description |
|---|---|
X-Vertaa-Signature | HMAC-SHA256 signature (hex-encoded) |
X-Vertaa-Timestamp | Unix timestamp when signature was created |
The signature is computed as: HMAC-SHA256(secret, timestamp + "." + payload)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| secret | string | required | Your webhook secret (from webhook registration) |
| payload | string | required | Raw request body as string |
| signature | string | required | X-Vertaa-Signature header value |
| timestamp | string | number | required | X-Vertaa-Timestamp header value |
| toleranceSeconds | number | optional | Maximum age of signature in seconds. Default: 300 (5 minutes) |
Express.js Handler
import express from 'express';
import { verifyWebhookSignature } from '@vertaaux/sdk';
const app = express();
// Important: Use raw body for signature verification
app.post(
'/webhooks/vertaaux',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-vertaa-signature'] as string;
const timestamp = req.headers['x-vertaa-timestamp'] as string;
const payload = req.body.toString('utf8');
// Verify signature
const isValid = await verifyWebhookSignature({
secret: process.env.WEBHOOK_SECRET!,
payload,
signature,
timestamp,
toleranceSeconds: 300, // Reject webhooks older than 5 minutes
});
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse and process the webhook
const event = JSON.parse(payload);
console.log('Received webhook:', event.job_id, event.status);
if (event.status === 'completed') {
console.log('Audit score:', event.scores?.overall);
console.log('Issues:', event.issues?.length);
// Process the results...
} else if (event.status === 'failed') {
console.error('Audit failed:', event.error);
// Handle failure...
}
// Acknowledge receipt
res.status(200).json({ received: true });
}
);
app.listen(3000);Next.js API Route
import { NextRequest, NextResponse } from 'next/server';
import { verifyWebhookSignature } from '@vertaaux/sdk';
export async function POST(request: NextRequest) {
// Get headers
const signature = request.headers.get('x-vertaa-signature');
const timestamp = request.headers.get('x-vertaa-timestamp');
if (!signature || !timestamp) {
return NextResponse.json(
{ error: 'Missing signature headers' },
{ status: 400 }
);
}
// Get raw body
const payload = await request.text();
// Verify signature
const isValid = await verifyWebhookSignature({
secret: process.env.WEBHOOK_SECRET!,
payload,
signature,
timestamp,
});
if (!isValid) {
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 401 }
);
}
// Parse and process
const event = JSON.parse(payload);
switch (event.status) {
case 'completed':
// Store results, notify users, etc.
console.log('Audit completed:', event.job_id);
console.log('Score:', event.scores?.overall);
break;
case 'failed':
// Log failure, retry, alert
console.error('Audit failed:', event.job_id, event.error);
break;
}
return NextResponse.json({ received: true });
}Webhook Events
audit.completed
Sent when an audit finishes successfully.
{
"job_id": "clu1a2b3c4d5e6f7g8h9i0j1",
"status": "completed",
"completed_at": "2024-01-15T10:32:30Z",
"scores": {
"overall": 78,
"ux": 82,
"accessibility": 74,
"information_architecture": 80,
"performance": 76
},
"issues": [
{
"id": "iss_color_contrast_001",
"severity": "error",
"category": "accessibility",
"description": "Text has insufficient contrast (3.2:1 ratio)",
"recommendation": "Increase contrast to at least 4.5:1 for body text",
"wcag_reference": "WCAG 2.1 SC 1.4.3 (AA)",
"selector": ".footer-text",
"element": "<p class=\"footer-text\">Contact Us</p>"
}
]
}audit.failed
Sent when an audit encounters an error.
{
"job_id": "clu1a2b3c4d5e6f7g8h9i0j1",
"status": "failed",
"completed_at": "2024-01-15T10:32:30Z",
"error": "Page load timeout after 30 seconds"
}Security Best Practices
1. Always Verify Signatures
Never process webhooks without verifying the signature:
// Always verify before parsing/processing
const isValid = await verifyWebhookSignature({ ... });
if (!isValid) {
return res.status(401).send('Unauthorized');
}2. Use Raw Body
Parse the body as raw text, not JSON, for signature verification:
// Express
app.post('/webhook', express.raw({ type: 'application/json' }), handler);
// Next.js
const payload = await request.text(); // Not request.json()3. Check Timestamp
Use toleranceSeconds to reject old (potentially replayed) webhooks:
await verifyWebhookSignature({
// ...
toleranceSeconds: 300, // Reject webhooks > 5 minutes old
});4. Respond Quickly
Return 200 quickly and process async to avoid timeouts:
app.post('/webhook', async (req, res) => {
// Verify signature
if (!await verify(req)) {
return res.status(401).send();
}
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(JSON.parse(req.body.toString()));
});Webhook Limits by Tier
| Tier | Max Webhooks |
|---|---|
| Free | 0 |
| Pro | 5 |
| Agency | 50 |
| Enterprise | 100 |
Related
- Audit Methods - Create audits
- Error Handling - Handle webhook errors
- API Webhooks - REST API webhook documentation
Was this page helpful?