PaybytokenDocs
Customer balances
Guide · Customer Balances

Pay and withdraw from customer balances

Use available customer funds without creating another deposit address.

Internal payment

Create a Payment Intent with payment_method_types: ["customer_balance"], the exact customer, chain, asset and amount. Set confirm: true for immediate automatic capture, or capture_method: "manual" when the merchant must authorize first and capture later.

const payment = await paybytoken.paymentIntents.create(
  {
    amount: '25.00',
    currency: 'USDC',
    chain: 'base',
    customer: customer.id,
    payment_method_types: ['customer_balance'],
    capture_method: 'automatic',
    confirm: true,
    metadata: { order_id: order.id },
  },
  { idempotencyKey: `customer-balance-payment:${order.id}:v1` },
)

The chain and asset must match one available customer balance row. Do not automatically fall back to another chain or stablecoin when funds are insufficient.

Manual authorization reserves the customer's funds. Capture only while the intent is requires_capture; cancel or allow authorization expiry to release them. Fulfill only after the canonical payment_intent.succeeded event.

const authorization = await paybytoken.paymentIntents.create(
  {
    amount: '25.00',
    currency: 'USDC',
    chain: 'base',
    customer: customer.id,
    payment_method_types: ['customer_balance'],
    capture_method: 'manual',
    confirm: true,
  },
  { idempotencyKey: `authorization:${order.id}:v1` },
)

if (authorization.status === 'requires_capture') {
  await paybytoken.paymentIntents.capture(authorization.id)
}

Capture and cancellation are state-checked, but your order logic must also be idempotent. Never capture from a browser return or because an inventory system emitted the same callback twice.

External withdrawal

Read /api/v1/withdrawal-assets, display the fee and validate the exact network and destination. Create the withdrawal with an idempotency key. The response can remain pending while Chain Service funds and submits the transaction, so use customer_withdrawal.* webhooks or authenticated retrieval for final state.

const withdrawal = await paybytoken.customerWithdrawals.create(
  customer.id,
  {
    chain: 'base',
    asset: 'USDC',
    amount: '20.00',
    destination_address: destination,
    reference: withdrawalRequest.id,
  },
  { idempotencyKey: `customer-withdrawal:${withdrawalRequest.id}:v1` },
)

Show the configured fee and total debit before authorization. A transaction hash may appear before your business considers the withdrawal final; use succeeded as the terminal success state and route requires_attention to operations.

Refund

Create a Refund against the captured Payment Intent. A customer-balance refund returns to the same merchant-scoped customer balance and never accepts an arbitrary address. Keep refund processing separate from the original payment status and consume refund.* events idempotently.

const refund = await paybytoken.refunds.create(
  {
    payment_intent: payment.id,
    amount: '10.00',
    reason: 'requested_by_customer',
    metadata: { support_case_id: supportCase.id },
  },
  { idempotencyKey: `refund:${supportCase.id}:v1` },
)

See the Customer withdrawals API and Refunds API for endpoint contracts.

On this page

API Workbench

Full Explorer

Open in new tab