Webhook Overview
Webhooks allow NotaryCam to send real-time notifications to your application when events occur during a transaction's lifecycle.
Webhook Flow
Webhook Events
NotaryCam sends webhooks for the following events:
| Event Type | Description |
|---|---|
transaction-status-update | Transaction status changed (in-progress, complete, cancelled) |
documents-processed | Documents uploaded via API finished processing |
tagging-started | Document tagging began in the session |
recording-available | Session recording finished processing |
auto-assigned-notary | A notary was auto-assigned to the transaction |
See Event Types for detailed payload examples for each event.
Quick Setup
1. Create Webhook Endpoint
Create an HTTPS endpoint at your domain:
// Express.js example
const express = require('express');
const app = express();
app.post('/webhooks/notarycam', express.json(), async (req, res) => {
try {
const event = req.body;
// Validate the Authorization header
const authHeader = req.headers['authorization'];
if (authHeader !== process.env.NOTARYCAM_WEBHOOK_AUTH_TOKEN) {
return res.status(401).send('Unauthorized');
}
// Process the event
await handleWebhookEvent(event);
// Respond quickly (< 5 seconds)
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Error processing webhook');
}
});
app.listen(3000);
2. Register with NotaryCam
Provide your webhook URL to NotaryCam:
- URL Format:
https://yourdomain.com/webhooks/notarycam - Method: POST
- Content-Type: application/json
- SSL: Required (HTTPS only)
Contact your NotaryCam account manager to register your webhook endpoint. You will provide:
- Your webhook URL
- An Authorization header value — a secret token that NotaryCam will include in the
Authorizationheader of every webhook request, allowing you to verify the request is from NotaryCam
3. Validate the Authorization Header
When NotaryCam sends a webhook, it includes the Authorization header you configured. Always validate this header to ensure the request is legitimate:
function validateWebhook(req) {
const authHeader = req.headers['authorization'];
const expectedToken = process.env.NOTARYCAM_WEBHOOK_AUTH_TOKEN;
if (!authHeader || authHeader !== expectedToken) {
return false;
}
return true;
}
Store your Authorization token securely as an environment variable. Never hard-code it in your application.
4. Handle Events
Route events to appropriate handlers:
async function handleWebhookEvent(event) {
switch (event.event) {
case 'transaction-status-update':
await handleStatusUpdate(event);
break;
case 'documents-processed':
await handleDocumentsProcessed(event);
break;
case 'tagging-started':
await handleTaggingStarted(event);
break;
case 'recording-available':
await handleRecordingAvailable(event);
break;
case 'auto-assigned-notary':
await handleAutoAssignedNotary(event);
break;
default:
console.log('Unhandled event:', event.event);
}
}
5. Respond Quickly
Respond to webhooks:
app.post('/webhooks/notarycam', async (req, res) => {
// Respond immediately
res.status(200).send('OK');
// Process asynchronously
setImmediate(async () => {
try {
await handleWebhookEvent(req.body);
} catch (error) {
console.error('Background processing error:', error);
}
});
});
Webhook Payload Structure
All webhooks share a common structure:
{
"event": "transaction-status-update",
"departmentId": "dept_abc123",
"data": {
"transactionId": "txn_abc123def456",
"status": "in-progress"
}
}
Common Fields
| Field | Type | Description |
|---|---|---|
event | string | Event type identifier |
departmentId | string | Department ID for the transaction |
data | object | Event-specific data (always includes transactionId) |
Example Events
Transaction Status Update
{
"event": "transaction-status-update",
"departmentId": "dept_abc123",
"data": {
"transactionId": "txn_abc123def456",
"status": "complete"
}
}
Documents Processed
{
"event": "documents-processed",
"departmentId": "dept_abc123",
"data": {
"success": true,
"transactionId": "txn_abc123def456"
}
}
Recording Available
{
"event": "recording-available",
"departmentId": "dept_abc123",
"data": {
"transactionId": "txn_abc123def456",
"recordingId": "rec_xyz789"
}
}
Event Deduplication
Prevent duplicate processing:
async function handleWebhookWithDedup(event) {
const eventId = `${event.event}_${event.data.transactionId}_${Date.now()}`;
// Check if already processed
const exists = await redis.get(`webhook:${eventId}`);
if (exists) {
console.log('Duplicate event, skipping:', eventId);
return { duplicate: true };
}
// Mark as processing
await redis.setex(`webhook:${eventId}`, 3600, 'processing');
try {
await handleWebhookEvent(event);
await redis.setex(`webhook:${eventId}`, 86400, 'processed');
} catch (error) {
await redis.del(`webhook:${eventId}`);
throw error;
}
}
Testing Events
Simulate Webhooks Locally
// Send test event to your webhook endpoint
const testEvent = {
event: 'transaction-status-update',
departmentId: 'test_dept_123',
data: {
transactionId: 'test_transaction_123',
status: 'in-progress'
}
};
await axios.post('http://localhost:3000/webhooks/notarycam', testEvent);
Next Steps
- Event Types - Detailed documentation for each event type