AbsolutePayDocs

Send payouts

Pay out to one or more addresses in a single idempotent batch, and track each recipient.

A payout sends funds from your workspace balance to external addresses. You submit a batch of one or more recipients in one call; each recipient is a suborder you track to completion. The batch is accepted synchronously (202) and settles asynchronously — you learn the outcome via a payout.settled / payout.partial / payout.failed webhook.

Before you start: the key needs payouts:write (reads need payouts:read), and the batch draws from your available balance (Balances). Amounts are decimal strings (e.g. "25.00").

1. Check options first

Fees, minimums, and which chains a currency supports:

const { options } = await ap.payouts.options({ currency: "USDT" });
// options: [{ currency, chain, label, withdrawFee, withdrawFeePercent, minWithdraw, maxWithdraw, minConfirm }, …]
options = ap.payouts.options(currency="USDT")["options"]
# options: [{ currency, chain, label, withdrawFee, withdrawFeePercent, minWithdraw, maxWithdraw, minConfirm }, …]
res, err := ap.Payouts.Options(ctx, "USDT")
if err != nil {
	log.Fatal(err)
}
options := res["options"]
// options: [{ currency, chain, label, withdrawFee, withdrawFeePercent, minWithdraw, maxWithdraw, minConfirm }, …]
curl "https://api.absolutepay.io/v1/payouts/options?currency=USDT" \
  -H "Authorization: Bearer $APP_TOKEN" $(sign GET "/v1/payouts/options?currency=USDT" "")

Validate every recipient's address + chain against these options before you submit — an invalid address or unsupported chain fails that suborder.

2. Create the batch

All three SDKs take an idempotency key on create; it becomes the batch reference (merchantBatchNo) you poll later.

const batch = await ap.payouts.create(
  {
    items: [
      { recipientAddress: "0xabc…", chain: "MATIC", amount: { amount: "25.00", currency: "USDT" }, memo: "June payout" },
      // …more recipients in the same batch
    ],
  },
  { idempotencyKey: "payout_2026_07_03_001" },
);
console.log(batch.merchantBatchNo, batch.status); // "payout_2026_07_03_001", "PENDING"
batch = ap.payouts.create(
    [
        {"recipientAddress": "0xabc…", "chain": "MATIC", "amount": {"amount": "25.00", "currency": "USDT"}, "memo": "June payout"},
        # …more recipients in the same batch
    ],
    idempotency_key="payout_2026_07_03_001",
)
print(batch["merchantBatchNo"], batch["status"])  # "payout_2026_07_03_001", "PENDING"
batch, err := ap.Payouts.Create(ctx,
	[]absolutepay.PayoutItem{
		{RecipientAddress: "0xabc…", Chain: "MATIC", Amount: absolutepay.Money{Amount: "25.00", Currency: "USDT"}, Memo: "June payout"},
		// …more recipients in the same batch
	},
	absolutepay.WithIdempotencyKey("payout_2026_07_03_001"),
)
if err != nil {
	log.Fatal(err)
}
fmt.Println(batch["merchantBatchNo"], batch["status"]) // "payout_2026_07_03_001", "PENDING"
BODY='{"items":[{"recipientAddress":"0xabc…","chain":"MATIC","amount":{"amount":"25.00","currency":"USDT"},"memo":"June payout"}]}'
curl https://api.absolutepay.io/v1/payouts \
  -H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout_2026_07_03_001" \
  $(sign POST /v1/payouts "$BODY") -d "$BODY"
{ "merchantBatchNo": "payout_2026_07_03_001", "status": "PENDING", "subOrders": [ { "recipientAddress": "0xabc…", "status": "PENDING" } ] }

Always send an idempotency key

Network retries happen. Send a unique key per batch so a retried request never pays twice — the same key returns the original batch instead of creating a new one. Generate it once per batch and reuse it on every retry. Keep keys to letters, digits, and underscores, and short (they're normalized to [A-Za-z0-9_] and truncated, so two long keys that only differ near the end could collide).

The payout rail is controlled — 202 means accepted, not sent

Batches accrue as PENDING and are submitted on-chain asynchronously; depending on your workspace's review status they can be held before dispatch. An unusual burst of payout batches in a short window automatically freezes the account for review (429 velocity_frozen). Design around the webhook and status poll, never around the create response.

3. Track settlement

A batch moves PENDING → SUBMITTED → PROCESSING → SUCCESS | PARTIAL | FAILED, and each recipient settles independently. Watch the webhooks — payout.settled (all succeeded), payout.partial (some failed), payout.failed (batch failed) — and poll the batch by its merchantBatchNo as backup:

const batch = await ap.payouts.get("payout_2026_07_03_001");
for (const s of batch.subOrders) console.log(s.recipientAddress, s.status);
batch = ap.payouts.get("payout_2026_07_03_001")
for s in batch["subOrders"]:
    print(s["recipientAddress"], s["status"])
batch, err := ap.Payouts.Get(ctx, "payout_2026_07_03_001")
if err != nil {
	log.Fatal(err)
}
for _, s := range batch["subOrders"].([]any) {
	sub := s.(map[string]any)
	fmt.Println(sub["recipientAddress"], sub["status"])
}
curl "https://api.absolutepay.io/v1/payouts/payout_2026_07_03_001" \
  -H "Authorization: Bearer $APP_TOKEN" $(sign GET /v1/payouts/payout_2026_07_03_001 "")

On PARTIAL, reconcile per suborder — each carries its own status, txid, and delivered amount/fee.

Gotchas

  • Payouts are irreversible. Once a suborder is on-chain there is no recall and no chargeback — validate addresses before you send.
  • Balance — the batch draws from available balance; check it first (Balances).
  • Memo chains — some networks require a memo/tag; memo is optional, max 100 chars.
  • Fees — the network fee from options() (withdrawFee fixed, plus withdrawFeePercent × amount — 0% on most chains) is charged per suborder on top of the amount.
  • Sandbox — on https://sandbox-api.absolutepay.io no real funds move; batches auto-settle after a short delay and fire the same payout.* webhooks, so you can test the full loop.

Full request/response fields are on POST /v1/payouts in the API reference.

Next

On this page