ZenLedger · AsyncAPI Specification

Zenledger Compliance Webhooks

Version

View Spec View on GitHub Crypto TaxDigital AssetsTax ComplianceBlockchain AnalyticsRegTechAccountingSanctions ScreeningFinancial ServicesPortfolio AggregationCryptocurrencyAsyncAPIEvents

AsyncAPI Specification

Raw ↑
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.
All 92 tools →

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.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.