Skip to main content

Overview

Webhooks deliver real-time HTTP callbacks when events happen in your Conduit account. Instead of polling the API, register an endpoint and Conduit pushes events to you. Conduit guarantees at-least-once delivery — your endpoint may receive the same event more than once. Clients SHOULD dedup by id and order by createdAt. We do not guarantee strict transport ordering.

Setting Up

1. Create an endpoint

curl -X POST https://api.conduit.financial/v2/webhooks/endpoints \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/conduit",
    "subscription": {
      "mode": "SELECTED",
      "eventTypes": ["application.approved", "application.rejected"]
    }
  }'
The response includes a secret. Save it securely, it is only shown once and cannot be retrieved later. subscription is a tagged union:
  • { "mode": "ALL" } (the default if subscription is omitted) subscribes the endpoint to every event type.
  • { "mode": "SELECTED", "eventTypes": ["..."] } subscribes only to the listed event types. eventTypes must be non-empty.
To change the subscription later, PATCH /v2/webhooks/endpoints/:id with the same subscription shape.

2. Verify signatures

Every webhook request includes a X-Conduit-Signature header in the format:
t=<unix-timestamp>,v1=<hex-hmac>[,v1=<hex-hmac>]
Where each v1 is HMAC-SHA256(<unix-timestamp>.<raw-body>, secret), computed over the raw request bytes before any JSON parsing. A delivery normally carries a single v1. While you are rotating an endpoint’s signing secret, deliveries carry two v1 values for a grace period — one signed with your new secret and one with the previous one — so deliveries keep verifying while you roll your secret over. Verify by recomputing the digest for your secret and accepting the delivery if it matches any v1 value. Once the grace period ends, only the current secret is used. We recommend rejecting deliveries where t is older than 300 seconds to guard against replay attacks.
The whsec_ prefix is part of the HMAC key — do not strip it. Your signing secret is shaped whsec_<64-hex>. Pass the FULL string verbatim, including the whsec_ prefix, as the HMAC-SHA256 key. Stripping the prefix produces a different digest and every valid delivery fails verification — the failure mode is identical to a tampered signature (silent 401, no diagnostic).
Node.js / Bun
import { createHmac, timingSafeEqual } from "node:crypto";

/**
 * Reference Node.js/Bun verifier. Other runtimes: use the equivalent
 * HMAC-SHA256 + constant-time compare. `rawBody` MUST be the raw UTF-8
 * bytes (do not parse JSON first). `t` is unix seconds. `secret` is the
 * full per-endpoint string including the `whsec_` prefix — pass it as-is.
 */
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const pairs = signatureHeader.split(",").map((p) => p.split("="));
  const t = pairs.find(([k]) => k === "t")?.[1];
  // A delivery carries more than one v1 while you rotate the signing secret
  // (the previous secret co-signs during the grace period). Collect every
  // valid v1 and accept if your secret matches any of them. Each v1 must be a
  // 64-char hex string (32 bytes); rejecting anything else protects
  // timingSafeEqual from throwing on a length mismatch.
  const signatures = pairs
    .filter(([k, v]) => k === "v1" && /^[0-9a-f]{64}$/i.test(v ?? ""))
    .map(([, v]) => v);
  if (!t || signatures.length === 0) return false;

  const tNum = Number(t);
  if (!Number.isFinite(tNum)) return false;

  const ageSeconds = Math.floor(Date.now() / 1000) - tNum;
  if (ageSeconds > 300 || ageSeconds < 0) return false;

  // `secret` is the full `whsec_...` string. Do NOT strip the prefix.
  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const expectedBuf = Buffer.from(expected, "hex");

  return signatures.some((v1) =>
    timingSafeEqual(expectedBuf, Buffer.from(v1, "hex")),
  );
}
Python 3
import hmac
import time
from hashlib import sha256

def verify_webhook_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    """
    Reference Python verifier. ``raw_body`` MUST be the raw request bytes
    (do not ``json.loads`` first). ``signature_header`` is the
    ``X-Conduit-Signature`` header value. ``secret`` is the full
    per-endpoint string including the ``whsec_`` prefix — pass it as-is.
    """
    pairs = [p.split("=", 1) for p in signature_header.split(",")]
    t = next((v for k, v in pairs if k == "t"), None)
    # A delivery carries more than one v1 while you rotate the signing secret
    # (the previous secret co-signs during the grace period). Collect every
    # valid v1 and accept if your secret matches any of them. Each v1 must be a
    # 64-char lowercase hex string (32 bytes).
    signatures = [
        v
        for k, v in pairs
        if k == "v1"
        and len(v) == 64
        and all(c in "0123456789abcdef" for c in v.lower())
    ]
    if not t or not signatures:
        return False
    try:
        t_int = int(t)
    except ValueError:
        return False
    age_seconds = int(time.time()) - t_int
    if age_seconds > 300 or age_seconds < 0:
        return False
    # ``secret`` is the full ``whsec_...`` string. Do NOT strip the prefix.
    expected = hmac.new(
        secret.encode("utf-8"),
        f"{t}.{raw_body.decode('utf-8')}".encode("utf-8"),
        sha256,
    ).hexdigest()
    return any(hmac.compare_digest(expected, v1) for v1 in signatures)
Always verify signatures before processing webhook payloads. Reject requests where the signature does not match or the timestamp is stale.

Common verification mistakes

MistakeSymptomFix
Stripping the whsec_ prefix before computing HMACEvery valid delivery returns 401; logs look identical to a tampered requestPass the secret verbatim — the prefix is key material, not a label
Parsing the request body as JSON before computing HMACMost deliveries pass, but any payload where field ordering or whitespace changes failsCompute HMAC over the raw bytes received on the wire, before any deserialization
Comparing digests with ==Subtle timing side-channel; nothing visibly brokenUse crypto.timingSafeEqual (Node) / hmac.compare_digest (Python)
Trusting X-Conduit-Event for routing without verifying the signature firstEndpoint accepts forged events from anyone who can reach the URLVerify the signature before reading any header or body field

3. Rotate your signing secret

If your signing secret is leaked — or you rotate secrets on a schedule — call:
POST /v2/webhooks/endpoints/:id/rotate
The response returns a new secret once, in the same shape as create ({ ...endpoint, "secret": "whsec_...", "signature": { ... } }). Store it immediately; it is never shown again.
This request requires an Idempotency-Key header. Rotation is destructive — it replaces your current secret — so a retried request that reused no key could rotate twice and discard the secret you just deployed. With a key, a retry replays the original response instead of rotating again. Use a fresh key per intentional rotation.
Rotation does not cut over instantly. For a grace period (about 48 hours) every delivery is signed with both the new and the previous secret — two v1 values in X-Conduit-Signature (see Verify signatures). This lets you roll your verification over without dropping events:
  1. Call rotate and store the new secret.
  2. Deploy the new secret to your verifier. Because you accept any matching v1, deliveries keep verifying throughout — under the old secret before you deploy, under the new one after.
  3. Once the grace period ends, only the new secret signs. Any verifier still on the old secret stops verifying — which is the forcing function that completes the rollover.
