Developer guide
Statement-bundle webhooks
Configure an HTTPS callback URL on a bundle and OptiTrust will post signed lifecycle events when the bundle or its files change.
Testing access
Discuss non-production testing before integration.
Contact Us to discuss non-production testing and access options.
Contact UsApplication limits
Plan for the current global limiter.
The application currently has an in-process global limiter of 600 requests per minute, partitioned by the remote IP observed by the application. Because it is global, it also applies to authenticated API calls; it is not a dedicated API quota. Requests rejected by the limiter receive HTTP 429.
Proxying and scale-out can affect the effective boundary, so this is not a throughput guarantee. The separate 30-per-minute public policy applies to selected anonymous endpoints and is not the authenticated API limit.
Delivery attempts
Treat every webhook as an idempotent delivery.
Delivery runs as a Hangfire background job. Any non-2xx response fails that delivery attempt. OptiTrust records the failure and rethrows it to Hangfire. Any retries are platform-managed; there is no guaranteed attempt count, backoff schedule, or delivery SLA.
Receivers must make duplicate deliveries harmless by using
X-OptiTrust-Event-Id as the idempotency key.
Bundle responses expose up to the 10 most recent webhook delivery entries, with
dateUtc, status (delivered or failed),
and detail.
Configuration
Set webhookUrl when creating or updating a bundle.
Send an HTTPS URL in the bundle API request. A blank value disables callbacks for that
bundle. When callbacks are enabled, the bundle response includes webhookUrl,
webhookSecret, and recent webhookDeliveries.
{
"idnumber": "8001015009087",
"type": "TransactionExtraction",
"dateRange": "LastThreeMonths",
"proofOfAccount": "NotNeeded",
"webhookUrl": "https://client.example.com/optitrust/webhooks"
}
File upload
Upload files through a server-issued blob target.
Clients do not need shared Azure Blob Storage access. The bundle file endpoint is a
completion call, not a multipart upload. Request an upload target first, upload the PDF
bytes to the returned signed URL, then post the file metadata and uploadToken
back to OptiTrust.
1. Request a one-file upload target.
POST /api/bundles/{identifier}/files/upload-target
XApiKey: <api key>
{
"fileId": "84a60d05-7546-4731-a23a-e41777d891e4",
"uploadUri": "https://storage.example/optitrust-upload/84a60d05-7546-4731-a23a-e41777d891e4?...",
"uploadToken": "<signed token>"
}
2. Upload the PDF bytes directly to the signed URL.
PUT {uploadUri}
x-ms-blob-type: BlockBlob
Content-Type: application/pdf
<PDF bytes>
3. Complete the file attachment in OptiTrust.
POST /api/bundles/{identifier}/files
XApiKey: <api key>
Content-Type: application/json
{
"fileName": "bank-statement.pdf",
"fileId": "84a60d05-7546-4731-a23a-e41777d891e4",
"size": 1024,
"mimeType": "application/pdf",
"uploadToken": "<uploadToken from upload-target>"
}
POST /api/bundles/{identifier}/filesdoes not accept raw PDF bytes.- The
uploadUriis HTTPS-only, create-only, scoped to one server-chosen blob name, and expires after 20 minutes. - The
uploadTokenproves thefileIdwas issued by OptiTrust and expires after 30 minutes. - Only
application/pdffiles up to 10 MB are accepted. - A successful completion returns the updated bundle and sends
bundle.file_addedwhen callbacks are configured.
| Event | When it is sent |
|---|---|
bundle.created |
A bundle is created through the API with callbacks configured. |
bundle.updated |
The bundle details or webhook URL are updated. |
bundle.deleted |
The bundle is soft-deleted. |
bundle.status_changed |
The bundle status changes during review or processing. |
bundle.file_added |
A file is attached to the bundle. |
bundle.file_deleted |
A bundle file is deleted. |
Signature
Verify every delivery before processing it.
X-OptiTrust-Event-Idcontains the idempotency key for this delivery.X-OptiTrust-Eventcontains the event name.X-OptiTrust-Timestampcontains the Unix timestamp in seconds.X-OptiTrust-Signaturecontainssha256=<hex>.
Compute HMAC-SHA256 using the bundle webhookSecret over this UTF-8 string,
then compare it with the signature header using a constant-time comparison:
{timestamp}.{eventId}.{rawJsonBody}
Reject unsigned requests, stale timestamps, and duplicate event IDs. Return any 2xx response only after the payload has been accepted.
Payload
Use eventId for idempotency and inspect the bundle or file object.
Use file.result as the machine-readable screening outcome. The
bundle status describes workflow state only and should not be used
as the anti-fraud verdict.
{
"eventId": "8f55f15d0c1f4b4c96f403edb89bb720",
"event": "bundle.file_added",
"occurredAtUtc": "2026-07-10T08:30:00Z",
"bundle": {
"identifier": "c73c4e02-85c5-4b1b-9ad0-9af82b46ec22",
"status": "Awaiting documents",
"dateAddedUtc": "2026-07-10T08:00:00Z",
"deleted": false,
"fileCount": 2
},
"file": {
"id": 42,
"fileName": "bank-statement.pdf",
"status": "Pending",
"result": {
"processingState": "pending",
"code": "ToBeProcessed",
"label": "To be processed",
"verdict": "pending",
"riskBand": "not_applicable",
"riskScore": null,
"issues": [],
"manuallyApproved": false,
"flaggedForReview": false
},
"deleted": false,
"deletedAtUtc": null
}
}
Receiver guidance
- Store the
webhookSecretsecurely and treat it as a credential. - Use
X-OptiTrust-Event-Idto make processing idempotent. - Use
file.resultfor the screening outcome;bundle.statusis the bundle workflow state. - Fetch the latest bundle from the API if your system needs current state after receiving an event.
- Review
webhookDeliverieson the bundle response when diagnosing delivery failures.