Liquidity Docs

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.

PropertyValue
MethodPOST
Content-Typeapplication/json
Timeout30 seconds
TLSRequired (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: 1711122720

The 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.

AttemptDelayTotal Elapsed
1 (initial)0s0s
2 (first retry)10s10s
3 (second retry)60s70s
4 (third retry)300s370s

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": { ... }
}
FieldTypeDescription
webhook_event.event_idstringUnique event ID for idempotency
webhook_event.event_typestringEvent type (see sections below)
webhook_event.endpointstringThe endpoint that generated this event
webhook_event.timestampISO 8601When the event was generated
webhook_event.versionstringAPI version (2.0.0)
webhook_event.transaction_typestringtrade, dividend, return_of_capital
webhook_event.blockchain_transaction_idstringOn-chain transaction hash (null if off-chain)
webhook_event.recipientsarrayDelivery 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 Typeissuer_typeTypical security_class
CorporationPrivate CorporationCommon Stock, Preferred Stock
LLCLimited Liability CompanyMembership Units
Limited PartnershipLimited PartnershipLimited Partnership Units
TrustPrivate PartnershipCommon Shares
SPVSpecial Purpose VehicleMembership Interests
Public CompanyPublic CorporationCommon Stock, Preferred Units
Sole ProprietorshipLimited Liability CompanyMembership Units

Commission Types

Typecommission_typecommission_ratecommission_amount
Flat feeflat_feenullFixed dollar amount
Percentagepercentage_of_grossDecimal (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"
    }
  }
}
StatusMeaning
settledTransfer complete, cap table updated, tokens minted/burned
failedSettlement could not be completed (compliance block, chain error)
reversedPreviously 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 Typeaction_typeKey Fields
Stock splitstock_splitsplit_ratio, price_adjustment_factor
Reverse splitreverse_splitsplit_ratio, price_adjustment_factor
Mergermergeracquiring_entity, conversion_ratio
Conversionconversionfrom_security_class, to_security_class, conversion_ratio
Name changename_changeold_name, new_name
Liquidationliquidationdistribution_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)

EventTriggerPayload Key Objects
trade.executedOrder matched on ATStransaction, buyer, seller, transfer_agent
settlement.recordedOn-chain settlement submittedtransaction.settlement
settlement.finalizedSettlement reaches terminal statetransaction.settlement
transfer.initiatedTA creates transfer instructiontransaction.transfer, transfer_agent
transfer.completedTA records ownership changetransaction.transfer, transfer_agent
dividend.declaredIssuer declares dividendtransaction.corporate_action
corporate_action.announcedCorporate action recordedtransaction.corporate_action

Inbound (Partner to Liquidity.io)

EventTriggerExpected Response
order.placedPartner investor places orderaccepted or rejected with reason
order.cancelledPartner requests cancellationcancelled with fill details
kyc.completedPartner completes investor KYCacknowledged
account.openedPartner approves investor accountacknowledged 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.

VersionStatusEnd-of-Life
2.0.0CurrentN/A
1.0.0Deprecated2026-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:

On this page