Skip to main content
Portfolio Wallet withdrawals may require customer approval (via Turnkey) before the payout transaction can be broadcast.

Approval lifecycle

At a high level:
  1. You create a withdrawal (POST /v2/portfolio-wallets/:id/withdraw).
  2. The withdrawal processor sources liquidity (unwind/bridge as needed).
  3. When ready, Braid creates a Turnkey signing activity for a payout leg.
  4. The payout transitions through broadcast + finality and becomes completed.
The canonical approval/broadcast state machine lives on payout legs:
  • payouts[].status = pending_customer_approval: a Turnkey approval is required
  • payouts[].status = pending_broadcast: approved/signed, waiting to broadcast
  • payouts[].status = broadcasted: broadcast submitted (finality pending)
  • payouts[].status = completed: terminal success

Head-of-line gating: one outstanding approval per wallet+chain

Braid enforces an EOA-compatible safety rule:
  • At most one customer approval can be outstanding per portfolioWalletId + chain.
If you create multiple withdrawals rapidly, later withdrawals may be ready for approval but intentionally wait behind the earlier approval. This is represented as:
  • withdrawal.status = queued_for_customer_approval
  • customerApprovalState = "queued"
  • blockedBy describes the blocking payout leg (when known)
  • The waiting payout leg uses payouts[].status = waiting_for_prior_approval
Once the blocking payout becomes terminal (completed / failed / cancelled / skipped), Braid wakes the next queued withdrawal and requests the next approval.

Automation patterns

Two common patterns work well:
  1. Webhook-driven:
    • Subscribe to portfolio_wallet.withdrawal.payout.status_changed.
    • When you see pending_customer_approval, prompt/approve in Turnkey.
    • When you see completed, consider the withdrawal leg finished.
  2. Polling-driven:
    • Poll GET /v2/portfolio-wallets/:id/withdrawals/:withdrawalId.
    • Use customerApprovalState to branch:
      • "required": customer action is needed now
      • "queued": you are blocked behind a prior approval; inspect blockedBy
      • "not_required": still sourcing liquidity / no approval needed
Recommended: handle webhooks when available, but keep polling as a backstop (webhooks are best-effort delivery). See Webhooks for how to register and verify webhook deliveries.