ZenLedger · AsyncAPI Specification
Zenledger Compliance Webhooks
Version
View Spec
View on GitHub
Crypto TaxDigital AssetsTax ComplianceBlockchain AnalyticsRegTechAccountingSanctions ScreeningFinancial ServicesPortfolio AggregationCryptocurrencyAsyncAPIEvents
AsyncAPI Specification
generated: '2026-09-05'
method: searched
source: https://docs.zenledger.io/compliance/v3/compliance_api.postman_collection.json
docs: https://docs.zenledger.io/compliance/v3/
api: ZenLedger Compliance Suite API
asyncapi_published: false
asyncapi_note: >-
ZenLedger publishes no AsyncAPI document. Probed 2026-09-05: no /asyncapi.yaml, /asyncapi.json or event catalog
on api.zenledger.io or docs.zenledger.io, and nothing in github.com/zenledger-io. The event surface below is the
webhook catalog as ZenLedger documents it, in the "Webhooks and notifications" folder of the v3 Postman
collection. It is transcribed, not converted — no AsyncAPI has been authored on the provider's behalf.
transport: HTTPS POST to a customer-registered endpoint
direction: provider-to-customer
subscription_model: >-
Endpoints are registered per Company, not per subscription and not through a webhook-management API. The URL is
set with the `import_notification_url` (import events) or `wallet_screening_notification_url` (screening events)
attribute on POST /companies, or updated later with PUT /companies/{company_reference}. There is no list, test,
rotate, or delete-subscription operation, and no way to enumerate what is currently registered other than reading
the company record back.
delivery:
expected_response: HTTP 200
retry_policy: >-
If the endpoint answers anything other than 200, or does not answer, ZenLedger retries after 1 minute, 3
minutes, 10 minutes, 30 minutes, 1 hour, and a final retry after 24 hours, then stops.
retry_schedule: [1m, 3m, 10m, 30m, 1h, 24h]
max_attempts: 7
ordering_guarantee: not-documented
at_least_once: true
signature_verification: none-documented
signature_note: >-
ZenLedger documents no webhook signature, shared secret, HMAC header or timestamp on inbound notifications, and
no source IP allowlist. A receiver has no published way to verify that a POST to its notification URL came from
ZenLedger — which matters because the payload carries sanctions-screening verdicts. This is the most consequential
documentation gap on this record.
replay_endpoint: none
event_count: 3
events:
- name: IMPORT_STATUS_UPDATE (complete)
notification_type: IMPORT_STATUS_UPDATE
trigger: A user's newly imported account has finished processing.
endpoint_attribute: import_notification_url
enabled_by_default: true
payload_fields:
- {name: notification_type, type: string, example: IMPORT_STATUS_UPDATE}
- {name: company_reference, type: string}
- {name: user_id, type: string}
- {name: source_id, type: string}
- {name: source_reference, type: string, example: coinbase}
- {name: import_status, type: string, example: complete}
- {name: transaction_count, type: integer, example: 7235}
example: |
{
"notification_type": "IMPORT_STATUS_UPDATE",
"company_reference": "company_reference",
"user_id": "7be47a6d-a1b7-43bf-a1b7f5b6-2973cd5e0859",
"source_id": "2973cd5e0859-a1b7-43bf-7be47a6d-97359cd5e082",
"source_reference": "coinbase",
"import_status": "complete",
"transaction_count": 7235
}
- name: IMPORT_STATUS_UPDATE (limit-reached)
notification_type: IMPORT_STATUS_UPDATE
trigger: >-
An import hit the configured transaction import limit (default 100,000, set on the Company or inherited from the
Enterprise). ZenLedger stops the import for that account and sends this instead of the "complete" notification.
endpoint_attribute: import_notification_url
same_endpoint_as: IMPORT_STATUS_UPDATE (complete)
discriminator: import_status == "limit-reached"
recovery: >-
GET /compliance/api/v3/companies/{company_reference}/holdings/{source_id}/resume lifts the limit for that
account and re-triggers the import; already-imported transactions are de-duplicated. A standard "complete"
notification follows when it finishes.
payload_fields:
- {name: notification_type, type: string}
- {name: company_reference, type: string}
- {name: user_id, type: string}
- {name: source_id, type: string}
- {name: source_reference, type: string}
- {name: import_status, type: string, example: limit-reached}
- {name: transaction_count, type: integer}
- {name: transaction_limit, type: integer, note: 'Only present on this variant; the configured cap.'}
example: |
{
"notification_type": "IMPORT_STATUS_UPDATE",
"company_reference": "company_reference",
"user_id": "7be47a6d-a1b7-43bf-a1b7f5b6-2973cd5e0859",
"source_id": "2973cd5e0859-a1b7-43bf-7be47a6d-97359cd5e082",
"source_reference": "coinbase",
"import_status": "limit-reached",
"transaction_count": 1000,
"transaction_limit": 1000
}
consumer_warning: >-
Both variants share one notification_type and one endpoint. A consumer that switches on notification_type alone
will treat a stopped, incomplete import as a completed one. The discriminator is import_status.
- name: ADDRESS_SCREENING_REPORT
notification_type: ADDRESS_SCREENING_REPORT
trigger: Sent before an import begins, carrying the sanctions/risk screening verdict for the imported address.
endpoint_attribute: wallet_screening_notification_url
enabled_by_default: false
availability: >-
Only if the Wallet Screening feature is enabled for the Enterprise (contact ZenLedger) and a destination
endpoint is set on the Company.
payload_fields:
- {name: notification_type, type: string, example: ADDRESS_SCREENING_REPORT}
- {name: company_reference, type: string}
- {name: user_id, type: string}
- {name: screening_status, type: string, enum: [filed, clean]}
- {name: screening_report, type: array}
- {name: 'screening_report[].blockchain', type: string, example: ETH}
- {name: 'screening_report[].address', type: string}
- {name: 'screening_report[].report_data', type: array, note: 'Objects with a description explaining the risk indicator, or stating that none was triggered.'}
- {name: 'screening_report[].owner_info', type: 'array|null', note: 'Attributed owner (name, url, legal_name) when known; null when clean.'}
status_semantics:
filed: The address matched a screening list — the published example cites the US Treasury OFAC sanction list.
clean: No risk indicator was triggered for this address.
example_sanctioned: |
{
"notification_type": "ADDRESS_SCREENING_REPORT",
"company_reference": "CompanyReference",
"user_id": "83b9c1ee-9df5-48e0-ac46-5ceec42a8a76",
"screening_status": "filed",
"screening_report": [
{
"blockchain": "ETH",
"address": "0x1da5821544e25c636c1417ba96ade4cf6d2f9b5a",
"report_data": [
{ "description": "The address listed on the US Treasury Department's Office of Foreign Assets Control sanction list." }
],
"owner_info": [
{ "owner_info": [ { "name": "Bittrex.com", "url": "https://www.Bittrex.com", "legal_name": "Bittrex Global GmbH" } ] }
]
}
]
}
example_clean: |
{
"notification_type": "ADDRESS_SCREENING_REPORT",
"company_reference": "CompanyReference",
"user_id": "83b9c1ee-9df5-48e0-ac46-5ceec42a8a76",
"screening_status": "clean",
"screening_report": [
{
"blockchain": "ETH",
"address": "0x6ee1362b3d16d94d0afe122b0000f1c16f482d72",
"report_data": [ { "description": "No risk indicator has been triggered for this address" } ],
"owner_info": null
}
]
}
shape_note: >-
owner_info is a doubly-nested array in the published example — an array of objects each carrying its own
`owner_info` array. Recorded as documented.
related_polling_alternative:
note: >-
Import state is also readable synchronously — each source in a holdings response carries a `status` field, with
the values documented in the Import Status Glossary. A consumer that cannot accept an unsigned inbound webhook
can poll GET .../holdings instead.
Work with this as data
Every AsyncAPI spec here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for asyncapi
4 MCP tools reach this
find_asyncapisBrowse and filter every AsyncAPI spec in the catalog.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This AsyncAPI spec
curl "https://apis.io/api/v1/asyncapis/zenledger-compliance-webhooks"
All asyncapi
curl "https://apis.io/api/v1/asyncapis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.