Veriff Webhooks

Configure and handle webhook notifications for asynchronous verification results.

Veriff Webhooks

Webhooks allow you to receive real-time notifications when verification processes complete. This is essential for handling asynchronous verifications and building responsive user experiences.

Overview

When a verification request is processed asynchronously, LoomAPI sends an HTTP POST request to your configured webhook URL with the results. This eliminates the need for polling and enables immediate user notifications.

Setup

1. Configure Webhook URL

Set up your webhook endpoint in the dashboard:

  1. Go to dashboard.loomapi.com
  2. Navigate to Webhooks section
  3. Add your webhook URL
  4. Configure secret for signature verification

2. Webhook URL Requirements

  • HTTPS Only: Webhooks must use HTTPS (HTTP not supported)
  • Public Access: URL must be accessible from the internet
  • Fast Response: Respond within 5 seconds to avoid timeouts
  • Idempotent: Handle duplicate deliveries gracefully

Webhook Payload

{
  "event": "verification.completed",
  "request_id": "req_1234567890abcdef",
  "timestamp": "2024-01-15T14:30:00Z",
  "verification": {
    "status": "verified",
    "verified_age": 25,
    "confidence_score": 0.98,
    "document_type": "passport",
    "processing_time_ms": 2340
  },
  "metadata": {
    "user_id": "user_123",
    "session_id": "session_456"
  }
}

Payload Fields

  • event: Event type (see Event Types)
  • request_id: Original verification request ID
  • timestamp: ISO 8601 timestamp of the event
  • verification: Verification results (same as sync response)
  • metadata: Custom metadata from your original request

Event Types

verification.completed

Sent when verification processing finishes successfully.

{
  "event": "verification.completed",
  "request_id": "req_1234567890abcdef",
  "verification": {
    "status": "verified",
    "verified_age": 25,
    "confidence_score": 0.98
  }
}

verification.failed

Sent when verification fails due to processing errors.

{
  "event": "verification.failed",
  "request_id": "req_1234567890abcdef",
  "error": {
    "code": "DOCUMENT_QUALITY_LOW",
    "message": "Document quality insufficient for processing"
  }
}

verification.expired

Sent when a verification session expires before completion.

{
  "event": "verification.expired",
  "request_id": "req_1234567890abcdef",
  "expired_at": "2024-01-15T15:00:00Z"
}

Signature Verification

All webhook requests include an X-Loom-Signature header for security verification.

Signature Format

X-Loom-Signature: t=1640995200,v1=abc123def456...

Where:

  • t: Unix timestamp
  • v1: HMAC-SHA256 signature

Verification Process

const crypto = require('crypto')