The grace deadline is not exposed on endpoint reads; size your rollout to complete within the window.

Webhook Headers

Every webhook request includes these headers:
HeaderDescription
Content-Typeapplication/json
X-Conduit-Signaturet={unix},v1={hmac-hex} — timestamp + HMAC-SHA256 signature (a second v1 is present while a signing-secret rotation is in its grace period)
X-Conduit-Delivery-IdUnique ID for this delivery attempt
X-Conduit-EventThe event type (e.g., application.approved)

Payload Format

{
  "id": "evt_...",
  "type": "application.approved",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_..."
  }
}
The data object varies by event type. Use the type field to determine how to process the payload.
FieldDescription
idUnique event ID. Use for client-side dedup.
typeEvent name (e.g. application.approved).
createdAtTimestamp the event was created (ISO 8601 UTC).
apiVersionPublic API major version: "2".
mode"live" or "sandbox". Lets one endpoint receive both safely.
dataEvent-specific payload. See GET /v2/webhooks/event-types.

Event Reference

Use GET /v2/webhooks/event-types for the current list of available events, including example payloads for each event type.

Paired events

A single business transition can emit more than one event. See the per-event descriptions in GET /v2/webhooks/event-types for the canonical dedupe rule on each pair.

Delivery Lifecycle

Each webhook delivery goes through these statuses:
StatusDescription
pendingQueued for delivery or scheduled for retry
processingCurrently being delivered
succeededYour endpoint responded with a 2xx status
failedAll retry attempts exhausted

Retries

Failed deliveries are retried with increasing delays:
AttemptDelay
130 seconds
22 minutes
315 minutes
41 hour
5+4 hours
You can also manually retry a failed delivery:
curl -X POST https://api.conduit.financial/v2/webhooks/deliveries/wdl_.../retry \
  -H "x-api-key: YOUR_API_KEY"

Managing Endpoints

OperationEndpoint
List endpointsGET /v2/webhooks/endpoints
Get endpointGET /v2/webhooks/endpoints/:id
Update endpointPATCH /v2/webhooks/endpoints/:id
Delete endpointDELETE /v2/webhooks/endpoints/:id
List deliveriesGET /v2/webhooks/deliveries?endpointId=:id&status=failed&eventType=transaction.failed
Get delivery detailGET /v2/webhooks/deliveries/:id
The status query parameter accepts pending, processing, succeeded, or failed (case-insensitive); unknown values return 400. Filters compose with endpointId. The eventType query parameter accepts an exact lowercase match against the event type (e.g. transaction.failed or order.failed). Unknown event types return an empty page. All three filters (endpointId, status, eventType) are optional and may be combined freely.

failureMessage symmetry contract

