Cross-Listing
Cross-list securities between Liquidity.io and external ATS operators, broker-dealers, and transfer agents
Liquidity.io supports cross-listing of private securities with external broker-dealers and ATS operators. This specification defines the webhook contract for bidirectional event delivery between Liquidity.io and partner platforms.
This specification defines the webhook contract for bidirectional event delivery between Liquidity.io and any partner platform.
Webhook Delivery
Transport
All webhook events are delivered as HTTP POST requests to a registered endpoint URL. Both sides register webhook endpoints during onboarding.
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Timeout | 30 seconds |
| TLS | Required (HTTPS only) |
Authentication
Every webhook request includes an HMAC-SHA256 signature in the
X-Webhook-Signature header. The signature is computed over the raw
request body using a shared secret exchanged during onboarding.
X-Webhook-Signature: sha256=<hex-encoded HMAC-SHA256>
X-Webhook-Timestamp: 1711122720The signature covers {timestamp}.{body} to prevent replay attacks.
Receivers MUST reject requests where the timestamp is older than 5 minutes.
Verification (Go)
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"math"
"net/http"
"strconv"
"time"
)
func VerifySignature(r *http.Request, body []byte, secret string) error {
sig := r.Header.Get("X-Webhook-Signature")
ts := r.Header.Get("X-Webhook-Timestamp")
if sig == "" || ts == "" {
return fmt.Errorf("missing signature or timestamp header")
}
// Check timestamp freshness (5 minute window)
tsInt, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
return fmt.Errorf("invalid timestamp: %w", err)
}
if math.Abs(float64(time.Now().Unix()-tsInt)) > 300 {
return fmt.Errorf("timestamp too old")
}
// Compute expected signature
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(fmt.Sprintf("%s.%s", ts, body)))
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(sig), []byte(expected)) {
return fmt.Errorf("signature mismatch")
}
return nil
}Verification (TypeScript)
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhookSignature(
body: string,
signature: string,
timestamp: string,
secret: string,
): boolean {
// Check timestamp freshness (5 minute window)
const ts = parseInt(timestamp, 10);
if (Math.abs(Date.now() / 1000 - ts) > 300) {
return false;
}
const expected =
'sha256=' +
createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}Retry Policy
Failed deliveries (non-2xx response or timeout) are retried with exponential backoff.
| Attempt | Delay | Total Elapsed |
|---|---|---|
| 1 (initial) | 0s | 0s |
| 2 (first retry) | 10s | 10s |
| 3 (second retry) | 60s | 70s |
| 4 (third retry) | 300s | 370s |
After 4 attempts, the event is moved to a dead-letter queue. Partners can query the dead-letter queue via the REST API.
Idempotency
Every event includes a unique event_id. Receivers MUST deduplicate events
by event_id. Liquidity.io guarantees at-least-once delivery.
Envelope Format
All webhook events share the same envelope structure.
{
"webhook_event": {
"event_id": "evt_trade_corp_001",
"event_type": "trade.executed",
"endpoint": "POST /v1/webhooks/trade",
"timestamp": "2026-03-22T14:32:00.000Z",
"version": "2.0.0",
"transaction_type": "trade",
"blockchain_transaction_id": "0x3a9f...",
"recipients": [
{
"recipient_id": "rec_001",
"name": "Buyer BD Inc.",
"role": "buyer_broker_dealer",
"delivered_at": "2026-03-22T14:32:01.123Z",
"delivery_status": "delivered"
}
]
},
"transaction": { ... },
"buyer": { ... },
"seller": { ... },
"transfer_agent": { ... }
}| Field | Type | Description |
|---|---|---|
webhook_event.event_id | string | Unique event ID for idempotency |
webhook_event.event_type | string | Event type (see sections below) |
webhook_event.endpoint | string | The endpoint that generated this event |
webhook_event.timestamp | ISO 8601 | When the event was generated |
webhook_event.version | string | API version (2.0.0) |
webhook_event.transaction_type | string | trade, dividend, return_of_capital |
webhook_event.blockchain_transaction_id | string | On-chain transaction hash (null if off-chain) |
webhook_event.recipients | array | Delivery status per recipient |
Events Sent TO Partners
These events are sent from Liquidity.io to the partner platform (partner) when activity occurs on the Liquidity.io ATS.
trade.executed
Sent when a trade is completed on the ATS. This is the primary event for cross-listed securities.
Trigger: ATS matches an order involving a cross-listed security.
{
"webhook_event": {
"event_id": "evt_trade_corp_001",
"event_type": "trade.executed",
"endpoint": "POST /v1/webhooks/trade",
"timestamp": "2026-03-22T14:32:00.000Z",
"version": "2.0.0",
"transaction_type": "trade",
"blockchain_transaction_id": "0x3a9f2c1d8e4b7056af1c2e3d4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
"recipients": [
{
"recipient_id": "rec_001",
"name": "Buyer BD Inc.",
"role": "buyer_broker_dealer",
"delivered_at": "2026-03-22T14:32:01.123Z",
"delivery_status": "delivered"
},
{
"recipient_id": "rec_002",
"name": "Seller BD Corp.",
"role": "seller_broker_dealer",
"delivered_at": "2026-03-22T14:32:01.456Z",
"delivery_status": "delivered"
},
{
"recipient_id": "rec_003",
"name": "Acme Transfer Agent Co.",
"role": "transfer_agent",
"delivered_at": "2026-03-22T14:32:01.789Z",
"delivery_status": "delivered"
}
]
},
"transaction": {
"transaction_id": "txn_corp_1a2b3c4d",
"transaction_type": "secondary_market_transfer",
"status": "pending_compliance_clearance",
"initiated_at": "2026-03-22T14:30:00.000Z",
"description": "Secondary market transfer of Series B Preferred Stock",
"security": {
"asset_id": "asset_corp_7g8h",
"asset_name": "Example Issuer Inc. — Series B Preferred Stock",
"asset_type": "private_security",
"security_class": "Preferred Stock",
"share_class": "Series B",
"cusip": null,
"isin": null,
"issuer_id": "iss_example_issuer_0001",
"issuer_name": "Example Issuer Inc.",
"issuer_type": "Private Corporation",
"number_of_shares": 1000,
"price_per_share": 45.00,
"currency": "USD",
"gross_trade_amount": 45000.00,
"accrued_interest": 0.00,
"commissions": {
"buyer_broker_dealer": {
"firm_name": "Buyer BD Inc.",
"crd_number": "705",
"commission_type": "flat_fee",
"commission_rate": null,
"commission_amount": 112.50,
"currency": "USD"
},
"seller_broker_dealer": {
"firm_name": "Seller BD Corp.",
"crd_number": "250",
"commission_type": "flat_fee",
"commission_rate": null,
"commission_amount": 112.50,
"currency": "USD"
},
"total_commissions": 225.00
},
"net_trade_amount": 45225.00,
"trade_execution_datetime": "2026-03-22T14:30:00.000Z",
"price_determination_method": "negotiated",
"bid_price": 44.50,
"ask_price": 45.50,
"last_valuation_price": 44.00,
"last_valuation_date": "2026-01-15"
},
"settlement": {
"settlement_date": "2026-03-24T00:00:00.000Z",
"settlement_type": "bilateral",
"settlement_status": "pending",
"settlement_currency": "USD"
},
"restrictions": {
"legend_required": true,
"rule_144_holding_period_met": false,
"transfer_restrictions": "Subject to issuer consent, right of first refusal, and applicable securities laws",
"lock_up_expiry_date": null
}
},
"buyer": {
"investor_id": "inv_entity_meridian_growth_corp_0001",
"account_id": "acct_buyer_corp_aa1b2c3d",
"account_type": "entity",
"broker_dealer": {
"firm_name": "Buyer BD Inc.",
"crd_number": "705",
"finra_member": true
},
"compliance_ref": {
"endpoint": "GET /v1/investors/inv_entity_meridian_growth_corp_0001/compliance",
"description": "Fetch full KYC, KYB, accreditation, PEPs & Sanctions, adverse media, and transaction monitoring data for this investor."
}
},
"seller": {
"investor_id": "inv_indv_patricia_nguyen_0099",
"account_id": "acct_seller_indv_corp",
"account_type": "individual",
"broker_dealer": {
"firm_name": "Seller BD Corp.",
"crd_number": "250",
"finra_member": true
},
"compliance_ref": {
"endpoint": "GET /v1/investors/inv_indv_patricia_nguyen_0099/compliance",
"description": "Fetch full KYC, KYB, accreditation, PEPs & Sanctions, adverse media, and transaction monitoring data for this investor."
}
},
"transfer_agent": {
"firm_name": "Acme Transfer Agent Co.",
"sec_registered": true,
"sec_registration_number": "84-01234",
"acknowledgment": {
"acknowledged": true,
"acknowledged_at": "2026-03-22T14:32:05.000Z",
"transfer_instruction_id": "ti_corp_aa1b2c3d",
"record_date": "2026-03-22T00:00:00.000Z",
"units_to_transfer": 1000,
"status": "pending_transfer"
}
}
}Issuer Types
The payload shape is identical across all issuer types. The issuer_type
and security_class fields vary.
| Issuer Type | issuer_type | Typical security_class |
|---|---|---|
| Corporation | Private Corporation | Common Stock, Preferred Stock |
| LLC | Limited Liability Company | Membership Units |
| Limited Partnership | Limited Partnership | Limited Partnership Units |
| Trust | Private Partnership | Common Shares |
| SPV | Special Purpose Vehicle | Membership Interests |
| Public Company | Public Corporation | Common Stock, Preferred Units |
| Sole Proprietorship | Limited Liability Company | Membership Units |
Commission Types
| Type | commission_type | commission_rate | commission_amount |
|---|---|---|---|
| Flat fee | flat_fee | null | Fixed dollar amount |
| Percentage | percentage_of_gross | Decimal (e.g., 0.005 = 0.5%) | Computed amount |
settlement.recorded
Sent when a settlement transaction is recorded on-chain.
Trigger: MPC signs and submits the settlement transaction to the SettlementRegistry contract.
{
"webhook_event": {
"event_id": "evt_settle_rec_001",
"event_type": "settlement.recorded",
"endpoint": "POST /v1/webhooks/settlement",
"timestamp": "2026-03-22T15:00:00.000Z",
"version": "2.0.0",
"transaction_type": "trade",
"blockchain_transaction_id": "0xabc123..."
},
"transaction": {
"transaction_id": "txn_corp_1a2b3c4d",
"settlement": {
"settlement_id": "stl_001",
"settlement_date": "2026-03-24T00:00:00.000Z",
"settlement_type": "bilateral",
"settlement_status": "recorded",
"settlement_currency": "USD",
"on_chain_tx_hash": "0xabc123...",
"block_number": 12345678,
"chain_id": 8675311
}
}
}settlement.finalized
Sent when a settlement status changes to a terminal state.
Trigger: Settlement transitions to SETTLED, FAILED, or REVERSED.
{
"webhook_event": {
"event_id": "evt_settle_fin_001",
"event_type": "settlement.finalized",
"endpoint": "POST /v1/webhooks/settlement",
"timestamp": "2026-03-24T16:00:00.000Z",
"version": "2.0.0",
"transaction_type": "trade",
"blockchain_transaction_id": "0xabc123..."
},
"transaction": {
"transaction_id": "txn_corp_1a2b3c4d",
"settlement": {
"settlement_id": "stl_001",
"settlement_date": "2026-03-24T00:00:00.000Z",
"settlement_type": "bilateral",
"settlement_status": "settled",
"settlement_currency": "USD",
"on_chain_tx_hash": "0xabc123...",
"block_number": 12345678,
"chain_id": 8675311,
"finalized_at": "2026-03-24T16:00:00.000Z"
}
}
}| Status | Meaning |
|---|---|
settled | Transfer complete, cap table updated, tokens minted/burned |
failed | Settlement could not be completed (compliance block, chain error) |
reversed | Previously settled trade reversed (regulatory action, error correction) |
transfer.initiated
Sent when the TA creates a transfer instruction.
Trigger: ATS notifies TA to record a transfer after settlement.
{
"webhook_event": {
"event_id": "evt_transfer_init_001",
"event_type": "transfer.initiated",
"endpoint": "POST /v1/webhooks/transfer",
"timestamp": "2026-03-24T16:01:00.000Z",
"version": "2.0.0",
"transaction_type": "trade",
"blockchain_transaction_id": null
},
"transaction": {
"transaction_id": "txn_corp_1a2b3c4d",
"transfer": {
"transfer_instruction_id": "ti_corp_aa1b2c3d",
"status": "initiated",
"from_investor_id": "inv_indv_patricia_nguyen_0099",
"to_investor_id": "inv_entity_meridian_growth_corp_0001",
"asset_id": "asset_corp_7g8h",
"quantity": 1000,
"record_date": "2026-03-22T00:00:00.000Z",
"initiated_at": "2026-03-24T16:01:00.000Z"
}
},
"transfer_agent": {
"firm_name": "Acme Transfer Agent Co.",
"sec_registered": true,
"sec_registration_number": "84-01234"
}
}transfer.completed
Sent when the TA completes the transfer on the cap table.
Trigger: TA records the ownership change in the shareholder registry.
{
"webhook_event": {
"event_id": "evt_transfer_comp_001",
"event_type": "transfer.completed",
"endpoint": "POST /v1/webhooks/transfer",
"timestamp": "2026-03-24T16:05:00.000Z",
"version": "2.0.0",
"transaction_type": "trade",
"blockchain_transaction_id": "0xdef456..."
},
"transaction": {
"transaction_id": "txn_corp_1a2b3c4d",
"transfer": {
"transfer_instruction_id": "ti_corp_aa1b2c3d",
"status": "completed",
"from_investor_id": "inv_indv_patricia_nguyen_0099",
"to_investor_id": "inv_entity_meridian_growth_corp_0001",
"asset_id": "asset_corp_7g8h",
"quantity": 1000,
"record_date": "2026-03-22T00:00:00.000Z",
"initiated_at": "2026-03-24T16:01:00.000Z",
"completed_at": "2026-03-24T16:05:00.000Z",
"on_chain_tx_hash": "0xdef456..."
}
},
"transfer_agent": {
"firm_name": "Acme Transfer Agent Co.",
"sec_registered": true,
"sec_registration_number": "84-01234"
}
}dividend.declared
Sent when an issuer declares a dividend.
Trigger: TA records a dividend declaration.
{
"webhook_event": {
"event_id": "evt_div_decl_001",
"event_type": "dividend.declared",
"endpoint": "POST /v1/webhooks/corporate-action",
"timestamp": "2026-04-01T09:00:00.000Z",
"version": "2.0.0",
"transaction_type": "dividend",
"blockchain_transaction_id": null
},
"transaction": {
"transaction_id": "txn_div_001",
"transaction_type": "dividend",
"corporate_action": {
"action_id": "ca_div_001",
"action_type": "dividend",
"asset_id": "asset_corp_7g8h",
"issuer_id": "iss_example_issuer_0001",
"issuer_name": "Example Issuer Inc.",
"declaration_date": "2026-04-01",
"record_date": "2026-04-15",
"payment_date": "2026-04-30",
"dividend_per_share": 1.25,
"currency": "USD",
"dividend_type": "cash",
"total_distribution": 125000.00,
"eligible_shares": 100000
}
}
}corporate_action.announced
Sent when a corporate action (stock split, merger, conversion, etc.) is announced.
Trigger: TA records a corporate action.
{
"webhook_event": {
"event_id": "evt_ca_001",
"event_type": "corporate_action.announced",
"endpoint": "POST /v1/webhooks/corporate-action",
"timestamp": "2026-05-01T09:00:00.000Z",
"version": "2.0.0",
"transaction_type": "corporate_action",
"blockchain_transaction_id": null
},
"transaction": {
"transaction_id": "txn_ca_001",
"transaction_type": "corporate_action",
"corporate_action": {
"action_id": "ca_split_001",
"action_type": "stock_split",
"asset_id": "asset_corp_7g8h",
"issuer_id": "iss_example_issuer_0001",
"issuer_name": "Example Issuer Inc.",
"announcement_date": "2026-05-01",
"effective_date": "2026-05-15",
"record_date": "2026-05-10",
"description": "2-for-1 stock split of Series B Preferred Stock",
"split_ratio": "2:1",
"details": {
"old_shares_outstanding": 100000,
"new_shares_outstanding": 200000,
"price_adjustment_factor": 0.5
}
}
}
}| Action Type | action_type | Key Fields |
|---|---|---|
| Stock split | stock_split | split_ratio, price_adjustment_factor |
| Reverse split | reverse_split | split_ratio, price_adjustment_factor |
| Merger | merger | acquiring_entity, conversion_ratio |
| Conversion | conversion | from_security_class, to_security_class, conversion_ratio |
| Name change | name_change | old_name, new_name |
| Liquidation | liquidation | distribution_per_share |
Events Received FROM Partners
These events are sent from the partner platform (partner) to Liquidity.io when activity occurs on the partner's side.
order.placed
Received when a partner's investor places an order for a cross-listed security.
Expected action: ATS validates the order, runs pre-trade compliance, and enters it into the order book.
{
"webhook_event": {
"event_id": "evt_partner_order_001",
"event_type": "order.placed",
"timestamp": "2026-03-22T14:00:00.000Z",
"version": "2.0.0"
},
"order": {
"external_order_id": "tz_ord_abc123",
"asset_id": "asset_corp_7g8h",
"side": "buy",
"order_type": "limit",
"quantity": 500,
"price": 45.00,
"currency": "USD",
"time_in_force": "GTC",
"investor": {
"investor_id": "tz_inv_001",
"account_type": "entity",
"broker_dealer": {
"firm_name": "Partner ATS LLC",
"crd_number": "169058",
"finra_member": true
}
}
}
}Response (synchronous):
{
"status": "accepted",
"order_id": "ord_liq_xyz789",
"external_order_id": "tz_ord_abc123"
}Or rejection:
{
"status": "rejected",
"external_order_id": "tz_ord_abc123",
"reason": "COMPLIANCE_REJECTED",
"message": "Investor accreditation not verified for Reg D 506c offering"
}order.cancelled
Received when a partner requests cancellation of a previously placed order.
Expected action: ATS cancels the order if it is still open.
{
"webhook_event": {
"event_id": "evt_partner_cancel_001",
"event_type": "order.cancelled",
"timestamp": "2026-03-22T14:10:00.000Z",
"version": "2.0.0"
},
"cancellation": {
"external_order_id": "tz_ord_abc123",
"order_id": "ord_liq_xyz789",
"reason": "investor_requested"
}
}Response:
{
"status": "cancelled",
"order_id": "ord_liq_xyz789",
"cancelled_quantity": 500,
"filled_quantity": 0
}kyc.completed
Received when a partner completes KYC verification for an investor who will trade cross-listed securities on Liquidity.io.
Expected action: BD records the KYC status for the investor. If the investor already has a Liquidity.io account, the compliance record is updated. If not, a shadow account is created.
{
"webhook_event": {
"event_id": "evt_partner_kyc_001",
"event_type": "kyc.completed",
"timestamp": "2026-03-22T10:00:00.000Z",
"version": "2.0.0"
},
"investor": {
"external_investor_id": "tz_inv_001",
"kyc_status": "approved",
"kyc_provider": "Partner KYC Provider",
"kyc_completed_at": "2026-03-22T10:00:00.000Z",
"accreditation_status": "verified",
"accreditation_type": "506c",
"accreditation_verified_at": "2026-03-22T10:00:00.000Z",
"broker_dealer": {
"firm_name": "Partner ATS LLC",
"crd_number": "169058",
"finra_member": true
}
}
}account.opened
Received when a partner approves a new investor account that will trade cross-listed securities.
Expected action: BD creates a shadow investor record on Liquidity.io with the partner's KYC attestation.
{
"webhook_event": {
"event_id": "evt_partner_acct_001",
"event_type": "account.opened",
"timestamp": "2026-03-22T09:00:00.000Z",
"version": "2.0.0"
},
"account": {
"external_account_id": "tz_acct_001",
"external_investor_id": "tz_inv_001",
"account_type": "entity",
"broker_dealer": {
"firm_name": "Partner ATS LLC",
"crd_number": "169058",
"finra_member": true
},
"entity": {
"legal_name": "Meridian Growth Corp.",
"entity_type": "corporation",
"ein": "12-3456789",
"jurisdiction": "US-DE"
}
}
}Webhook Registration
Partners register webhook endpoints via the Liquidity.io API.
Register Endpoint
POST /v1/webhooks/endpoints
Authorization: Bearer <partner-iam-token>{
"url": "https://partner.example.com/webhooks/liquidity",
"events": [
"trade.executed",
"settlement.recorded",
"settlement.finalized",
"transfer.initiated",
"transfer.completed",
"dividend.declared",
"corporate_action.announced"
],
"secret": "whsec_..."
}Response:
{
"endpoint_id": "ep_001",
"url": "https://partner.example.com/webhooks/liquidity",
"events": ["trade.executed", "settlement.recorded", "..."],
"status": "active",
"created_at": "2026-03-22T00:00:00.000Z"
}List Endpoints
GET /v1/webhooks/endpoints
Authorization: Bearer <partner-iam-token>Delete Endpoint
DELETE /v1/webhooks/endpoints/{endpoint_id}
Authorization: Bearer <partner-iam-token>Query Dead-Letter Queue
GET /v1/webhooks/dead-letter?endpoint_id=ep_001&since=2026-03-22T00:00:00Z
Authorization: Bearer <partner-iam-token>Replay Event
POST /v1/webhooks/replay
Authorization: Bearer <partner-iam-token>{
"event_id": "evt_trade_corp_001",
"endpoint_id": "ep_001"
}Event Summary
Outbound (Liquidity.io to Partner)
| Event | Trigger | Payload Key Objects |
|---|---|---|
trade.executed | Order matched on ATS | transaction, buyer, seller, transfer_agent |
settlement.recorded | On-chain settlement submitted | transaction.settlement |
settlement.finalized | Settlement reaches terminal state | transaction.settlement |
transfer.initiated | TA creates transfer instruction | transaction.transfer, transfer_agent |
transfer.completed | TA records ownership change | transaction.transfer, transfer_agent |
dividend.declared | Issuer declares dividend | transaction.corporate_action |
corporate_action.announced | Corporate action recorded | transaction.corporate_action |
Inbound (Partner to Liquidity.io)
| Event | Trigger | Expected Response |
|---|---|---|
order.placed | Partner investor places order | accepted or rejected with reason |
order.cancelled | Partner requests cancellation | cancelled with fill details |
kyc.completed | Partner completes investor KYC | acknowledged |
account.opened | Partner approves investor account | acknowledged with shadow account ID |
Versioning
The version field in the webhook envelope indicates the payload schema
version. Breaking changes increment the major version. Liquidity.io
supports two major versions simultaneously during migration periods.
| Version | Status | End-of-Life |
|---|---|---|
2.0.0 | Current | N/A |
1.0.0 | Deprecated | 2026-12-31 |
Partners specify the desired version when registering a webhook endpoint. The default is the latest version.
Integration Guides
Choose the guide that matches your organization type:
- ATS Integration — For ATS operators who cross-list securities and route orders
- Broker-Dealer Integration — For registered broker-dealers who execute trades and manage investor accounts
- Transfer Agent Integration — For transfer agents who record ownership transfers and manage cap tables