Webhooks
Webhook Integration
Receive real-time notifications when documents are verified by human reviewers.
Why Webhooks Are Essential
SyncAI uses Human-in-the-Loop verification. When a document has low confidence, it goes to a human reviewer. This process takes 1-5 minutes.
Instead of polling our API repeatedly, we push the verified data to your server via webhook the moment it's ready. This is more efficient and provides real-time updates.
Step 1: Register Your Webhook
POST
/webhooks/registercurl -X POST https://sync-ai.up.railway.app/webhooks/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"url": "https://your-server.com/syncai-webhook",
"events": ["document.verified", "document.rejected"],
"secret": "your-webhook-secret-for-hmac"
}'Events
- •
document.verified- Human approved the extraction - •
document.rejected- Human rejected (data quality issue)
Response
{
"webhook_id": "wh_abc123",
"url": "https://...",
"status": "active"
}Step 2: Handle the Webhook Payload
document.verified Payload
Sent when a human reviewer approves the extracted data
{
"event": "document.verified",
"timestamp": "2024-01-15T10:35:00Z",
"document_id": "doc_abc123",
"review_id": "rev_xyz789",
"verified_by": "human_reviewer",
"corrections_made": true,
"data": {
"header": {
"vendor": "TechFlow Inc.",
"date": "2024-01-15",
"invoice_no": "INV-2024-0892"
},
"amounts": {
"subtotal": 8500.00,
"tax": 680.00,
"total": 9180.00
},
"line_items": [
{
"description": "Software License - Annual",
"quantity": 1,
"unit_price": 5000.00,
"amount": 5000.00
}
]
},
"confidence": {
"original": 0.72,
"final": 1.0
}
}Step 3: Verify Webhook Signature (HMAC)
Security Warning
Always verify the webhook signature to ensure requests are actually from SyncAI, not an attacker.
Python
import hmac
import hashlib
from flask import Flask, request, jsonify
WEBHOOK_SECRET = "your-webhook-secret"
app = Flask(__name__)
@app.route("/syncai-webhook", methods=["POST"])
def handle_webhook():
# Get the signature from header
signature = request.headers.get("X-SyncAI-Signature")
if not signature:
return jsonify({"error": "Missing signature"}), 401
# Calculate expected signature
payload = request.get_data()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
# Verify signature matches
if not hmac.compare_digest(signature, expected):
return jsonify({"error": "Invalid signature"}), 401
# Process the webhook
data = request.json
if data["event"] == "document.verified":
print(f"Document verified: {data['document_id']}")
print(f"Extracted data: {data['data']}")
return jsonify({"received": True}), 200Node.js (Express)
const express = require('express');
const crypto = require('crypto');
const WEBHOOK_SECRET = 'your-webhook-secret';
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/syncai-webhook', (req, res) => {
const signature = req.headers['x-syncai-signature'];
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
// Calculate expected signature
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
// Verify signature
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the webhook
const data = JSON.parse(req.body);
if (data.event === 'document.verified') {
console.log('Document verified:', data.document_id);
console.log('Extracted data:', data.data);
}
res.json({ received: true });
});
app.listen(3000);Best Practices
- Return 200 quickly - Respond within 5 seconds. Process async if needed.
- Idempotency - Handle duplicate webhooks gracefully (check document_id).
- Retry handling - We retry failed webhooks 3 times with exponential backoff.
- HTTPS only - We only send webhooks to HTTPS endpoints.