Skip to main content

Webhook Management

Webhooks enable real-time notifications when events occur in your Nafsi AI account. All webhooks use mTLS (mutual TLS) authentication for secure communication.

Overview

Webhooks allow you to:

  • Receive real-time notifications when verifications complete
  • Integrate verification checks via custom webhooks
  • Subscribe to system events like low balance alerts
  • Build custom workflows with external verification services

Webhook Types

1. Event Notification Webhooks

Receive notifications when system events occur:

Example Events:

  • verification.completed - Verification finished successfully
  • verification.failed - Verification failed
  • verification.review_required - Manual review needed
  • workflow.created - New workflow created
  • units.low_balance - Credits running low

View all available events →

2. Verification Check Webhooks

Create custom verification checks that call your external service:

Use Cases:

  • Custom liveness detection
  • Third-party AML screening
  • Proprietary fraud detection
  • Document validation against internal databases

Creating a Webhook

Navigate to WebhooksCreate Webhook

Step 1: Basic Information

FieldDescriptionRequired
Webhook TypeEvent Notification or Verification CheckYes
NameDescriptive name (e.g., "Production Webhook")Yes
DescriptionPurpose and use caseOptional
Webhook URLHTTPS endpoint to receive webhooksYes
HTTP MethodPOST, PUT, or PATCHYes (default: POST)

Example:

Type: Event Notification
Name: Production Verification Webhook
Description: Receive notifications for all verification events
URL: https://api.myapp.com/webhooks/nafsi
Method: POST

Requirements:

  • URL must use HTTPS (no HTTP allowed)
  • URL must be publicly accessible
  • Endpoint must respond within 30 seconds
  • Endpoint must return 2xx status code for success

Step 2: Authentication (mTLS)

All webhooks require mTLS authentication for security.

What is mTLS?

mTLS (mutual Transport Layer Security) is a two-way authentication method where:

  • Your server verifies Nafsi's identity
  • Nafsi verifies your server's identity
  • All communication is encrypted

Configure mTLS

  1. Upload CA Certificate (PEM format)

Click Choose File and select your CA certificate file.

Example CA Certificate:

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRKP7MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
... (your certificate content) ...
-----END CERTIFICATE-----
  1. Click "Validate Certificate"

The system will:

  • Verify the certificate format (PEM)
  • Test TLS handshake with your webhook URL
  • Check certificate expiry
  • Extract certificate details

Validation Response:

✅ Certificate valid
Expires in 365 days
No expiry warning

If Validation Fails:

  • Ensure certificate is in PEM format (not DER or other formats)
  • Verify webhook URL is accessible
  • Check that your server is configured for mTLS
  • Ensure certificate is not expired

Generate a Self-Signed Certificate (for testing)

# Generate private key
openssl genrsa -out server.key 2048

# Generate certificate signing request
openssl req -new -key server.key -out server.csr

# Generate self-signed certificate
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

# Convert to PEM format (if needed)
cat server.crt > server.pem

For production, use a certificate from a trusted CA.

  1. Webhook Secret (Optional but Recommended)

Add a secret key for HMAC signature verification:

Secret: sk_webhook_abc123xyz789

This secret is used to sign webhook payloads so you can verify authenticity.

Learn how to verify signatures →

Step 3: Custom Headers (Optional)

Add custom HTTP headers to include in webhook requests:

Header NameHeader ValueEnabled
X-Custom-HeaderMyValue
X-API-Versionv1
AuthorizationBearer token123

Use Cases:

  • Add API keys for your endpoint
  • Include version headers
  • Add correlation IDs

Step 4: Event Subscription

(Event Notification Webhooks Only)

Select which events you want to receive:

Verification Lifecycle Events

  • ☑️ verification.started - Verification request received
  • ☑️ verification.completed - Verification succeeded
  • ☑️ verification.failed - Verification failed
  • ☑️ verification.timeout - Verification timed out
  • verification.review_required - Manual review needed

Workflow Events

  • workflow.created - New workflow created
  • workflow.updated - Workflow modified
  • workflow.activated - Workflow activated
  • workflow.deactivated - Workflow deactivated

Billing Events

  • ☑️ units.low_balance - Credits below threshold
  • ☑️ units.depleted - Credits exhausted

System Events

  • webhook.delivery.failed - Webhook delivery failed
  • client.credits.low - Client credits low

Note: You can subscribe to as many events as needed. Each event will trigger a separate webhook delivery.

Step 5: Verification Criteria

(Verification Check Webhooks Only)

Define success criteria for custom verification checks:

SettingDescriptionDefault
Require Success FieldResponse must include success: trueON
Require Score FieldResponse must include confidence scoreON
Minimum ThresholdMinimum score for pass (0.0 - 1.0)0.85

Example Response from Your Webhook:

{
"success": true,
"score": 0.92,
"data": {
"result": "approved",
"reason": "Face match successful"
}
}

Request Schema (Optional): Define the JSON structure sent to your webhook.