function verifyWebhookSignature(payload, signature, secret) {
  // Extract timestamp and signature
  const [timestampPart, signaturePart] = signature.split(',')
  const timestamp = timestampPart.split('=')[1]
  const expectedSignature = signaturePart.split('=')[1]

  // Create signed payload
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`

  // Calculate expected signature
  const calculatedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload, 'utf8')
    .digest('hex')

  // Compare signatures
  return crypto.timingSafeEqual(
    Buffer.from(calculatedSignature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  )
}
import hmac
import hashlib
import json
from typing import Dict, Any

def verify_webhook_signature(payload: Dict[str, Any], signature: str, secret: str) -> bool:
    # Extract timestamp and signature
    timestamp_part, signature_part = signature.split(',')
    timestamp = timestamp_part.split('=')[1]
    expected_signature = signature_part.split('=')[1]

    # Create signed payload
    signed_payload = f"{timestamp}.{json.dumps(payload, separators=(',', ':'))}"

    # Calculate expected signature
    calculated_signature = hmac.new(
        secret.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Compare signatures using constant-time comparison
    return hmac.compare_digest(calculated_signature, expected_signature)

Security Best Practices

  • Always verify signatures before processing webhooks
  • Use HTTPS for your webhook endpoint
  • Store secrets securely (environment variables, not code)
  • Implement timeouts to prevent hanging requests
  • Log webhook attempts for debugging

Handling Webhooks

Basic Handler

app.post('/webhooks/loom', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-loom-signature']
  const secret = process.env.LOOM_WEBHOOK_SECRET

  // Verify signature
  if (!verifyWebhookSignature(req.body, signature, secret)) {
    console.error('Invalid webhook signature')
    return res.status(401).send('Unauthorized')
  }

  // Process webhook
  const { event, request_id, verification } = req.body

  switch (event) {
    case 'verification.completed':
      handleVerificationCompleted(request_id, verification)
      break
    case 'verification.failed':
      handleVerificationFailed(request_id, verification)
      break
    default:
      console.log(`Unhandled event: ${event}`)
  }

  // Respond quickly
  res.status(200).send('OK')
})

Idempotency

Handle duplicate webhooks gracefully:

const processedWebhooks = new Set()

function processWebhook(requestId, eventType, data) {
  const webhookId = `${requestId}-${eventType}`

  if (processedWebhooks.has(webhookId)) {
    console.log(`Duplicate webhook ignored: ${webhookId}`)
    return
  }

  // Process webhook
  // ... your logic here ...

  // Mark as processed
  processedWebhooks.add(webhookId)

  // Optional: Clean up old entries periodically
  if (processedWebhooks.size > 10000) {
    // Clear old entries (implement based on your needs)
  }
}

Retry Policy

Automatic Retries

LoomAPI automatically retries failed webhook deliveries:

  • Retry Schedule: 1min, 5min, 15min, 1hr, 6hr, 24hr
  • Max Attempts: 7 attempts over 24 hours
  • Backoff: Exponential backoff with jitter

Failure Handling

Webhook delivery fails if:

  • HTTP status code ≥ 300
  • Connection timeout (>5 seconds)
  • DNS resolution failure
  • SSL/TLS errors

Manual retries

You can retry failed webhook deliveries from the dashboard (Webhooks section) if your plan supports it. The API does not expose a dedicated webhook retry endpoint in v1.

Testing Webhooks

Development Tools

Use tools like ngrok or localtunnel to expose local development servers:

# Using ngrok
ngrok http 3000
# Forwarding: https://abc123.ngrok.io -> http://localhost:3000

# Using localtunnel
npx localtunnel --port 3000
# Tunnel URL: https://random-name.loca.lt

Testing locally

Use a tunnel (e.g. ngrok) to expose your local endpoint, then start a real verification via POST /verify/start and complete the session; your webhook will be called when the event is sent. There is no separate "test webhook" API in v1 — use the dashboard or a real verification to trigger events.

Monitoring

Dashboard Monitoring

Monitor webhook delivery in your dashboard:

  • Delivery success rates
  • Response times
  • Failure reasons
  • Recent deliveries

Logging

Implement comprehensive logging:

function logWebhook(requestId, event, status, details = {}) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    request_id: requestId,
    event,
    status, // 'received', 'processed', 'failed'
    details,
    user_agent: req.get('User-Agent'),
    ip_address: req.ip
  }

  console.log(JSON.stringify(logEntry))

  // Optional: Send to monitoring service
  // monitoringService.log('webhook', logEntry)
}

Troubleshooting

Common Issues

  • Signature verification fails: Check timestamp format and secret key
  • Timeouts: Ensure your endpoint responds within 5 seconds
  • Duplicate processing: Implement idempotency checks
  • Missing webhooks: Check firewall rules and HTTPS configuration

Debugging

If signature verification or delivery fails, check your webhook secret, ensure the endpoint responds with 2xx within a few seconds, and inspect logs. Use the dashboard to see delivery status and failure reasons. The API does not expose a separate webhook debug endpoint in v1.

Best Practices

Security

  • Always verify webhook signatures
  • Use HTTPS endpoints only
  • Implement rate limiting on your webhook endpoints
  • Validate payload structure before processing

Reliability

  • Implement idempotency to handle duplicates
  • Respond quickly to webhook requests
  • Monitor delivery success rates
  • Have fallback mechanisms for critical processes

Performance

  • Process webhooks asynchronously in your application
  • Use queues for high-volume processing
  • Monitor response times and optimize slow endpoints
  • Implement circuit breakers for downstream failures

Support

Need help with webhooks?