Zquence escrow holds funds (or title) between two parties until both sides confirm. A transaction has a lifecycle of precise states and emits a webhook on every transition.

Lifecycle

Prerequisites

  • Both parties must exist as users in your tenant.
  • Both parties must have completed KYC (or a reusable KYC record).

Step 1 - Create the transaction

curl https://api.zquence.com/v1/transactions/create \
  -H "x-api-key: $ZQUENCE_PUBLIC_KEY" \
  -H "x-api-secret: $ZQUENCE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $ORDER_ID" \
  -d '{
    "accountType": "real_estate",
    "title": "1972 Rolex Submariner",
    "amount": 1850000,
    "currency": "EUR",
    "sellerId": "usr_01HX3ZAB...",
    "buyerId":  "usr_01HX3ZAC...",
    "timeline": { "deliveryDays": 7, "inspectionDays": 3 }
  }'
You can pass a counterparty by userId (existing user) or email (auto-invited). If the email doesn’t resolve to a user, Zquence creates a pending user and sends an invitation.

Step 2 - Invite the counterparty

The create call returns an inviteToken - a signed URL that lets the counterparty accept without logging in first.
{
  "id": "tx_01HX3ZM...",
  "status": "invited",
  "inviteToken": "ey...",
  "inviteUrl": "https://app.zquence.ai/accept/ey...",
  ...
}
Embed inviteUrl in your notification (email, SMS, in-app). The counterparty lands on the Zquence-hosted acceptance page - no integration needed from your side.

Step 3 - Wait for acceptance

When the counterparty clicks through and agrees, you get:
{
  "id": "evt_01HX3ZN...",
  "type": "transaction.accepted",
  "tenantId": "tnt_01HX3Z8MQW...",
  "data": {
    "transactionId": "tx_01HX3ZM...",
    "acceptedBy": "usr_01HX3ZO...",
    "acceptedAt": "2026-04-22T11:02:44.000Z"
  }
}

Step 4 - Fund the escrow

Once accepted, the buyer deposits funds through your approved settlement process. After the deposit is confirmed, call:
POST /v1/transactions/tx_01HX3ZM.../mark-funded
{ "reference": "wire-ref-abc123" }
This fires transaction.status.changed with status: "funded".

Step 5 - Release or refund

Either party can request release/refund through the hosted UI, or you can drive it programmatically:
POST /v1/transactions/tx_01HX3ZM.../release
POST /v1/transactions/tx_01HX3ZM.../refund
Each fires a corresponding transaction.status.changed.

Fetching the current transaction

For a logged-in user, resolve the transaction they’re currently party to:
GET /v1/transactions/current
Returns the first transaction where userId matches either buyer.id or seller.id.

Disputes

If either party disputes, the transaction enters disputed. Your ops team (or ours, if you’ve enabled managed disputes) reviews evidence and issues a decision. Disputes emit:
  • transaction.disputed when opened
  • transaction.dispute.updated on every evidence upload
  • transaction.status.changed with released or refunded when resolved
Subscribe your webhook URL to transaction.* - the wildcard keeps you covered as new sub-events are added.