Response Schema (Optional): Define the expected JSON structure from your webhook.

Step 6: Review & Create

Review all settings and click Create Webhook.

The webhook is created with status: Inactive

To activate: Click Activate on the webhook row.

Managing Webhooks

Webhook List

View all webhooks with:

ColumnDescription
NameWebhook name
TypeEvent Notification or Verification Check
URLWebhook endpoint (truncated)
EventsNumber of subscribed events
StatusActive / Inactive / Paused
Success RateDelivery success percentage
Last TriggeredTimestamp of last delivery
ActionsEdit / Test / Delete / View Logs

Testing a Webhook

Before going live, test your webhook:

  1. Click Test on the webhook row
  2. Select an event type (e.g., verification.completed)
  3. Click Send Test Event

Test Payload Example:

{
"event": "verification.completed",
"timestamp": "2026-01-28T10:30:00Z",
"test": true,
"data": {
"verification_id": "test_ver_123",
"status": "success",
"confidence": 0.87
}
}

Test Results:

  • ✅ Success (200ms) - Webhook endpoint responded correctly
  • ❌ Failed (HTTP 500) - Server error
  • ❌ TLS verification failed - Certificate issue
  • ⏱️ Timeout (30s) - Endpoint too slow

Viewing Delivery Logs

Track webhook delivery history:

  1. Click View Logs on the webhook
  2. See all delivery attempts with:
    • Timestamp
    • Event type
    • Status (Success/Failed/Retrying)
    • Response code
    • Response time
    • Error message (if failed)

Filter logs by:

  • Date range
  • Status
  • Event type

Export logs as CSV for analysis.

Webhook Statistics

View webhook performance:

  • Total Deliveries: All attempts
  • Successful Deliveries: 2xx responses
  • Failed Deliveries: 4xx/5xx errors
  • Average Response Time: Median latency
  • Success Rate: Percentage of successful deliveries
  • Last Success: Timestamp
  • Last Failure: Timestamp

Editing a Webhook

  1. Click Edit on the webhook
  2. Modify settings (except webhook type cannot be changed)
  3. Click Update Webhook

Note: Editing a webhook does not affect pending deliveries.

Pausing/Resuming a Webhook

Temporarily stop webhook deliveries:

  1. Click Pause on the webhook
  2. Status changes to Paused
  3. Events will not be delivered while paused

To resume: Click Resume

Deleting a Webhook

Permanently delete a webhook:

  1. Click Delete on the webhook
  2. Confirm deletion
  3. Webhook and all logs are deleted

Warning: This action cannot be undone!

Webhook Delivery

Delivery Flow

Event Occurs → Queue Event → Validate Webhook → Prepare Payload
→ Add Signature → Deliver to Endpoint → Log Result → Retry if Failed

Payload Structure

All webhook payloads follow this structure:

{
"event": "verification.completed",
"event_id": "evt_abc123",
"timestamp": "2026-01-28T10:30:00Z",
"organisation_id": 18,
"data": {
-- Event-specific data
}
}

HTTP Headers

Each webhook request includes:

POST /webhooks/nafsi HTTP/1.1
Host: api.myapp.com
Content-Type: application/json
X-Nafsi-Signature: sha256=abc123...
X-Nafsi-Event: verification.completed
X-Webhook-ID: webhook_xyz789
X-Delivery-ID: delivery_uvw456
User-Agent: Nafsi-Webhooks/1.0

Verifying Webhook Signatures

Why Verify?

  • Ensure webhook came from Nafsi
  • Prevent replay attacks
  • Detect tampering

How to Verify:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

// Compare signatures (constant-time comparison)
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from('sha256=' + expectedSignature)
);
}

// In your webhook endpoint
app.post('/webhooks/nafsi', (req, res) => {
const signature = req.headers['x-nafsi-signature'];
const payload = req.body;
const secret = process.env.WEBHOOK_SECRET;

if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}

// Process webhook
console.log('Valid webhook received:', payload);

res.status(200).send('OK');
});

Python Example:

import hmac
import hashlib
import json

def verify_webhook_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(
signature,
'sha256=' + expected
)

# In your webhook handler
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/nafsi', methods=['POST'])
def webhook():
signature = request.headers.get('X-Nafsi-Signature')
payload = request.json
secret = os.getenv('WEBHOOK_SECRET')

if not verify_webhook_signature(payload, signature, secret):
return 'Invalid signature', 401

# Process webhook
print('Valid webhook received:', payload)

return 'OK', 200

Retry Logic

If webhook delivery fails, Nafsi automatically retries:

AttemptDelayCondition
1stImmediateInitial delivery
2nd2 secondsIf 1st fails
3rd4 secondsIf 2nd fails

Exponential Backoff: Delay = 2^attempt seconds

Retryable Errors:

  • 5xx server errors
  • Timeouts
  • Connection errors

Non-Retryable Errors:

  • 4xx client errors (bad request)
  • TLS verification failures

