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 Us

Application 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}/files does not accept raw PDF bytes.
  • The uploadUri is HTTPS-only, create-only, scoped to one server-chosen blob name, and expires after 20 minutes.
  • The uploadToken proves the fileId was issued by OptiTrust and expires after 30 minutes.
  • Only application/pdf files up to 10 MB are accepted.
  • A successful completion returns the updated bundle and sends bundle.file_added when callbacks are configured.
Statement-bundle webhook events
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-Id contains the idempotency key for this delivery.
  • X-OptiTrust-Event contains the event name.
  • X-OptiTrust-Timestamp contains the Unix timestamp in seconds.
  • X-OptiTrust-Signature contains sha256=<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 webhookSecret securely and treat it as a credential.
  • Use X-OptiTrust-Event-Id to make processing idempotent.
  • Use file.result for the screening outcome; bundle.status is the bundle workflow state.
  • Fetch the latest bundle from the API if your system needs current state after receiving an event.
  • Review webhookDeliveries on the bundle response when diagnosing delivery failures.