Skip to content

Webhook Integration

Webhook Integration

This guide explains how to use the webhook integration to create identity verification challenges programmatically and receive callbacks when challenges complete.

Overview

The webhook integration allows external systems to:

  1. Create identity verification challenges via REST API
  2. Create Slack-based challenges programmatically (alternative to slash commands)
  3. Check challenge status via API
  4. Receive outbound webhook callbacks when challenges complete

Setup

1. Create a Webhook Integration

  1. Log in to the Challenge admin console at challenge.veraproof.io
  2. Navigate to IntegrationsWebhook Integrations
  3. Click Add Webhook Integration
  4. Fill in:
    • Display Name: A friendly name (e.g., “ITSM System”)
    • Callback URL (Optional): URL to receive webhook callbacks when challenges complete
    • Webhook Secret (Optional): Secret for signing outbound webhooks (HMAC SHA256)
  5. Click Create Integration
  6. Copy the API Key - it will only be shown once!

2. Get Your API Key

The API key is generated automatically when you create a webhook integration. If you lose it:

  1. Go to IntegrationsWebhook Integrations
  2. Find your integration
  3. Click Regenerate API Key
  4. Copy the new key immediately - it won’t be shown again

Authentication

All webhook API requests require authentication using an API key. Include the API key in one of two ways:

Terminal window
X-API-Key: your-api-key-here

Option 2: Authorization Bearer Header

Terminal window
Authorization: Bearer your-api-key-here

Creating Challenges

Standard Webhook Challenge

Creates a standard identity verification challenge that returns a verification URL.

Endpoint: POST /api/v1/challenges

Request Body:

{
"target_user_id": "user123",
"target_user_email": "user@example.com",
"context": "Access request for sensitive system",
"callback_url": "https://your-system.com/webhook/callback",
"requester_id": "system-name"
}

Fields:

  • target_user_id (optional): Target user identifier
  • target_user_email (optional): Target user email (required if target_user_id not provided)
  • context (optional): Reason/message for the challenge
  • callback_url (optional): Per-request callback URL (overrides integration default)
  • requester_id (optional): Requester identifier (defaults to “webhook”)

Response:

{
"challenge_id": "550e8400-e29b-41d4-a716-446655440000",
"verification_url": "https://challenge.veraproof.io/challenge/start/550e8400-e29b-41d4-a716-446655440000",
"status": "initiated",
"expires_at": "2025-12-01T12:00:00Z"
}

Example cURL:

Terminal window
curl -X POST https://challenge.veraproof.io/api/v1/challenges \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"target_user_email": "user@example.com",
"context": "Access request for production database",
"callback_url": "https://your-system.com/webhook/callback"
}'

Slack Challenge via Webhook

Creates a Slack-based challenge programmatically (alternative to using the /challenge slash command).

Endpoint: POST /api/v1/challenges

Request Body:

{
"integration_type": "slack",
"slack_user_id": "U01234ABCD",
"target_user_id": "U05678EFGH",
"target_user_email": "user@example.com",
"context": "Security verification required",
"callback_url": "https://your-system.com/webhook/callback"
}

Fields:

  • integration_type: Must be "slack"
  • slack_user_id (optional): Slack user ID of the requester. If not provided, requester_email must be provided.
  • requester_email (optional): Email of the requester. If not provided, slack_user_id must be provided. The system will automatically look up the Slack user ID.
  • target_user_id (optional): Target Slack user ID. If not provided, target_user_email must be provided.
  • target_user_email (optional): Target user email. If not provided, target_user_id must be provided. The system will automatically look up the Slack user ID.
  • context (optional): Reason/message for the challenge
  • callback_url (optional): Per-request callback URL
  • slack_instance_id (optional): Specific Slack integration instance ID to use. If not provided, the first available Slack integration for the tenant will be used.

Note: You can use any API key (webhook or Slack integration) to create Slack challenges, as long as your tenant has at least one Slack workspace configured. The system will automatically find and use the appropriate Slack integration for your tenant. Email addresses are preferred as they’re easier for external systems to provide - the app will automatically look up Slack user IDs.

Example cURL:

Terminal window
# Using emails (recommended - easier for external systems)
curl -X POST https://challenge.veraproof.io/api/v1/challenges \
-H "X-API-Key: your-webhook-api-key" \
-H "Content-Type: application/json" \
-d '{
"integration_type": "slack",
"requester_email": "requester@example.com",
"target_user_email": "user@example.com",
"context": "Security verification required"
}'

Checking Challenge Status

Check the current status of a challenge.

Endpoint: GET /api/v1/challenges/<challenge_id>

Response:

{
"challenge_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "verified",
"created_at": "2025-12-01T10:00:00Z",
"expires_at": "2025-12-01T10:15:00Z",
"completed_at": "2025-12-01T10:05:00Z",
"is_expired": false,
"device_fingerprint": {
"browser": "Chrome",
"os": "Windows",
"screen": "1920x1080",
"ip": "203.0.113.1"
}
}

Status Values:

  • initiated: Challenge created but not started
  • in-progress: User has started the verification process
  • verified: Challenge completed successfully
  • failed: Challenge failed
  • expired: Challenge expired

Example cURL:

Terminal window
curl -X GET https://challenge.veraproof.io/api/v1/challenges/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key-here"

Receiving Webhook Callbacks

When a challenge completes, the system will send a POST request to your configured callback URL (either the integration default or the per-request callback_url).

Callback Payload

HTTP Method: POST

Content-Type: application/json

