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).
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")),
);
}
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
| Mistake | Symptom | Fix |
|---|
Stripping the whsec_ prefix before computing HMAC | Every valid delivery returns 401; logs look identical to a tampered request | Pass the secret verbatim — the prefix is key material, not a label |
| Parsing the request body as JSON before computing HMAC | Most deliveries pass, but any payload where field ordering or whitespace changes fails | Compute HMAC over the raw bytes received on the wire, before any deserialization |
Comparing digests with == | Subtle timing side-channel; nothing visibly broken | Use crypto.timingSafeEqual (Node) / hmac.compare_digest (Python) |
Trusting X-Conduit-Event for routing without verifying the signature first | Endpoint accepts forged events from anyone who can reach the URL | Verify 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:
- Call rotate and store the new secret.
- 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.
- 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.
Every webhook request includes these headers:
| Header | Description |
|---|
Content-Type | application/json |
X-Conduit-Signature | t={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-Id | Unique ID for this delivery attempt |
X-Conduit-Event | The event type (e.g., application.approved) |
{
"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.
| Field | Description |
|---|
id | Unique event ID. Use for client-side dedup. |
type | Event name (e.g. application.approved). |
createdAt | Timestamp the event was created (ISO 8601 UTC). |
apiVersion | Public API major version: "2". |
mode | "live" or "sandbox". Lets one endpoint receive both safely. |
data | Event-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:
| Status | Description |
|---|
pending | Queued for delivery or scheduled for retry |
processing | Currently being delivered |
succeeded | Your endpoint responded with a 2xx status |
failed | All retry attempts exhausted |
Retries
Failed deliveries are retried with increasing delays:
| Attempt | Delay |
|---|
| 1 | 30 seconds |
| 2 | 2 minutes |
| 3 | 15 minutes |
| 4 | 1 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
| Operation | Endpoint |
|---|
| List endpoints | GET /v2/webhooks/endpoints |
| Get endpoint | GET /v2/webhooks/endpoints/:id |
| Update endpoint | PATCH /v2/webhooks/endpoints/:id |
| Delete endpoint | DELETE /v2/webhooks/endpoints/:id |
| List deliveries | GET /v2/webhooks/deliveries?endpointId=:id&status=failed&eventType=transaction.failed |
| Get delivery detail | GET /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 origin | DB failure_message | Polled GET failureMessage | Webhook failureMessage |
|---|
Operator-driven (simulate/* with a reason) | the supplied reason, verbatim | the supplied reason, verbatim | the supplied reason, verbatim |
| Compliance-driven (AML, sanctions, sender-info timeout) | NULL | static ErrorCatalog text for the code | static 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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the approved application. |
customerId | string | Yes | Customer created by this application. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the rejected application. |
customerId | string | null | Yes | Customer associated with this application, if one was created. |
failureCode | enum: “REJECTED_BY_OPS” | “COMPLIANCE_DENIED” | No | Machine-readable failure code identifying the rejection category. Mirrors failureCode on GET /v2/applications/:id. |
failureMessage | string | No | Human-readable message intended for display to your end user, if provided. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer 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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the approved customer update application. |
customerId | string | Yes | Customer whose profile was updated. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the rejected customer update application. |
customerId | string | Yes | Customer whose update was rejected. |
failureCode | enum: “REJECTED_BY_OPS” | “COMPLIANCE_DENIED” | No | Machine-readable failure code identifying the rejection category. |
failureMessage | string | No | Human-readable message intended for display to your end user, if provided. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Unique ID of the newly created customer. |
applicationId | string | Yes | Onboarding application that triggered customer creation. |
clientReferenceId | string | No | Caller-supplied external reference from the onboarding application. |
customerType | enum: “business” | “individual” | Yes | Whether 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"
}
}
| Field | Type | Required | Description |
|---|
orderId | string | Yes | Unique ID of the cancelled order. |
customerId | string | Yes | Customer who placed this order. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
reason | enum: “expired” | “client_cancelled” | Yes | Whether 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. |
cancelledAt | string | Yes | ISO-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"
}
}
| Field | Type | Required | Description |
|---|
orderId | string | Yes | Unique ID of the created order. |
customerId | string | Yes | Customer who placed this order. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
type | enum: “onramp” | “offramp” | Yes | Direction of the conversion (onramp or offramp). |
sourceAssetAmount | object | Yes | Source asset and amount that will be debited from the customer when the order executes. |
sourceAssetAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
sourceAssetAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
sourceAssetAmount.amount | string | Yes | Decimal string, asset-precision rounded |
destinationAssetAmount | object | Yes | Destination asset and amount the customer will receive when the order executes. |
destinationAssetAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
destinationAssetAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
destinationAssetAmount.amount | string | Yes | Decimal string, asset-precision rounded |
lockExpiresAt | string | Yes | ISO-8601 timestamp when the order’s rate lock expires; after this unclaimed pending orders are eligible for auto-cancel with reason expired. |
autoExecute | boolean | Yes | Whether the order will auto-execute when source funds land (true) or requires an explicit POST /v2/orders/:id/execute (false). |
createdAt | string | Yes | ISO-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"
}
}
| Field | Type | Required | Description |
|---|
orderId | string | Yes | Unique ID of the order that failed to execute. |
customerId | string | Yes | Customer who placed this order. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
reasonCode | enum: “INSUFFICIENT_FUNDS” | “PROVIDER_UNAVAILABLE” | “PROVIDER_REJECTED” | “INTERNAL_ERROR” | “CANCELLED” | Yes | Public 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). |
failureMessage | string | No | Human-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. |
failedAt | string | Yes | ISO-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"
}
}
| Field | Type | Required | Description |
|---|
orderId | string | Yes | Unique ID of the succeeded order. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
transactionId | string | null | Yes | ID of the transactions row this order spawned. Use to GET the underlying transaction for per-leg detail. |
txHash | string | null | Yes | On-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. |
customerId | string | Yes | Customer who placed this order. |
status | ”succeeded” | Yes | Terminal status. Always succeeded on this event; failures fire order.failed instead. |
sourceAssetAmount | object | Yes | Conversion principal in source-asset terms (excludes the fee). totalDebit = sourceAssetAmount + fees is what’s actually debited from the customer. |
sourceAssetAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
sourceAssetAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
sourceAssetAmount.amount | string | Yes | Decimal string, asset-precision rounded |
destinationAssetAmount | object | Yes | Destination asset and amount credited to the customer (equals sourceAssetAmount × rate). |
destinationAssetAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
destinationAssetAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
destinationAssetAmount.amount | string | Yes | Decimal string, asset-precision rounded |
totalDebit | object | Yes | Total amount debited from the customer in source-asset terms (sourceAssetAmount + fees). |
totalDebit.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
totalDebit.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
totalDebit.amount | string | Yes | Decimal string, asset-precision rounded |
payoutAmount | object | Yes | Alias for destinationAssetAmount; kept for reconciliation tooling that pre-dates the symmetric amount semantics. |
payoutAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
payoutAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
payoutAmount.amount | string | Yes | Decimal string, asset-precision rounded |
fees | array of object | Yes | Fee components applied to this order. |
succeededAt | string | Yes | ISO-8601 timestamp when the order finished executing. |
executionTrigger | enum: “client” | “auto” | Yes | Whether 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"
}
}
| Field | Type | Required | Description |
|---|
organizationName | string | Yes | Display 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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the approved organization onboarding application. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the rejected organization onboarding application. |
reason | string | No | Human-readable rejection reason, if provided. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the parked deposit transaction. |
customerId | string | Yes | Customer who received the deposit. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
sourceAddress | string | Yes | Normalized sender address the fintech must register. |
assetAmount | object | Yes | Amount and asset of the parked deposit. |
assetAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
assetAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
assetAmount.amount | string | Yes | Decimal string, asset-precision rounded |
detectedAt | string | Yes | ISO-8601 timestamp when the deposit was first detected on-chain. |
occurredAt | string | Yes | ISO-8601 timestamp when the deposit was parked because the source address was not registered. |
expiresAt | string | Yes | ISO-8601 deadline — deposit auto-freezes if address is not registered by this time. |
daysRemaining | integer | null | Yes | Days remaining before the sender-info deadline. Re-emitted as the deadline approaches with the updated value; null on the final terminal-reached emission. |
deadlineAt | string | Yes | ISO-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
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the parked payout transaction. |
customerId | string | Yes | Customer initiating the payout. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
assetAmount | object | Yes | Amount and asset of the pending payout. |
assetAmount.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
assetAmount.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat. |
assetAmount.amount | string | Yes | Decimal string, asset-precision rounded |
destinationAddress | string | Yes | On-chain destination address for the payout. |
verificationUrl | string | Yes | Conduit-hosted URL the fintech routes the user to for approval. |
requiredApprovals | integer | Yes | M — business-signer stamps required to authorize, frozen at prepare time. |
occurredAt | string | Yes | ISO-8601 timestamp when the payout was parked awaiting the user’s signature. |
expiresAt | string | Yes | ISO-8601 deadline — payout auto-fails if the user does not sign by this time. |
attempt | integer | Yes | Re-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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the cancelled transaction. |
customerId | string | Yes | Customer associated with this transaction. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
type | enum: “deposit” | “withdrawal” | “onramp” | “offramp” | Yes | Transaction type. Same value as on transaction.created. |
source | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Source side of the cancelled transaction — same nested shape as GET /v2/transactions/:id. |
destination | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Destination side of the cancelled transaction. |
cancellationReason | enum: “expired” | “client_cancelled” | Yes | Whether 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. |
cancelledAt | string | Yes | ISO-8601 timestamp when the transaction was cancelled. |
linkedOrderId | string | No | Present 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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the completed transaction. |
customerId | string | Yes | Customer associated with this transaction. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
type | enum: “deposit” | “withdrawal” | “onramp” | “offramp” | Yes | Transaction type. |
status | ”completed” | Yes | Terminal status. Always completed on this event; failures fire transaction.failed instead. |
source | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Source 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. |
destination | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Destination side of the transaction. See source for the settlement-reference placement rule. |
payout | object | No | Payout rail metadata. Present only for outbound transactions. |
payout.rail | enum: “fedwire” | “rtp” | “fednow” | “swift” | null | Yes | Fiat payment rail used, if applicable. Null for crypto payouts. |
completedAt | string | Yes | ISO-8601 timestamp when the transaction completed. |
matchableOrders | array of object | No | Pending orders that can execute against the deposited funds. Only populated for inbound DEPOSIT transactions. |
matchableOrdersTruncated | boolean | No | True 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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the transaction. |
customerId | string | Yes | Customer who initiated or received this transaction. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
type | enum: “deposit” | “withdrawal” | “onramp” | “offramp” | Yes | Transaction type. |
source | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Source 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). |
destination | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Destination 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). |
linkedOrderId | string | No | Present on chained Withdrawals spawned from an Order’s autoPayout; points at the parent Order id. Absent on all other transactions. |
createdAt | string | Yes | ISO-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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the failed transaction. |
customerId | string | Yes | Customer associated with this transaction. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
type | enum: “deposit” | “withdrawal” | “onramp” | “offramp” | Yes | Transaction type. Same value as on transaction.created. |
source | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Source side of the failed transaction — same nested shape as GET /v2/transactions/:id. Populated even on terminal failures so the payload is self-contained. |
destination | object (one of: wallet, virtual_account, external_crypto, external_bank, external_bank_inbound, external_unknown) | Yes | Destination side of the failed transaction. May surface as external_unknown when failure occurred before counterparty resolution. |
failureCode | enum: “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” | No | Machine-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. |
failureMessage | string | No | Human-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). |
payout | object | No | Payout rail metadata. Present only for outbound transactions. |
payout.rail | enum: “fedwire” | “rtp” | “fednow” | “swift” | null | Yes | Fiat payment rail used, if applicable. Null for crypto payouts. |
failedAt | string | Yes | ISO-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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | ID of the transaction that reached signer quorum. |
customerId | string | Yes | Customer associated with the transaction. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | Unique ID of the rejected payout. |
customerId | string | Yes | Customer who initiated this payout. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
type | ”withdrawal” | Yes | Always withdrawal — only payouts are subject to document review. |
status | ”failed” | Yes | Public terminal status. Always failed on this event; the payout is terminal and funds are returned. |
reasonCategory | enum: “document_inadequate” | Yes | Machine-readable rejection category. document_inadequate — the submitted document could not satisfy the compliance requirement. |
acceptedDocumentTypes | array of enum: “bank_verification_letter” | “invoice” | “contract” | “payroll_register” | “investment_agreement” | “other” | Yes | Supporting-document types accepted as evidence on a resubmitted payout. Attach a document of one of these types and resubmit with a fresh idempotency key. |
rejectedAt | string | Yes | ISO-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
}
}
| Field | Type | Required | Description |
|---|
transactionId | string | Yes | ID of the transaction being signed. |
customerId | string | Yes | Customer associated with the transaction. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
walletSignerId | string | Yes | ID of the signer whose stamp was just collected. |
collected | integer | Yes | Count of distinct signer stamps gathered so far. |
required | integer | Yes | Total 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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the approved virtual account application. |
customerId | string | Yes | Customer who owns this virtual account application. |
asset | object | Yes | Asset the virtual account will hold. |
asset.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
asset.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
applicationId | string | Yes | Unique ID of the rejected virtual account application. |
customerId | string | Yes | Customer who owns this virtual account application. |
asset | object | Yes | Asset the virtual account would have held. |
asset.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
asset.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat |
failureCode | enum: “REJECTED_BY_OPS” | “COMPLIANCE_DENIED” | No | Machine-readable failure code identifying the rejection category. |
failureMessage | string | No | Human-readable message intended for display to your end user, if provided. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
virtualAccountId | string | Yes | Unique ID of the now-active virtual account. |
customerId | string | Yes | Customer who owns this virtual account. |
asset | object | Yes | Asset this virtual account holds. |
asset.code | enum: “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” | Yes | Asset code (USDC, USD, etc.) |
asset.chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | No | Chain when the asset is on-chain; omitted for fiat |
activatedAt | string | Yes | ISO-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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer the signer belongs to. |
walletSignerId | string | Yes | Unique ID of the wallet signer. |
email | string | Yes | Email address of the signer. |
role | enum: “admin” | “signer” | Yes | Roster role of the signer. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
credentialType | enum: “passkey” | “api_key” | Yes | Credential 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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer the signer belongs to. |
walletSignerId | string | Yes | Unique ID of the wallet signer. |
email | string | Yes | Email address of the signer. |
role | enum: “admin” | “signer” | Yes | Roster role of the signer. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
previousRole | ”admin” | Yes | Role 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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer the signer belongs to. |
walletSignerId | string | Yes | Unique ID of the wallet signer. |
email | string | Yes | Email address of the signer. |
role | enum: “admin” | “signer” | Yes | Roster role of the signer. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
passkeyCount | integer | No | Number 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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer the signer belongs to. |
walletSignerId | string | Yes | Unique ID of the wallet signer. |
email | string | Yes | Email address of the signer. |
role | enum: “admin” | “signer” | Yes | Roster role of the signer. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
name | string | No | Display name of the signer, if provided at invite time. |
credentialType | enum: “passkey” | “api_key” | Yes | Credential type the signer will enroll. |
verificationUrl | string | Yes | Conduit-hosted enrollment URL the fintech routes the signer to. One URL per invited signer, distributed out-of-band. |
expiresAt | string | Yes | ISO-8601 deadline — the invitation auto-expires if the signer has not enrolled by this time. |
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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer the signer belongs to. |
walletSignerId | string | Yes | Unique ID of the wallet signer. |
email | string | Yes | Email address of the signer. |
role | enum: “admin” | “signer” | Yes | Roster role of the signer. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
previousRole | ”signer” | Yes | Role 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"
}
}
| Field | Type | Required | Description |
|---|
customerId | string | Yes | Customer the signer belongs to. |
walletSignerId | string | Yes | Unique ID of the wallet signer. |
email | string | Yes | Email address of the signer. |
role | enum: “admin” | “signer” | Yes | Roster role of the signer. |
clientReferenceId | string | No | Caller-supplied external reference, if provided. |
reason | enum: “customer_removed” | “ops_removed” | Yes | Whether 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"
}
}
| Field | Type | Required | Description |
|---|
walletId | string | Yes | Unique ID of the newly created wallet. |
customerId | string | Yes | Customer who owns this wallet. |
chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | Yes | Blockchain network this wallet operates on. |
address | string | Yes | On-chain address of the wallet. |
custodyModel | enum: “custodial” | “non_custodial” | No | Which 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. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
walletId | string | Yes | Unique ID of the wallet that was rotated (now inactive). |
replacedByWalletId | string | Yes | Unique ID of the new wallet that replaced this one. |
customerId | string | Yes | Customer who owns this wallet. |
chain | enum: “ethereum” | “base” | “solana” | “polygon” | “arbitrum” | “optimism” | “avalanche” | “tron” | “stellar” | “bsc” | “bitcoin” | Yes | Blockchain network this wallet operates on. |
custodyModel | enum: “custodial” | “non_custodial” | No | Which side holds the signing key on the replacement wallet (replacedByWalletId). custodial — Conduit signs; non_custodial — the customer co-signs each payout. Omitted when unknown. |
rotatedAt | string | Yes | ISO-8601 timestamp when the wallet was rotated. |
clientReferenceId | string | No | Caller-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"
}
}
| Field | Type | Required | Description |
|---|
whitelistRecipientId | string | Yes | Unique ID of the whitelist recipient entry. |
customerId | string | Yes | Customer who owns this whitelist entry. |
rail | enum: “US” | “SWIFT” | Yes | Payment rail for this whitelist entry (US or SWIFT). |
relationship | enum: “SELF” | “GROUP_ENTITY” | Yes | Intercompany relationship between the customer and the recipient. |
status | enum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected” | Yes | Current status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id. |
holderName | string | Yes | Legal name of the account holder. |
label | string | null | Yes | Optional 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"
}
}
| Field | Type | Required | Description |
|---|
whitelistRecipientId | string | Yes | Unique ID of the whitelist recipient entry. |
customerId | string | Yes | Customer who owns this whitelist entry. |
rail | enum: “US” | “SWIFT” | Yes | Payment rail for this whitelist entry (US or SWIFT). |
relationship | enum: “SELF” | “GROUP_ENTITY” | Yes | Intercompany relationship between the customer and the recipient. |
status | enum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected” | Yes | Current status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id. |
holderName | string | Yes | Legal name of the account holder. |
label | string | null | Yes | Optional 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"
}
}
| Field | Type | Required | Description |
|---|
whitelistRecipientId | string | Yes | Unique ID of the whitelist recipient entry. |
customerId | string | Yes | Customer who owns this whitelist entry. |
rail | enum: “US” | “SWIFT” | Yes | Payment rail for this whitelist entry (US or SWIFT). |
relationship | enum: “SELF” | “GROUP_ENTITY” | Yes | Intercompany relationship between the customer and the recipient. |
status | enum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected” | Yes | Current status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id. |
holderName | string | Yes | Legal name of the account holder. |
label | string | null | Yes | Optional 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"
}
}
| Field | Type | Required | Description |
|---|
whitelistRecipientId | string | Yes | Unique ID of the whitelist recipient entry. |
customerId | string | Yes | Customer who owns this whitelist entry. |
rail | enum: “US” | “SWIFT” | Yes | Payment rail for this whitelist entry (US or SWIFT). |
relationship | enum: “SELF” | “GROUP_ENTITY” | Yes | Intercompany relationship between the customer and the recipient. |
status | enum: “pending_review” | “registered” | “suspended” | “revoked” | “rejected” | Yes | Current status of the whitelist entry. Matches the status field on GET /v2/customers/:customerId/whitelist-recipients/:id. |
holderName | string | Yes | Legal name of the account holder. |
label | string | null | Yes | Optional caller-assigned label for this entry. |