Skip to main content

Webhooks

Webhooks notify your application in real-time when events occur in TrustGate, such as verification completions, screening hits, or case updates.

How Webhooks Work

┌─────────────────────────────────────────────────────────┐
│ TRUSTGATE │
│ │
│ Event occurs (e.g., verification completed) │
└─────────────────────────┬───────────────────────────────┘

│ HTTP POST
v
┌─────────────────────────────────────────────────────────┐
│ YOUR ENDPOINT │
│ │
│ https://your-app.com/webhooks/trustgate │
└─────────────────────────┬───────────────────────────────┘

│ Process event
v
┌─────────────────────────────────────────────────────────┐
│ YOUR APPLICATION │
│ │
│ Update database, notify user, trigger workflow │
└─────────────────────────────────────────────────────────┘

Creating Webhooks

Via API

curl -X POST https://api.bytrustgate.com/v1/webhooks \
-H "Authorization: Bearer tg_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/trustgate",
"events": [
"applicant.verified",
"applicant.rejected",
"screening.hit",
"case.created"
],
"description": "Production webhook"
}'

Response

{
"id": "webhook_abc123",
"url": "https://your-app.com/webhooks/trustgate",
"events": ["applicant.verified", "applicant.rejected", "screening.hit", "case.created"],
"secret": "whsec_your_webhook_secret_here",
"status": "active",
"created_at": "2025-01-20T14:00:00Z"
}

Important: Store the secret securely - it's used to verify webhook signatures.

Webhook Payload

Payload Structure

{
"id": "evt_123456",
"type": "applicant.verified",
"created_at": "2025-01-20T14:30:00Z",
"data": {
"applicant_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "verified",
"verification_level": "standard",
"checks_completed": ["document", "biometric", "screening"]
}
}

Headers

HeaderDescription
X-TrustGate-SignatureHMAC signature for verification
X-TrustGate-EventEvent type
X-TrustGate-DeliveryUnique delivery ID
X-TrustGate-TimestampUnix timestamp

Verifying Signatures

Always verify webhook signatures to ensure authenticity:

Node.js

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const timestamp = signature.split(',')[0].split('=')[1];
const receivedSignature = signature.split(',')[1].split('=')[1];

const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
}

app.post('/webhooks/trustgate', (req, res) => {
const signature = req.headers['x-trustgate-signature'];
const payload = JSON.stringify(req.body);

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

// Process the webhook
const event = req.body;
handleEvent(event);

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

Python

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
parts = dict(part.split('=') for part in signature.split(','))
timestamp = parts['t']
received_signature = parts['v1']

signed_payload = f"{timestamp}.{payload}"
expected_signature = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(received_signature, expected_signature)

@app.route('/webhooks/trustgate', methods=['POST'])
def webhook():
signature = request.headers.get('X-TrustGate-Signature')
payload = request.get_data(as_text=True)

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

event = request.get_json()
handle_event(event)

return 'OK', 200

Handling Events

Event Handler Example

function handleEvent(event) {
switch (event.type) {
case 'applicant.verified':
handleVerified(event.data);
break;
case 'applicant.rejected':
handleRejected(event.data);
break;
case 'screening.hit':
handleScreeningHit(event.data);
break;
case 'case.created':
handleCaseCreated(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}

function handleVerified(data) {
// Update user status in your database
await db.users.update({
where: { applicantId: data.applicant_id },
data: { kycStatus: 'verified' }
});

// Send confirmation email
await sendEmail(data.applicant_id, 'verification_complete');

// Trigger next onboarding step
await activateAccount(data.applicant_id);
}

Retry Policy

Automatic Retries

Failed deliveries are retried automatically:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
66 hours
724 hours

Success Criteria

A webhook is considered successful when your endpoint returns:

  • HTTP status code 2xx
  • Within 30 seconds

Failure Handling

{
"webhook_id": "webhook_abc123",
"delivery_id": "del_xyz789",
"status": "failed",
"attempts": 3,
"last_error": "Connection timeout",
"next_retry_at": "2025-01-20T15:30:00Z"
}

Managing Webhooks

List Webhooks

curl -X GET "https://api.bytrustgate.com/v1/webhooks" \
-H "Authorization: Bearer tg_live_xxx"

Update Webhook

curl -X PATCH "https://api.bytrustgate.com/v1/webhooks/webhook_abc123" \
-H "Authorization: Bearer tg_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"events": ["applicant.verified", "applicant.rejected"],
"status": "active"
}'

Delete Webhook

curl -X DELETE "https://api.bytrustgate.com/v1/webhooks/webhook_abc123" \
-H "Authorization: Bearer tg_live_xxx"

Rotate Secret

curl -X POST "https://api.bytrustgate.com/v1/webhooks/webhook_abc123/rotate-secret" \
-H "Authorization: Bearer tg_live_xxx"

Testing Webhooks

Send Test Event

curl -X POST "https://api.bytrustgate.com/v1/webhooks/webhook_abc123/test" \
-H "Authorization: Bearer tg_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"event_type": "applicant.verified"
}'

View Delivery History

curl -X GET "https://api.bytrustgate.com/v1/webhooks/webhook_abc123/deliveries" \
-H "Authorization: Bearer tg_live_xxx"

Response

{
"deliveries": [
{
"id": "del_001",
"event_type": "applicant.verified",
"status": "delivered",
"response_code": 200,
"response_time_ms": 145,
"delivered_at": "2025-01-20T14:30:00Z"
},
{
"id": "del_002",
"event_type": "screening.hit",
"status": "failed",
"error": "Connection refused",
"attempts": 3,
"last_attempt_at": "2025-01-20T14:35:00Z"
}
]
}

Replay Failed Delivery

curl -X POST "https://api.bytrustgate.com/v1/webhooks/deliveries/del_002/retry" \
-H "Authorization: Bearer tg_live_xxx"

Best Practices

Endpoint Design

  1. Return 200 quickly - Process asynchronously if needed
  2. Handle duplicates - Events may be delivered more than once
  3. Verify signatures - Always validate webhook authenticity
  4. Use HTTPS - Required for production webhooks
  5. Log deliveries - Track received webhooks for debugging

Idempotency

Handle duplicate deliveries gracefully:

app.post('/webhooks/trustgate', async (req, res) => {
const eventId = req.body.id;

// Check if already processed
const existing = await db.webhookEvents.findUnique({
where: { eventId }
});

if (existing) {
return res.status(200).send('Already processed');
}

// Process and record
await processEvent(req.body);
await db.webhookEvents.create({
data: { eventId, processedAt: new Date() }
});

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

Next Steps