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/register
curl -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}), 200

Node.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.