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 manually retry failed webhooks from the dashboard or trigger them via API:

curl -X POST https://api.loomapi.com/v1/webhooks/retry \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"request_id": "req_1234567890abcdef"}'

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

Test Mode

Use test mode to trigger webhook events without real verifications:

curl -X POST https://api.loomapi.com/v1/webhooks/test \
  -H "Authorization: Bearer your_test_key" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "verification.completed",
    "test_data": {
      "status": "verified",
      "verified_age": 25
    }
  }'

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

Debug Mode

Enable debug logging for detailed webhook information:

curl -X PUT https://api.loomapi.com/v1/webhooks/debug \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

This provides additional headers with debugging information.

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?