Headers:

  • X-Webhook-Signature (if webhook secret is configured): sha256=<signature>

Payload:

{
"challenge_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "verified",
"created_at": "2025-12-01T10:00:00Z",
"completed_at": "2025-12-01T10:05:00Z",
"expires_at": "2025-12-01T10:15:00Z",
"target_user_id": "user123",
"target_user_email": "user@example.com",
"context": "Access request for sensitive system",
"device_fingerprint": {
"browser": "Chrome 120.0",
"os": "Windows 11",
"screen": "1920x1080",
"timezone": "America/New_York",
"ip": "203.0.113.1",
"user_agent": "Mozilla/5.0..."
},
"auth_method": "saml"
}

Callback URL Priority

  1. Per-request callback URL (if provided in challenge creation)
  2. Integration default callback URL (if configured)
  3. No callback (if neither is configured)

Webhook Signature Verification

If you configured a webhook secret in your integration settings, all outbound webhooks will include an X-Webhook-Signature header for verification.

Signature Format

X-Webhook-Signature: sha256=<hex_signature>

Verification Process

  1. Extract the signature from the X-Webhook-Signature header
  2. Remove the sha256= prefix
  3. Compute HMAC SHA256 of the request body using your webhook secret
  4. Compare the computed signature with the provided signature using constant-time comparison

Example Verification (Python)

import hmac
import hashlib
def verify_webhook_signature(payload, signature_header, secret):
"""
Verify webhook signature.
Args:
payload: Raw request body (bytes or string)
signature_header: Value from X-Webhook-Signature header
secret: Your webhook secret
Returns:
bool: True if signature is valid
"""
if not secret or not signature_header:
return False
# Extract signature (remove "sha256=" prefix)
if signature_header.startswith('sha256='):
provided_signature = signature_header[7:]
else:
provided_signature = signature_header
# Convert payload to bytes if string
if isinstance(payload, str):
payload = payload.encode('utf-8')
# Compute expected signature
expected_signature = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
# Use constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected_signature, provided_signature)
# Example usage (Flask)
from flask import request
@app.route('/webhook/callback', methods=['POST'])
def webhook_callback():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data()
secret = 'your-webhook-secret'
if not verify_webhook_signature(payload, signature, secret):
return {'error': 'Invalid signature'}, 401
data = request.get_json()
# Process webhook data...
return {'status': 'ok'}, 200

Example Verification (Node.js)

const crypto = require('crypto');
function verifyWebhookSignature(payload, signatureHeader, secret) {
if (!secret || !signatureHeader) {
return false;
}
// Extract signature (remove "sha256=" prefix)
const providedSignature = signatureHeader.replace('sha256=', '');
// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(providedSignature)
);
}
// Example usage (Express)
app.post('/webhook/callback', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body;
const secret = 'your-webhook-secret';
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const data = JSON.parse(payload);
// Process webhook data...
res.json({ status: 'ok' });
});

Examples

Complete Workflow Example

Terminal window
# 1. Create a challenge
RESPONSE=$(curl -X POST https://challenge.veraproof.io/api/v1/challenges \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"target_user_email": "user@example.com",
"context": "Access request for production database",
"callback_url": "https://your-system.com/webhook/callback"
}')
# Extract challenge_id from response
CHALLENGE_ID=$(echo $RESPONSE | jq -r '.challenge_id')
VERIFICATION_URL=$(echo $RESPONSE | jq -r '.verification_url')
echo "Challenge created: $CHALLENGE_ID"
echo "Verification URL: $VERIFICATION_URL"
# 2. Share the verification URL with the user
# (via email, ITSM ticket, Slack message, etc.)
# 3. Poll for status (optional)
while true; do
STATUS=$(curl -s -X GET \
https://challenge.veraproof.io/api/v1/challenges/$CHALLENGE_ID \
-H "X-API-Key: your-api-key-here" | jq -r '.status')
echo "Challenge status: $STATUS"
if [ "$STATUS" = "verified" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "expired" ]; then
break
fi
sleep 5
done
# 4. Or wait for webhook callback at your callback URL

ITSM Integration Example

Terminal window
# Create challenge and add verification URL to ITSM ticket
curl -X POST https://challenge.veraproof.io/api/v1/challenges \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"target_user_email": "user@example.com",
"context": "ITSM Ticket #12345 - Access request",
"callback_url": "https://itsm.example.com/api/webhooks/challenge-complete",
"requester_id": "itsm-system"
}' | jq -r '.verification_url' | \
xargs -I {} curl -X POST https://itsm.example.com/api/tickets/12345/comments \
-H "Authorization: Bearer itsm-token" \
-H "Content-Type: application/json" \
-d "{\"comment\": \"Please complete identity verification: {}\"}"

Error Responses

401 Unauthorized

{
"error": "Invalid or missing API key"
}

400 Bad Request

{
"error": "target_user_id or target_user_email is required"
}

404 Not Found

{
"error": "Challenge not found"
}

500 Internal Server Error

{
"error": "Failed to create challenge"
}

Best Practices

  1. Store API keys securely: Never commit API keys to version control
  2. Use HTTPS: Always use HTTPS for callback URLs
  3. Verify signatures: Always verify webhook signatures if a secret is configured
  4. Handle timeouts: Webhook callbacks have a 10-second timeout
  5. Idempotency: Handle duplicate webhook callbacks gracefully
  6. Error handling: Implement retry logic for failed webhook deliveries
  7. Logging: Log all webhook events for audit purposes

Support

For issues or questions, contact support@veraproof.io or refer to the main documentation.