After 3 Failed Attempts:

  • Webhook delivery marked as failed_permanent
  • Event webhook.delivery.failed is triggered
  • Webhook stats updated (failure_count++)

Auto-Deactivation: After 10 consecutive failures, the webhook is automatically paused and you receive an email notification.

Response Requirements

Your webhook endpoint should:

  1. Respond quickly (< 30 seconds)
  2. Return 2xx status code for success
  3. Return 4xx for validation errors (won't retry)
  4. Return 5xx for server errors (will retry)

Good Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
"received": true,
"message": "Webhook processed successfully"
}

Error Response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
"error": "Database connection failed",
"retry": true
}

Event Payloads

verification.completed

{
"event": "verification.completed",
"event_id": "evt_abc123",
"timestamp": "2026-01-28T10:30:00Z",
"organisation_id": 18,
"data": {
"verification_id": "ver_xyz789",
"workflow_id": "wf_abc123",
"workflow_name": "Basic KYC",
"status": "success",
"confidence": 0.87,
"units_consumed": 11,
"processing_time_ms": 2340,
"result": {
"decision": "approved",
"extracted_data": {
"full_name": "JOHN KAMAU DOE",
"date_of_birth": "1990-01-15",
"national_id_number": "12345678"
},
"checks": {
"face_match": {
"result": "pass",
"confidence": 0.87
}
}
},
"metadata": {
"customer_id": "cust_123"
}
}
}

verification.failed

{
"event": "verification.failed",
"event_id": "evt_def456",
"timestamp": "2026-01-28T10:35:00Z",
"organisation_id": 18,
"data": {
"verification_id": "ver_uvw012",
"workflow_id": "wf_abc123",
"status": "failed",
"reason": "Face match confidence below threshold",
"failed_check": "face_match",
"confidence": 0.62,
"threshold": 0.75,
"units_consumed": 5
}
}

units.low_balance

{
"event": "units.low_balance",
"event_id": "evt_ghi789",
"timestamp": "2026-01-28T14:00:00Z",
"organisation_id": 18,
"data": {
"current_balance": 850,
"threshold": 1000,
"estimated_verifications_remaining": 70,
"projected_depletion_date": "2026-01-29"
}
}

View all event payloads →

Best Practices

1. Idempotency

Handle duplicate deliveries gracefully:

const processedEvents = new Set();

app.post('/webhooks/nafsi', (req, res) => {
const eventId = req.body.event_id;

if (processedEvents.has(eventId)) {
return res.status(200).send('Already processed');
}

// Process event
processWebhook(req.body);

// Mark as processed
processedEvents.add(eventId);

res.status(200).send('OK');
});

2. Async Processing

Don't block the webhook response:

app.post('/webhooks/nafsi', async (req, res) => {
// Immediately acknowledge receipt
res.status(200).send('OK');

// Process async
processWebhookAsync(req.body).catch(err => {
console.error('Error processing webhook:', err);
});
});

3. Error Handling

app.post('/webhooks/nafsi', async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).send('OK');
} catch (err) {
console.error('Webhook processing error:', err);
// Return 5xx so Nafsi retries
res.status(500).send('Processing error');
}
});

4. Logging

Log all webhook receipts for debugging:

app.post('/webhooks/nafsi', (req, res) => {
console.log('Webhook received:', {
event: req.body.event,
eventId: req.body.event_id,
timestamp: req.body.timestamp,
data: req.body.data
});

// Process and respond
res.status(200).send('OK');
});

Troubleshooting

Issue: Certificate Validation Fails

Possible Causes:

  • Certificate not in PEM format
  • Certificate expired
  • Webhook URL not accessible
  • Server not configured for mTLS

Solutions:

  • Convert certificate to PEM format
  • Renew expired certificate
  • Ensure URL is publicly accessible
  • Configure your server for mTLS:
# Nginx example
ssl_client_certificate /path/to/ca.crt;
ssl_verify_client on;

Issue: Webhooks Not Being Delivered

Possible Causes:

  • Webhook is paused/inactive
  • Endpoint returning errors
  • Firewall blocking requests

Solutions:

  • Check webhook status (should be Active)
  • Check delivery logs for errors
  • Whitelist Nafsi IP addresses
  • Test webhook endpoint manually

Issue: Duplicate Webhook Deliveries

Possible Causes:

  • Endpoint slow to respond
  • Network retries
  • Multiple subscriptions to same event

Solutions:

  • Implement idempotency (check event_id)
  • Respond faster (< 1 second)
  • Check event subscriptions for duplicates

Security Considerations

  1. Always verify signatures - Don't trust unverified webhooks
  2. Use HTTPS only - Never use HTTP
  3. Keep secrets secure - Store webhook secrets in environment variables
  4. Validate payload structure - Don't assume payload format
  5. Rate limit your endpoint - Protect against abuse
  6. Monitor for anomalies - Alert on unusual webhook activity
  7. Rotate secrets periodically - Best practice for production

Next Steps

Support


Last Updated: 2026-01-28