Webhooks
Webhooks allow you to receive real-time HTTP notifications when events happen in your FaceSign account. Rather than polling the API for updates, webhooks push data to your endpoint as soon as an event occurs.
Setting up webhooks
To start receiving webhooks:
- Create an endpoint on your server to receive webhook events
- Register your endpoint URL in the FaceSign Dashboard
- Handle incoming webhook events in your application
Creating a webhook endpoint
Your webhook endpoint should:
- Accept POST requests with JSON payloads
- Respond quickly with a 2xx status code
- Verify the webhook signature for security
Basic webhook handler
import express from 'express';
import crypto from 'crypto';
const app = express();
// Important: Use raw body for signature verification
app.post('/webhooks/facesign',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['facesign-signature'];
const body = req.body;
// Verify signature (see below)
if (!verifySignature(body, signature, webhookSecret)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(body);
// Handle the event
switch (event.type) {
case 'session.verified':
console.log('Session verified:', event.data.id);
break;
case 'session.failed':
console.log('Session failed:', event.data.id);
break;
// ... handle other event types
}
// Return 200 to acknowledge receipt
res.status(200).send('OK');
}
);
Webhook events
FaceSign sends the following webhook events:
Session events
- Name
session.created
- Type
- event
- Description
Sent when a new verification session is created.
- Name
session.started
- Type
- event
- Description
Sent when a user begins the verification process.
- Name
session.verified
- Type
- event
- Description
Sent when a session is successfully verified.
- Name
session.failed
- Type
- event
- Description
Sent when verification fails (e.g., document rejected, face mismatch).
- Name
session.cancelled
- Type
- event
- Description
Sent when a session is cancelled by the user or via API.
- Name
session.expired
- Type
- event
- Description
Sent when a session expires without completion.
Event object structure
All webhook events follow this structure:
Event structure
{
"id": "evt_1a2b3c4d5e6f",
"object": "event",
"type": "session.verified",
"created": 1706284800,
"data": {
// The full session object
"id": "vs_1a2b3c4d5e6f",
"object": "verification_session",
"status": "verified",
"verified_outputs": {
"first_name": "John",
"last_name": "Doe",
// ... other verification results
}
// ... other session properties
}
}
Verifying webhook signatures
FaceSign signs all webhook payloads so you can verify they're authentic. The signature is included in the FaceSign-Signature
header.
Signature verification
import crypto from 'crypto';
function verifySignature(
payload: Buffer | string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Always use a timing-safe comparison function to prevent timing attacks when verifying signatures.
Handling webhooks
Best practices
- Respond quickly - Return a 2xx status code as soon as possible
- Process asynchronously - Use a queue for time-consuming operations
- Handle duplicates - Use the event ID to ensure idempotency
- Verify signatures - Always verify the webhook signature
- Handle failures gracefully - Implement proper error handling
Retries
FaceSign will retry failed webhook deliveries with exponential backoff:
- First retry: 5 seconds
- Second retry: 30 seconds
- Third retry: 2 minutes
- Fourth retry: 10 minutes
- Fifth retry: 1 hour
After 5 failed attempts, the webhook is marked as failed and won't be retried.
Testing webhooks
You can test your webhook endpoint using the FaceSign Dashboard:
- Go to Settings → Webhooks
- Click on your webhook endpoint
- Click Send test webhook
- Select an event type and click Send
Example: Complete webhook handler
Here's a complete example that handles all session events:
Complete webhook handler
import express from 'express';
import crypto from 'crypto';
const app = express();
const webhookSecret = process.env.FACESIGN_WEBHOOK_SECRET;
// Webhook handler
app.post('/webhooks/facesign',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['facesign-signature'] as string;
// Verify signature
if (!verifySignature(req.body, signature, webhookSecret)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
// Prevent duplicate processing
if (await isEventProcessed(event.id)) {
return res.status(200).send('Already processed');
}
try {
// Handle different event types
switch (event.type) {
case 'session.created':
await handleSessionCreated(event.data);
break;
case 'session.verified':
await handleSessionVerified(event.data);
break;
case 'session.failed':
await handleSessionFailed(event.data);
break;
case 'session.cancelled':
await handleSessionCancelled(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
// Mark event as processed
await markEventProcessed(event.id);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).send('Internal error');
}
}
);
async function handleSessionVerified(session: any) {
// Update user verification status
await updateUserVerificationStatus(session.metadata.user_id, {
verified: true,
verifiedAt: new Date(),
verificationData: session.verified_outputs,
});
// Send confirmation email
await sendVerificationSuccessEmail(session.metadata.user_id);
}
async function handleSessionFailed(session: any) {
// Log failure reason
console.error(`Verification failed for user ${session.metadata.user_id}`);
// Notify user
await sendVerificationFailedEmail(session.metadata.user_id);
}
Webhook security
IP allowlisting
For additional security, you can restrict webhook requests to FaceSign's IP addresses:
52.89.214.238
34.212.75.30
54.218.53.128
52.32.178.7
Request headers
All webhook requests include these headers:
- Name
FaceSign-Signature
- Type
- string
- Description
HMAC signature of the request body
- Name
FaceSign-Event-Id
- Type
- string
- Description
Unique identifier for this event
- Name
Content-Type
- Type
- string
- Description
Always
application/json
Next steps
Now that you understand webhooks, explore these related topics: