Hey! These docs are for version 1.0, which is no longer officially supported. Click here for the latest version, 1.1!


A large number Modern Treasury's integrations are asynchronous in nature and do not take effect immediately. Primarily this is due to how the underlying systems operate (e.g. file-based messaging over FTP) but it also allows Modern Treasury to efficiently batch its communications to the bank. Fewer files often translates into lower costs and less operational overhead.

To make dealing with these systems easier, the Modern Treasury API is built around the idea of sending important updates over HTTP webhooks. These endpoints can be configured through the settings page inside the application and apply to the entire organization. If needed, separate URLs may be provided for live and test traffic.

Some resources allow the ability to pass a custom webhook_url during creation. This will override the organization-level settings for the created instance.

Technical Details

All endpoints must be configured to receive a HTTP POST with a JSON payload. The webhook system will wait a maximum 5 seconds for a 2XX HTTP status code in order to mark the payload as delivered. If the endpoint takes longer to respond or an error is returned, the webhook will be re-enqueued for delivery at a later time subject to an exponential backoff.

All endpoints are strongly encouraged to use HTTPS as this protects the payload in-transit.


To verify that a webhook was actually sent by Modern Treasury, every payload is signed with a signature that is passed through as the HTTP header X-Signature. The signature is Hex encoded and can be replicated by applying HMAC-SHA-256 to the body of the webhook with your specific webhook key, which can be found in your webhook settings page.

Please contact support if your webhook key is accidentally made public. We will rotate the key and coordinate the change with you.

Webhook Idempotency

Each webhook has a unique ID. This is passed through as the HTTP header X-Webhook-ID. You can save these IDs as you process webhooks to ensure each webhook is only processed once. If a webhook is sent multiple times, its ID will remain the same between requests.

Live vs Test

When making API calls that invoke webhooks, the endpoint URL will depend on whether the live or test key was used. If you want to use the same URL for both live and test webhooks, you can still differentiate using the HTTP header X-Live-Mode which is true for live traffic and false for test traffic.


Each webhook payload contains a topic that describes which category the event belongs to. This is passed through as the HTTP header X-Topic.

balance_reportAny balance report lifecycle event.
external_accountAny external account lifecycle event.
expected_paymentAny expected payment lifecycle event.
paper_itemAny paper item lifecycle event.
payment_orderAny payment order lifecycle event.
returnAny return lifecycle event.
transactionAny transaction lifecycle event

Contents and Structure

All Modern Treasury webhooks have the same top level structure.

  • event specifies what happened to the object.
  • data contains the updated object that changed.
  "event": "event_type",
  "data": {
    // serialized object
  "content-length": "464",
  "x-topic": "paper_item",
  "x-signature": "62d1745dcb53d510963cfb9d3588109903a9ff2cb895ea0f59e5ed38472f6ac8",
  "x-delivery-id": "bee2db7f-5794-47cd-9d98-84453baa5117",
  "content-type": "application/json",
  "x-live-mode": "true",
  "x-internal-signature": "884ee247af1d1d5b0c7122c3e69e1aadf7f83f16c992ea6c7fe557679e7753db",
  "x-organization-id": "<ORGANIZATION_ID>",
  "x-webhook-id": "7b5bc00751e4c9760974ee85",
  "user-agent": "http.rb/4.2.0"
  "event": "created",
  "data": {
    "account_number": "222222223",
    "amount": 9900,
    "check_number": "11",
    "created_at": "2019-12-12T22:34:59Z",
    "currency": "USD",
    "deposit_date": "2019-12-12",
    "id": "9e5c22be-2145-4da4-963f-b0434765d18f",
    "lockbox_number": "12345",
    "memo_field": "Christmas Tip",
    "object": "paper_item",
    "remitter_name": "Cristina Angela",
    "routing_number": "021000021",
    "status": "pending",
    "transaction_id": null,
    "transaction_line_item_id": null,
    "updated_at": "2019-12-12T22:34:59Z"