The failureMessage value is consistent across three surfaces: the database row, the polled GET /v2/transactions/{id} response, and the transaction.failed webhook payload. Applies equally to order.failed.
Failure originDB failure_messagePolled GET failureMessageWebhook failureMessage
Operator-driven (simulate/* with a reason)the supplied reason, verbatimthe supplied reason, verbatimthe supplied reason, verbatim
Compliance-driven (AML, sanctions, sender-info timeout)NULLstatic ErrorCatalog text for the codestatic ErrorCatalog text for the code
The three surfaces never diverge. An integrator can rely on either the polled GET or the webhook payload as the source of truth.

Pausing an Endpoint

Set active: false (via PATCH /v2/webhooks/endpoints/:id) to stop receiving new deliveries on an endpoint. The endpoint is excluded from event fan-out — no new deliveries are enqueued. In-flight deliveries already queued at the moment of the flip continue to retry per the retry schedule and are not cancelled. Flip back to active: true to resume receiving new deliveries.

Best Practices

  • Respond quickly. Return a 2xx status within 5 seconds. Process the event asynchronously after acknowledging receipt.
  • Deduplicate. Use the event id to detect and skip duplicate deliveries.
  • Verify signatures. Always validate X-Conduit-Signature before processing the payload.
  • Handle unknown events. Your endpoint may receive new event types as the API evolves. Return 2xx for events you don’t recognize — don’t reject them.
  • Use HTTPS. Webhook endpoint URLs must use HTTPS.

application.approved

Fired when a customer onboarding application is approved. Paired with customer.created (same applicationId, customerId, clientReferenceId); dedupe on (applicationId, customerId) if your handler reacts to either. Idempotent re-approval of an already-approved application does not re-emit the pair.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "application.approved",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-onboarding-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the approved application.
customerIdstringYesCustomer created by this application.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

application.rejected

Fired when a customer onboarding application is rejected
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "application.rejected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": null,
    "reason": "Application does not meet compliance requirements",
    "clientReferenceId": "ext-onboarding-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the rejected application.
customerIdstring | nullYesCustomer associated with this application, if one was created.
failureCodeenum: “REJECTED_BY_OPS” | “COMPLIANCE_DENIED”NoMachine-readable failure code identifying the rejection category. Mirrors failureCode on GET /v2/applications/:id.
failureMessagestringNoHuman-readable message intended for display to your end user, if provided.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

crypto_wallet.completed

Fired when the customer’s end user has finished onboarding their non-custodial wallets and the wallets are ready to receive funds. Carries the wallet IDs so you can use them immediately without polling GET /v2/customers/:id/wallets.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "crypto_wallet.completed",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer whose crypto wallet onboarding has completed.

customer_update.approved

Fired when a customer update application is approved
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "customer_update.approved",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-update-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the approved customer update application.
customerIdstringYesCustomer whose profile was updated.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

customer_update.rejected

Fired when a customer update application is rejected
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "customer_update.rejected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "reason": "Application does not meet compliance requirements",
    "clientReferenceId": "ext-update-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the rejected customer update application.
customerIdstringYesCustomer whose update was rejected.
failureCodeenum: “REJECTED_BY_OPS” | “COMPLIANCE_DENIED”NoMachine-readable failure code identifying the rejection category.
failureMessagestringNoHuman-readable message intended for display to your end user, if provided.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

customer.created

Fired when a customer is created after onboarding approval. Paired with application.approved on the first approval (same applicationId, customerId, clientReferenceId); on idempotent re-approval the customer already exists so the pair is not re-emitted. Dedupe on (applicationId, customerId) if your handler reacts to either.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "customer.created",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-onboarding-001",
    "customerType": "business"
  }
}
FieldTypeRequiredDescription
customerIdstringYesUnique ID of the newly created customer.
applicationIdstringYesOnboarding application that triggered customer creation.
clientReferenceIdstringNoCaller-supplied external reference from the onboarding application.
customerTypeenum: “business” | “individual”YesWhether the customer is an individual or a business.

order.cancelled

Fired when a pending order is cancelled — either by the sweep job (reason=expired, when lock_expires_at elapses) or by the client (reason=client_cancelled, via POST /v2/orders/:id/cancel).
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "order.cancelled",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "orderId": "ord_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-order-1",
    "reason": "client_cancelled",
    "cancelledAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
orderIdstringYesUnique ID of the cancelled order.
customerIdstringYesCustomer who placed this order.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
reasonenum: “expired” | “client_cancelled”YesWhether the order was cancelled by the client (client_cancelled) or expired due to elapsed lock time (expired). Matches the cancellationReason field on GET /v2/orders/:id.
cancelledAtstringYesISO-8601 timestamp when the order was cancelled.

order.created

Fired when an order is created via POST /v2/orders. The order is in pending status with the rate locked until lockExpiresAt. The order will not move funds until it is executed — either explicitly via POST /v2/orders/:id/execute, or automatically by the platform when source funds land (if autoExecute is true). If the lock expires while the order is still pending and unclaimed, the expiry sweep cancels it and order.cancelled fires with reason: expired.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "order.created",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "orderId": "ord_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-order-1",
    "type": "onramp",
    "sourceAssetAmount": {
      "code": "USD",
      "amount": "10000.00"
    },
    "destinationAssetAmount": {
      "code": "USDC",
      "chain": "ethereum",
      "amount": "9995.000000"
    },
    "lockExpiresAt": "2026-01-15T11:30:00.000Z",
    "autoExecute": true,
    "createdAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
orderIdstringYesUnique ID of the created order.
customerIdstringYesCustomer who placed this order.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
typeenum: “onramp” | “offramp”YesDirection of the conversion (onramp or offramp).
sourceAssetAmountobjectYesSource asset and amount that will be debited from the customer when the order executes.
sourceAssetAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
sourceAssetAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
sourceAssetAmount.amountstringYesDecimal string, asset-precision rounded
destinationAssetAmountobjectYesDestination asset and amount the customer will receive when the order executes.
destinationAssetAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
destinationAssetAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
destinationAssetAmount.amountstringYesDecimal string, asset-precision rounded
lockExpiresAtstringYesISO-8601 timestamp when the order’s rate lock expires; after this unclaimed pending orders are eligible for auto-cancel with reason expired.
autoExecutebooleanYesWhether the order will auto-execute when source funds land (true) or requires an explicit POST /v2/orders/:id/execute (false).
createdAtstringYesISO-8601 timestamp when the order was created.

order.failed

Fired when an order cannot execute or execution reaches a terminal failure. This includes pending auto-execute ONRAMP orders whose fiat source deposit terminates without crediting the customer — frozen, returned, or terminated before credit (e.g. sender-info timeout). reasonCode identifies the customer-facing failure category: INSUFFICIENT_FUNDS (source funds insufficient at execution time), PROVIDER_UNAVAILABLE (transient rail/provider unavailability — retry may succeed), PROVIDER_REJECTED (provider or screening declined the leg, including source-deposit rejection on auto-execute ONRAMP orders; retry will not help — submit with a different recipient or funding source), INTERNAL_ERROR (Conduit-side failure — contact support), CANCELLED (execution cancelled mid-flight).
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "order.failed",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "orderId": "ord_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-order-1",
    "reasonCode": "PROVIDER_REJECTED",
    "failedAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
orderIdstringYesUnique ID of the order that failed to execute.
customerIdstringYesCustomer who placed this order.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
reasonCodeenum: “INSUFFICIENT_FUNDS” | “PROVIDER_UNAVAILABLE” | “PROVIDER_REJECTED” | “INTERNAL_ERROR” | “CANCELLED”YesPublic failure category. INSUFFICIENT_FUNDS (source funds insufficient at execution time), PROVIDER_UNAVAILABLE (transient provider/rail unavailability — retry may succeed), PROVIDER_REJECTED (provider or screening declined the leg, including source-deposit rejection on auto-execute ONRAMP orders; retry will not help — submit with a different recipient or funding source), INTERNAL_ERROR (Conduit-side failure — contact support), CANCELLED (execution cancelled mid-flight).
failureMessagestringNoHuman-readable description of reasonCode. Defaults to the public error catalog text for the code. Sandbox simulators (e.g. the reason body on orders/:id/simulate/conversion-failed) and the counterparty travel-rule channel may pass through the operator/counterparty-supplied reason instead. The counterparty channel applies in both live and sandbox builds; raw provider/compliance text from other vendors is scrubbed at the same boundary that scrubs reasonCode.
failedAtstringYesISO-8601 timestamp when execution failure was recorded.

order.succeeded

Fired when an order completes successfully — the source amount has been debited from the customer and the destination amount has been credited and is available to spend. Carries the spawned transactionId (and txHash when a chain leg ran) so integrators can reconcile and link back to the underlying transaction without a GET round-trip.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "order.succeeded",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "orderId": "ord_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-order-1",
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "txHash": "0x7e0fb8d288a8d0058c6940f9327592f543415065a9180728688b51e0da44481c",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "status": "succeeded",
    "sourceAssetAmount": {
      "code": "USD",
      "amount": "10000.00"
    },
    "destinationAssetAmount": {
      "code": "USDC",
      "chain": "ethereum",
      "amount": "9995.000000"
    },
    "totalDebit": {
      "code": "USD",
      "amount": "10010.00"
    },
    "payoutAmount": {
      "code": "USDC",
      "chain": "ethereum",
      "amount": "9995.000000"
    },
    "fees": [
      {
        "type": "fixed",
        "assetAmount": {
          "code": "USD",
          "amount": "10.00"
        }
      }
    ],
    "succeededAt": "2026-01-15T09:30:00.000Z",
    "executionTrigger": "client"
  }
}
FieldTypeRequiredDescription
orderIdstringYesUnique ID of the succeeded order.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
transactionIdstring | nullYesID of the transactions row this order spawned. Use to GET the underlying transaction for per-leg detail.
txHashstring | nullYesOn-chain transaction hash from the order’s chain leg (destination leg for ONRAMP, source leg for OFFRAMP). Null when the order has no chain leg or the hash is not yet known.
customerIdstringYesCustomer who placed this order.
status”succeeded”YesTerminal status. Always succeeded on this event; failures fire order.failed instead.
sourceAssetAmountobjectYesConversion principal in source-asset terms (excludes the fee). totalDebit = sourceAssetAmount + fees is what’s actually debited from the customer.
sourceAssetAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
sourceAssetAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
sourceAssetAmount.amountstringYesDecimal string, asset-precision rounded
destinationAssetAmountobjectYesDestination asset and amount credited to the customer (equals sourceAssetAmount × rate).
destinationAssetAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
destinationAssetAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
destinationAssetAmount.amountstringYesDecimal string, asset-precision rounded
totalDebitobjectYesTotal amount debited from the customer in source-asset terms (sourceAssetAmount + fees).
totalDebit.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
totalDebit.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
totalDebit.amountstringYesDecimal string, asset-precision rounded
payoutAmountobjectYesAlias for destinationAssetAmount; kept for reconciliation tooling that pre-dates the symmetric amount semantics.
payoutAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
payoutAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
payoutAmount.amountstringYesDecimal string, asset-precision rounded
feesarray of objectYesFee components applied to this order.
succeededAtstringYesISO-8601 timestamp when the order finished executing.
executionTriggerenum: “client” | “auto”YesWhether execution was triggered by the client or automatically by the platform.

organization.activated

Fired when an organization is activated
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "organization.activated",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "organizationName": "Acme Corp"
  }
}
FieldTypeRequiredDescription
organizationNamestringYesDisplay name of the organization that was activated.

organization.approved

Fired when an organization onboarding application is approved
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "organization.approved",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-org-onboarding-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the approved organization onboarding application.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

organization.rejected

Fired when an organization onboarding application is rejected
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "organization.rejected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "reason": "Application does not meet compliance requirements",
    "clientReferenceId": "ext-org-onboarding-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the rejected organization onboarding application.
reasonstringNoHuman-readable rejection reason, if provided.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

transaction.awaiting_sender_information

Fired when a deposit is parked waiting for sender information for the source address. Payload carries the sourceAddress and an expiresAt deadline — after which the deposit auto-rejects. Chain is on assetAmount.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.awaiting_sender_information",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-deposit-1",
    "sourceAddress": "0x742d35cc6634c0532925a3b844bc9e7595f0beb1",
    "assetAmount": {
      "code": "USDC",
      "chain": "ethereum",
      "amount": "1000.000000"
    },
    "detectedAt": "2026-01-15T09:30:00.000Z",
    "occurredAt": "2026-01-15T09:30:00.000Z",
    "expiresAt": "2026-02-14T09:30:00.000Z",
    "daysRemaining": 30,
    "deadlineAt": "2026-02-14T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the parked deposit transaction.
customerIdstringYesCustomer who received the deposit.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
sourceAddressstringYesNormalized sender address the fintech must register.
assetAmountobjectYesAmount and asset of the parked deposit.
assetAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
assetAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
assetAmount.amountstringYesDecimal string, asset-precision rounded
detectedAtstringYesISO-8601 timestamp when the deposit was first detected on-chain.
occurredAtstringYesISO-8601 timestamp when the deposit was parked because the source address was not registered.
expiresAtstringYesISO-8601 deadline — deposit auto-freezes if address is not registered by this time.
daysRemaininginteger | nullYesDays remaining before the sender-info deadline. Re-emitted as the deadline approaches with the updated value; null on the final terminal-reached emission.
deadlineAtstringYesISO-8601 timestamp of the customer-facing deadline for providing sender information. Once exceeded, the deposit is terminated with failureCode SENDER_INFO_TIMEOUT. In live builds this is the real 30-day deadline. In sandbox builds the deadline is compressed (default 30 seconds via SANDBOX_SENDER_INFO_DEADLINE_MS) but this field still reflects the live-equivalent 30-day deadline so SDK consumers see the contract a live customer would receive.

transaction.awaiting_user_signature

Fired once per payout when the customer’s signing roster must approve before broadcast. Payload carries the single shared verificationUrl (Conduit-hosted approval page distributed by the fintech to its signers), expiresAt, and attempt. In Phase 0 the attempt field is always 1; the rebuild-on-expiry path that would increment it ships in a follow-up — the field is kept on the payload for forward-compat. After expiresAt the payout auto-fails with USER_SIGNATURE_TIMEOUT.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.awaiting_user_signature",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "assetAmount": {
      "code": "USDC",
      "chain": "ethereum",
      "amount": "1000.000000"
    },
    "destinationAddress": "0x742d35cc6634c0532925a3b844bc9e7595f0beb1",
    "verificationUrl": "https://verify.conduit.financial/verify/vtok_2xKjF9mQb7vN4hL1pR3w8t",
    "requiredApprovals": 2,
    "occurredAt": "2026-01-15T09:30:00.000Z",
    "expiresAt": "2026-01-15T09:45:00.000Z",
    "attempt": 1
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the parked payout transaction.
customerIdstringYesCustomer initiating the payout.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
assetAmountobjectYesAmount and asset of the pending payout.
assetAmount.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
assetAmount.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat.
assetAmount.amountstringYesDecimal string, asset-precision rounded
destinationAddressstringYesOn-chain destination address for the payout.
verificationUrlstringYesConduit-hosted URL the fintech routes the user to for approval.
requiredApprovalsintegerYesM — business-signer stamps required to authorize, frozen at prepare time.
occurredAtstringYesISO-8601 timestamp when the payout was parked awaiting the user’s signature.
expiresAtstringYesISO-8601 deadline — payout auto-fails if the user does not sign by this time.
attemptintegerYesRe-issue counter for the signing link. In Phase 0 always 1 — the rebuild-on-expiry path that would increment it ships in a follow-up. The field is kept on the payload for forward-compat so future consumers can dedupe redeliveries without a schema change.

transaction.cancelled

Fired when a transaction is cancelled before reaching its terminal-completed state. Distinct from transaction.failed: a cancelled transaction is not a failure; the client (or, in the future, an expiry sweep) terminated it intentionally. cancellationReason is client_cancelled when the client called POST /v2/payouts/:id/cancel. Reserved value expired is published when the underlying lock window elapses (future). The payload intentionally has no failureCode/failureMessage. Shares the cancellation semantics and cancellationReason vocabulary with order.cancelled (the payload itself is transaction-shaped: transactionId + nested source/destination).
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.cancelled",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "type": "withdrawal",
    "source": {
      "type": "virtual_account",
      "virtualAccountId": "vac_2xKjF9mQb7vN4hL1pR3w8t",
      "assetAmount": {
        "code": "USD",
        "amount": "1000.00"
      }
    },
    "destination": {
      "type": "external_bank",
      "recipient": {
        "rail": "us",
        "type": "INDIVIDUAL",
        "firstName": "Jane",
        "lastName": "Doe",
        "accountNumber": "0123456789",
        "routingNumber": "021000021",
        "accountType": "CHECKING",
        "bankAddress": {
          "addressLine1": "1 Main St",
          "city": "NYC",
          "country": "USA"
        },
        "phone": "+15551234",
        "postalAddress": {
          "addressLine1": "1 Main St",
          "city": "NYC",
          "country": "USA"
        }
      },
      "assetAmount": {
        "code": "USD",
        "amount": "1000.00"
      }
    },
    "cancellationReason": "client_cancelled",
    "cancelledAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the cancelled transaction.
customerIdstringYesCustomer associated with this transaction.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
typeenum: “deposit” | “withdrawal” | “onramp” | “offramp”YesTransaction type. Same value as on transaction.created.
sourceobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesSource side of the cancelled transaction — same nested shape as GET /v2/transactions/:id.
destinationobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesDestination side of the cancelled transaction.
cancellationReasonenum: “expired” | “client_cancelled”YesWhether the transaction was cancelled by the client (client_cancelled) — today the only path that publishes this event — or, in the future, expired (expired). Matches the cancellationReason field on GET /v2/transactions/:id. Mirrors order.cancelled’s reason field.
cancelledAtstringYesISO-8601 timestamp when the transaction was cancelled.
linkedOrderIdstringNoPresent on chained transactions (a Withdrawal spawned from an Order’s autoPayout). Joins back to the parent Order so cross-leg reconciliation works on cancel, same as on transaction.created. Absent on standalone payouts.

transaction.completed

Fired when a transaction completes successfully. source and destination carry the same nested shape as GET /v2/transactions/:id. The settlement reference lives inside the relevant side variant: external_crypto.txHash for crypto rails, and on external_bank the real wire references as typed fields — swiftUetr, fedwireImad, fedwireOmad, achTraceNumber, rtpTransactionId, fedNowMessageId — each present only when the network exposes it (on-us transfers carry none). Fiat payout events include the selected rail for reconciliation on payout.rail.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.completed",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "type": "withdrawal",
    "status": "completed",
    "source": {
      "type": "virtual_account",
      "virtualAccountId": "vac_2xKjF9mQb7vN4hL1pR3w8t",
      "assetAmount": {
        "code": "USD",
        "amount": "1000.00"
      }
    },
    "destination": {
      "type": "external_bank",
      "recipient": {
        "rail": "us",
        "type": "INDIVIDUAL",
        "firstName": "Jane",
        "lastName": "Doe",
        "accountNumber": "0123456789",
        "routingNumber": "021000021",
        "accountType": "CHECKING",
        "bankAddress": {
          "addressLine1": "1 Main St",
          "city": "NYC",
          "country": "USA"
        },
        "phone": "+15551234",
        "postalAddress": {
          "addressLine1": "1 Main St",
          "city": "NYC",
          "country": "USA"
        }
      },
      "assetAmount": {
        "code": "USD",
        "amount": "1000.00"
      },
      "fedwireImad": "20260616MMQFMP9C000123"
    },
    "payout": {
      "rail": "fedwire"
    },
    "completedAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the completed transaction.
customerIdstringYesCustomer associated with this transaction.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
typeenum: “deposit” | “withdrawal” | “onramp” | “offramp”YesTransaction type.
status”completed”YesTerminal status. Always completed on this event; failures fire transaction.failed instead.
sourceobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesSource side of the transaction — same nested shape as GET /v2/transactions/:id. On crypto rails the on-chain settlement hash lives inside the matching external_crypto.txHash; on fiat rails the real wire references are typed fields on the matching external_bank (swiftUetr, fedwireImad, fedwireOmad, achTraceNumber, rtpTransactionId, fedNowMessageId), present only when the network exposes one.
destinationobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesDestination side of the transaction. See source for the settlement-reference placement rule.
payoutobjectNoPayout rail metadata. Present only for outbound transactions.
payout.railenum: “fedwire” | “rtp” | “fednow” | “swift” | nullYesFiat payment rail used, if applicable. Null for crypto payouts.
completedAtstringYesISO-8601 timestamp when the transaction completed.
matchableOrdersarray of objectNoPending orders that can execute against the deposited funds. Only populated for inbound DEPOSIT transactions.
matchableOrdersTruncatedbooleanNoTrue when the matchable orders list was truncated due to size limits.

transaction.created

Fired when a new transaction is initiated. source and destination carry the same nested shape as GET /v2/transactions/:id — discriminated by type (wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) and each variant carries its own assetAmount. Chained transactions (a Withdrawal spawned from an Order’s autoPayout) carry linkedOrderId referencing the parent Order; absent on all other transactions.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.created",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "type": "deposit",
    "source": {
      "type": "external_crypto",
      "address": "0x742d35cc6634c0532925a3b8d4c9c2c7a3b3d7e1",
      "assetAmount": {
        "code": "ETH",
        "chain": "ethereum",
        "amount": "0.500000000000000000"
      }
    },
    "destination": {
      "type": "wallet",
      "walletId": "wlt_2xKjF9mQb7vN4hL1pR3w8t",
      "address": "0x742d35cc6634c0532925a3b844bc9e7595f2bd18",
      "assetAmount": {
        "code": "ETH",
        "chain": "ethereum",
        "amount": "0.500000000000000000"
      }
    },
    "createdAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the transaction.
customerIdstringYesCustomer who initiated or received this transaction.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
typeenum: “deposit” | “withdrawal” | “onramp” | “offramp”YesTransaction type.
sourceobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesSource side of the transaction — the variant matches the GET /v2/transactions/:id shape (wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown). Includes assetAmount (amount + asset code/chain).
destinationobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesDestination side of the transaction — same shape as source. The variant determines which counterparty details are populated (wallet, virtual account with optional wireReceive, external crypto address, external bank recipient).
linkedOrderIdstringNoPresent on chained Withdrawals spawned from an Order’s autoPayout; points at the parent Order id. Absent on all other transactions.
createdAtstringYesISO-8601 timestamp when the transaction was created.

transaction.failed

Fired when a transaction reaches a terminal failed state. source and destination carry the same nested shape as GET /v2/transactions/:id. The payload carries a failureCode your integration can branch on:
  • USER_SIGNATURE_* / CRYPTO_WALLET_MISCONFIGURED — recoverable: submit a new transaction.
  • PROVIDER_REJECTED — chain RPC or sandbox-scenario declined the broadcast; failureMessage carries the operator/scenario-supplied reason when the underlying message was marked for public surfacing (sandbox/scenario paths). Live provider diagnostics are gated off the public surface. Adjust inputs (e.g. destination address, amount) and retry.
  • TRAVEL_RULE_REJECTED — counterparty VASP rejected the travel-rule transfer; failureMessage carries the counterparty’s reason when supplied. Not retryable without coordinating with the receiving institution.
  • COMPLIANCE_HOLD / AML_SANCTIONED / AML_REJECTED — compliance review required; not retryable without investigation.
  • RETURNED_BY_SENDER — fiat sender reversed the inbound transfer, or compliance marked the deposit returned before credit.
  • RAIL_POLICY_REJECTED / INSUFFICIENT_FUNDS_AT_SETTLE / RAIL_UNAVAILABLE — payment-rail failure; adjust amount, recipient, or rail and retry.
  • SENDER_INFO_TIMEOUT — sender-info gate timed out; submit with sender details included. When failureCode is absent the failure has no actionable code — contact support. Order-level failures (including conversion provider unavailability) surface on order.failed with a reasonCode, not here.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.failed",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "type": "withdrawal",
    "source": {
      "type": "wallet",
      "walletId": "wlt_2xKjF9mQb7vN4hL1pR3w8t",
      "address": "0x742d35cc6634c0532925a3b844bc9e7595f2bd18",
      "assetAmount": {
        "code": "USDC",
        "chain": "ethereum",
        "amount": "100.000000"
      }
    },
    "destination": {
      "type": "external_crypto",
      "address": "0x9abc456789defabcdef0123456789abcdef01234",
      "assetAmount": {
        "code": "USDC",
        "chain": "ethereum",
        "amount": "100.000000"
      }
    },
    "failureCode": "USER_SIGNATURE_DECLINED",
    "failureMessage": "The customer declined the payout from the approval page. No funds were moved.",
    "failedAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the failed transaction.
customerIdstringYesCustomer associated with this transaction.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
typeenum: “deposit” | “withdrawal” | “onramp” | “offramp”YesTransaction type. Same value as on transaction.created.
sourceobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesSource side of the failed transaction — same nested shape as GET /v2/transactions/:id. Populated even on terminal failures so the payload is self-contained.
destinationobject (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown)YesDestination side of the failed transaction. May surface as external_unknown when failure occurred before counterparty resolution.
failureCodeenum: “USER_SIGNATURE_TIMEOUT” | “USER_SIGNATURE_DECLINED” | “USER_SIGNATURE_REJECTED_BY_PROVIDER” | “CRYPTO_WALLET_MISCONFIGURED” | “COMPLIANCE_HOLD” | “AML_REJECTED” | “COMPLIANCE_REJECTED” | “RETURNED_BY_SENDER” | “RAIL_POLICY_REJECTED” | “INSUFFICIENT_FUNDS_AT_SETTLE” | “RAIL_UNAVAILABLE” | “SENDER_INFO_TIMEOUT” | “TRAVEL_RULE_REJECTED” | “PROVIDER_REJECTED” | “CHAIN_BROADCAST_FAILED” | “ROSTER_CHANGED”NoMachine-readable failure code identifying the cause (e.g. USER_SIGNATURE_TIMEOUT, PROVIDER_REJECTED, TRAVEL_RULE_REJECTED). Present when the failure has a recoverable cause the integration can act on. When absent, the payout cannot be completed and retrying will not help — contact support if recovery is needed.
failureMessagestringNoHuman-readable description of failureCode. Defaults to the public error catalog text for the code. Sandbox simulators and the counterparty travel-rule channel may pass through the operator/counterparty-supplied reason instead (the counterparty channel applies in both live and sandbox builds; raw provider/compliance text from other vendors is scrubbed at the public boundary).
payoutobjectNoPayout rail metadata. Present only for outbound transactions.
payout.railenum: “fedwire” | “rtp” | “fednow” | “swift” | nullYesFiat payment rail used, if applicable. Null for crypto payouts.
failedAtstringYesISO-8601 timestamp when the transaction failed.

transaction.quorum_met

Fired when all required signer stamps are in (collected >= required). Conduit’s compliance step runs next and is the final gate before broadcast.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.quorum_met",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesID of the transaction that reached signer quorum.
customerIdstringYesCustomer associated with the transaction.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

transaction.rejected

Fired when a compliance reviewer rejects the supporting document on an accepted payout, before execution. Terminal: funds are returned to the available balance. Resubmit a new payout with an acceptable document (see acceptedDocumentTypes) and a fresh idempotency key.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.rejected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "type": "withdrawal",
    "status": "failed",
    "reasonCategory": "document_inadequate",
    "acceptedDocumentTypes": [
      "bank_verification_letter",
      "invoice",
      "contract",
      "payroll_register",
      "investment_agreement",
      "other"
    ],
    "rejectedAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
transactionIdstringYesUnique ID of the rejected payout.
customerIdstringYesCustomer who initiated this payout.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
type”withdrawal”YesAlways withdrawal — only payouts are subject to document review.
status”failed”YesPublic terminal status. Always failed on this event; the payout is terminal and funds are returned.
reasonCategoryenum: “document_inadequate”YesMachine-readable rejection category. document_inadequate — the submitted document could not satisfy the compliance requirement.
acceptedDocumentTypesarray of enum: “bank_verification_letter” | “invoice” | “contract” | “payroll_register” | “investment_agreement” | “other”YesSupporting-document types accepted as evidence on a resubmitted payout. Attach a document of one of these types and resubmit with a fresh idempotency key.
rejectedAtstringYesISO-8601 timestamp when the document review rejection was recorded.

transaction.signature_collected

Fired once per signer stamp collected on a multi-signer payout. Track collected / required to drive a progress UI; once collected >= required, transaction.quorum_met follows.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "transaction.signature_collected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "transactionId": "txn_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "clientReferenceId": "ext-payout-1",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "collected": 1,
    "required": 2
  }
}
FieldTypeRequiredDescription
transactionIdstringYesID of the transaction being signed.
customerIdstringYesCustomer associated with the transaction.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
walletSignerIdstringYesID of the signer whose stamp was just collected.
collectedintegerYesCount of distinct signer stamps gathered so far.
requiredintegerYesTotal signer stamps required (signingThreshold).

virtual_account_application.approved

Fired when a virtual account application is approved
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "virtual_account_application.approved",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "asset": {
      "code": "USD"
    },
    "clientReferenceId": "ext-va-app-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the approved virtual account application.
customerIdstringYesCustomer who owns this virtual account application.
assetobjectYesAsset the virtual account will hold.
asset.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
asset.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat
clientReferenceIdstringNoCaller-supplied external reference, if provided.

virtual_account_application.rejected

Fired when a virtual account application is rejected
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "virtual_account_application.rejected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "applicationId": "app_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "asset": {
      "code": "USD"
    },
    "reason": "Application does not meet compliance requirements",
    "clientReferenceId": "ext-va-app-001"
  }
}
FieldTypeRequiredDescription
applicationIdstringYesUnique ID of the rejected virtual account application.
customerIdstringYesCustomer who owns this virtual account application.
assetobjectYesAsset the virtual account would have held.
asset.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
asset.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat
failureCodeenum: “REJECTED_BY_OPS” | “COMPLIANCE_DENIED”NoMachine-readable failure code identifying the rejection category.
failureMessagestringNoHuman-readable message intended for display to your end user, if provided.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

virtual_account.activated

Fired when a virtual account is activated
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "virtual_account.activated",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "virtualAccountId": "vac_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "asset": {
      "code": "USD"
    },
    "activatedAt": "2026-01-15T09:30:00.000Z"
  }
}
FieldTypeRequiredDescription
virtualAccountIdstringYesUnique ID of the now-active virtual account.
customerIdstringYesCustomer who owns this virtual account.
assetobjectYesAsset this virtual account holds.
asset.codeenum: “USD” | “EUR” | “GBP” | “CHF” | “JPY” | “CAD” | “AUD” | “NZD” | “SGD” | “HKD” | “CNY” | “KRW” | “INR” | “BRL” | “MXN” | “ARS” | “CLP” | “COP” | “PEN” | “ZAR” | “NGN” | “KES” | “GHS” | “EGP” | “AED” | “SAR” | “ILS” | “TRY” | “PLN” | “CZK” | “HUF” | “SEK” | “NOK” | “DKK” | “THB” | “IDR” | “MYR” | “PHP” | “VND” | “TWD” | “USDC” | “USDT” | “DAI” | “EURC” | “PYUSD” | “BTC” | “ETH” | “SOL” | “TRX”YesAsset code (USDC, USD, etc.)
asset.chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”NoChain when the asset is on-chain; omitted for fiat
activatedAtstringYesISO-8601 timestamp when the virtual account became active.

wallet_signer.added

Fired when a wallet signer row is created on the roster (immediately at invite time, status PENDING_ACTIVATION) — co-emitted with wallet_signer.invited from the same outbox transaction. Distinct from wallet_signer.invited (which carries the verification URL) and wallet_signer.enrolled (sent later when the signer completes credential enrollment).
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet_signer.added",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "email": "signer@example.com",
    "role": "signer",
    "credentialType": "passkey",
    "clientReferenceId": "ext-signer-001"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer the signer belongs to.
walletSignerIdstringYesUnique ID of the wallet signer.
emailstringYesEmail address of the signer.
roleenum: “admin” | “signer”YesRoster role of the signer.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
credentialTypeenum: “passkey” | “api_key”YesCredential type the signer enrolled.

wallet_signer.demoted

Fired when an admin is demoted to signer via the two-step demote ceremony (root-quorum update + tag update). Validates min-2-admins floor before starting.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet_signer.demoted",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "email": "signer@example.com",
    "role": "signer",
    "previousRole": "admin",
    "clientReferenceId": "ext-signer-001"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer the signer belongs to.
walletSignerIdstringYesUnique ID of the wallet signer.
emailstringYesEmail address of the signer.
roleenum: “admin” | “signer”YesRoster role of the signer.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
previousRole”admin”YesRole the signer held before demotion.

wallet_signer.enrolled

Fired when a wallet signer completes credential enrollment via the verification URL. passkeyCount reflects the number of passkeys registered for passkey signers.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet_signer.enrolled",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "email": "signer@example.com",
    "role": "signer",
    "passkeyCount": 1,
    "clientReferenceId": "ext-signer-001"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer the signer belongs to.
walletSignerIdstringYesUnique ID of the wallet signer.
emailstringYesEmail address of the signer.
roleenum: “admin” | “signer”YesRoster role of the signer.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
passkeyCountintegerNoNumber of passkeys the signer has enrolled. Present when credentialType is passkey.

wallet_signer.invited

Fired when a wallet signer is invited to enroll their credential. Payload carries the per-signer enrollment verificationUrl the fintech distributes out-of-band, plus expiresAt — after which the invitation auto-expires.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet_signer.invited",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "email": "signer@example.com",
    "name": "Jane Doe",
    "role": "signer",
    "credentialType": "passkey",
    "verificationUrl": "https://verify.conduit.financial/verify/vtok_3yLkG0nRc8wO5iM2qS4x9u",
    "expiresAt": "2026-01-22T09:30:00.000Z",
    "clientReferenceId": "ext-signer-001"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer the signer belongs to.
walletSignerIdstringYesUnique ID of the wallet signer.
emailstringYesEmail address of the signer.
roleenum: “admin” | “signer”YesRoster role of the signer.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
namestringNoDisplay name of the signer, if provided at invite time.
credentialTypeenum: “passkey” | “api_key”YesCredential type the signer will enroll.
verificationUrlstringYesConduit-hosted enrollment URL the fintech routes the signer to. One URL per invited signer, distributed out-of-band.
expiresAtstringYesISO-8601 deadline — the invitation auto-expires if the signer has not enrolled by this time.

wallet_signer.promoted

Fired when a wallet signer is promoted to admin via the two-step promote ceremony (tag update + root-quorum update). Requires the signer to have at least 2 passkeys enrolled.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet_signer.promoted",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "email": "admin@example.com",
    "role": "admin",
    "previousRole": "signer",
    "clientReferenceId": "ext-signer-001"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer the signer belongs to.
walletSignerIdstringYesUnique ID of the wallet signer.
emailstringYesEmail address of the signer.
roleenum: “admin” | “signer”YesRoster role of the signer.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
previousRole”signer”YesRole the signer held before promotion.

wallet_signer.removed

Fired when a wallet signer is removed from the roster. reason discriminates between customer-initiated removal (customer_removed) and internal ops removal (ops_removed). Pending payouts carrying the removed signer’s stamp are voided with failureCode ROSTER_CHANGED.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet_signer.removed",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "walletSignerId": "wsg_2xKjF9mQb7vN4hL1pR3w8t",
    "email": "signer@example.com",
    "role": "signer",
    "reason": "customer_removed",
    "clientReferenceId": "ext-signer-001"
  }
}
FieldTypeRequiredDescription
customerIdstringYesCustomer the signer belongs to.
walletSignerIdstringYesUnique ID of the wallet signer.
emailstringYesEmail address of the signer.
roleenum: “admin” | “signer”YesRoster role of the signer.
clientReferenceIdstringNoCaller-supplied external reference, if provided.
reasonenum: “customer_removed” | “ops_removed”YesWhether the signer was removed by a customer admin (customer_removed) or via an internal ops action (ops_removed).

wallet.created

Fired when a crypto wallet is created
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet.created",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "walletId": "wlt_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "chain": "ethereum",
    "address": "0x742d35cc6634c0532925a3b8d4c9c2c7a3b3d7e1",
    "custodyModel": "custodial",
    "clientReferenceId": "ext-wallet-001"
  }
}
FieldTypeRequiredDescription
walletIdstringYesUnique ID of the newly created wallet.
customerIdstringYesCustomer who owns this wallet.
chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”YesBlockchain network this wallet operates on.
addressstringYesOn-chain address of the wallet.
custodyModelenum: “custodial” | “non_custodial”NoWhich side holds the signing key. custodial — Conduit signs on the customer’s behalf; non_custodial — the customer co-signs each payout via the verify URL. Omitted when the custody model has not yet been determined.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

wallet.rotated

Fired when a crypto wallet is rotated
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "wallet.rotated",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "walletId": "wlt_2xKjF9mQb7vN4hL1pR3w8t",
    "replacedByWalletId": "wlt_3yLkG0nRc8wO5iM2qS4x9u",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "chain": "ethereum",
    "custodyModel": "non_custodial",
    "rotatedAt": "2026-01-15T09:30:00.000Z",
    "clientReferenceId": "ext-wallet-001"
  }
}
FieldTypeRequiredDescription
walletIdstringYesUnique ID of the wallet that was rotated (now inactive).
replacedByWalletIdstringYesUnique ID of the new wallet that replaced this one.
customerIdstringYesCustomer who owns this wallet.
chainenum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin”YesBlockchain network this wallet operates on.
custodyModelenum: “custodial” | “non_custodial”NoWhich side holds the signing key on the replacement wallet (replacedByWalletId). custodial — Conduit signs; non_custodial — the customer co-signs each payout. Omitted when unknown.
rotatedAtstringYesISO-8601 timestamp when the wallet was rotated.
clientReferenceIdstringNoCaller-supplied external reference, if provided.

whitelist_recipient.registered

Fired when compliance approves a pending intercompany whitelist registration. The recipient can now receive purpose=INTERCOMPANY payouts for this customer.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "whitelist_recipient.registered",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "whitelistRecipientId": "wlr_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "rail": "US",
    "relationship": "GROUP_ENTITY",
    "status": "registered",
    "holderName": "Acme Treasury Inc",
    "label": "acme-us-treasury"
  }
}
FieldTypeRequiredDescription
whitelistRecipientIdstringYesUnique ID of the whitelist recipient entry.
customerIdstringYesCustomer who owns this whitelist entry.
railenum: “US” | “SWIFT”YesPayment rail for this whitelist entry (US or SWIFT).
relationshipenum: “SELF” | “GROUP_ENTITY”YesIntercompany relationship between the customer and the recipient.
statusenum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected”YesCurrent status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id.
holderNamestringYesLegal name of the account holder.
labelstring | nullYesOptional caller-assigned label for this entry.

whitelist_recipient.rejected

Fired when compliance rejects a pending registration.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "whitelist_recipient.rejected",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "whitelistRecipientId": "wlr_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "rail": "US",
    "relationship": "GROUP_ENTITY",
    "status": "rejected",
    "holderName": "Acme Treasury Inc",
    "label": "acme-us-treasury"
  }
}
FieldTypeRequiredDescription
whitelistRecipientIdstringYesUnique ID of the whitelist recipient entry.
customerIdstringYesCustomer who owns this whitelist entry.
railenum: “US” | “SWIFT”YesPayment rail for this whitelist entry (US or SWIFT).
relationshipenum: “SELF” | “GROUP_ENTITY”YesIntercompany relationship between the customer and the recipient.
statusenum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected”YesCurrent status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id.
holderNamestringYesLegal name of the account holder.
labelstring | nullYesOptional caller-assigned label for this entry.

whitelist_recipient.revoked

Fired when an entry is revoked (terminal; client-initiated or compliance action).
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "whitelist_recipient.revoked",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "whitelistRecipientId": "wlr_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "rail": "US",
    "relationship": "GROUP_ENTITY",
    "status": "revoked",
    "holderName": "Acme Treasury Inc",
    "label": "acme-us-treasury"
  }
}
FieldTypeRequiredDescription
whitelistRecipientIdstringYesUnique ID of the whitelist recipient entry.
customerIdstringYesCustomer who owns this whitelist entry.
railenum: “US” | “SWIFT”YesPayment rail for this whitelist entry (US or SWIFT).
relationshipenum: “SELF” | “GROUP_ENTITY”YesIntercompany relationship between the customer and the recipient.
statusenum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected”YesCurrent status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id.
holderNamestringYesLegal name of the account holder.
labelstring | nullYesOptional caller-assigned label for this entry.

whitelist_recipient.suspended

Fired when an active entry is suspended — it no longer satisfies INTERCOMPANY payouts.
{
  "id": "evt_2xKjF9mQb7vN4hL1pR3w8t",
  "type": "whitelist_recipient.suspended",
  "createdAt": "2026-01-15T09:30:00.000Z",
  "apiVersion": "2",
  "mode": "live",
  "data": {
    "whitelistRecipientId": "wlr_2xKjF9mQb7vN4hL1pR3w8t",
    "customerId": "cus_2xKjF9mQb7vN4hL1pR3w8t",
    "rail": "US",
    "relationship": "GROUP_ENTITY",
    "status": "suspended",
    "holderName": "Acme Treasury Inc",
    "label": "acme-us-treasury"
  }
}
FieldTypeRequiredDescription
whitelistRecipientIdstringYesUnique ID of the whitelist recipient entry.
customerIdstringYesCustomer who owns this whitelist entry.
railenum: “US” | “SWIFT”YesPayment rail for this whitelist entry (US or SWIFT).
relationshipenum: “SELF” | “GROUP_ENTITY”YesIntercompany relationship between the customer and the recipient.
statusenum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected”YesCurrent status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id.
holderNamestringYesLegal name of the account holder.
labelstring | nullYesOptional caller-assigned label for this entry.