Tenant Webhooks
Receive real-time notifications for tenant-scoped events. Trustist sends HTTP POST payloads to your endpoint so you can react without polling.
Overview
Tenant webhooks currently help you:
- React when identity verification sessions complete
- Update your internal workflow state from webhook events
- Trigger follow-up checks and customer journeys asynchronously
Available Events
| Event | Description | Trigger |
|---|---|---|
onboard.session.completed |
Onboard session has completed | Customer finishes all required onboarding steps |
Note: Payment and standing order webhooks are documented separately in
Payment Webhooks.
Registering a Webhook
Endpoint
POST
/v1/webhooks
Required Permission: manage_webhooks
Backward compatibility: /v1/tenant/webhooks and /v1/onboard/webhooks are also accepted.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | Absolute URL to receive webhook POSTs (HTTPS recommended) |
events |
string[] | No | Subscribed event names. Defaults to ["onboard.session.completed"] when omitted; empty arrays are rejected |
Example Request
{
"url": "https://yourapp.com/webhooks/trustist",
"name": "Production Tenant Webhook",
"events": ["onboard.session.completed"]
}
Example Response
{
"id": "w1X2y3Z4",
"url": "https://yourapp.com/webhooks/trustist",
"name": "Production Tenant Webhook",
"events": ["onboard.session.completed"],
"active": true,
"created": "2025-10-21T10:00:00Z",
"updated": "2025-10-21T10:00:00Z",
"lastDelivered": null,
"deliveryAttempts": 0
}
Webhook Payload Structure
Current payload shape for onboard.session.completed (note the PascalCase property names):
{
"Event": "onboard.session.completed",
"Timestamp": "2025-10-21T11:00:00Z",
"Session": {
"Id": "12345678-89ab-cdef-0123-456789abcdef",
"Status": "Complete",
"Progress": {
"Identity": {
"Completed": true,
"Confirmed": true,
"CompletedAt": "2025-10-21T10:55:00Z"
},
"Ais": {
"Completed": false,
"Confirmed": null,
"CompletedAt": null
}
},
"Created": "2025-10-21T10:30:00Z",
"Updated": "2025-10-21T10:59:00Z"
}
}
Handling Webhooks
Implementation Requirements
- Accept HTTP POST requests
- Return a 2xx HTTP status code quickly
- Keep endpoint publicly reachable
- Process webhook work asynchronously
- Implement idempotency for duplicate deliveries
Delivery behavior: tenant webhook delivery is currently attempted once per event per active endpoint.
Failed attempts increment
deliveryAttempts; there is no automatic disable threshold in this service.
Example Implementation (Node.js/Express)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/trustist', async (req, res) => {
res.status(200).send('OK');
processWebhookAsync(req.body).catch(err => {
console.error('Webhook processing failed:', err);
});
});
async function processWebhookAsync(payload) {
const { Event, Session } = payload;
if (Event !== 'onboard.session.completed') {
console.warn(`Unknown event type: ${Event}`);
return;
}
await handleSessionCompleted(Session);
}
async function handleSessionCompleted(session) {
const { Id, Status, Progress } = session;
await db.onboardSessions.upsert({
sessionId: Id,
status: Status,
identityCompleted: Progress?.Identity?.Completed ?? false,
updatedAt: new Date()
});
// Follow-up pattern: fetch detailed checks after completion.
await queueFetchResults(Id);
}
app.listen(3000);
Example Implementation (C# ASP.NET Core)
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("webhooks/trustist")]
public class TrustistWebhookController : ControllerBase
{
[HttpPost]
public IActionResult HandleWebhook([FromBody] WebhookPayload payload)
{
_ = ProcessWebhookAsync(payload);
return Ok();
}
private async Task ProcessWebhookAsync(WebhookPayload payload)
{
if (payload.Event != "onboard.session.completed")
{
return;
}
await _sessionStore.UpsertAsync(new SessionRecord
{
SessionId = payload.Session.Id,
Status = payload.Session.Status,
IdentityCompleted = payload.Session.Progress?.Identity?.Completed ?? false,
Updated = DateTime.UtcNow
});
await _resultsQueue.EnqueueAsync(payload.Session.Id);
}
}
Testing Webhooks
Local Development with ngrok
# Install ngrok
npm install -g ngrok
# Start your local server (e.g., port 3000)
node server.js
# Create tunnel in another terminal
ngrok http 3000
# Register the ngrok HTTPS URL as your webhook endpoint
Inspect Payloads
- Use webhook.site to generate a temporary endpoint
- Register that URL via
POST /v1/webhooks - Complete a session
- Inspect the payload and status code response
Managing Webhooks
List Webhooks
GET
/v1/webhooks
Delete a Webhook
DELETE
/v1/webhooks/{webhookId}
Deletion is a soft delete (active = false).
Troubleshooting
Webhook Not Receiving Events
- Check endpoint URL is public and reachable
- Check endpoint returns 2xx status
- Check webhook is active
- Check subscription includes
onboard.session.completed
Delivery Failures
- Inspect
deliveryAttemptsandlastDeliveryfrom list response - Review endpoint logs for request/response details
- Fix endpoint behavior and keep webhook active or recreate if needed
Duplicate Events
Design handlers to be idempotent and deduplicate by event/session identity.
async function handleSessionCompleted(session) {
const existing = await db.processedWebhooks.findOne({ sessionId: session.Id });
if (existing) return;
await processSession(session);
await db.processedWebhooks.create({ sessionId: session.Id, processedAt: new Date() });
}
Security Best Practices
- Use HTTPS endpoints in production
- Validate payload shape before processing
- Acknowledge quickly and queue heavy work
- Log receipts and failures for auditability
Future enhancement: webhook signature verification is planned to support payload authenticity checks.