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
availablebalance; check it first (Balances). - Memo chains — some networks require a memo/tag;
memois optional, max 100 chars. - Fees — the network fee from
options()(withdrawFeefixed, pluswithdrawFeePercent× amount —0%on most chains) is charged per suborder on top of the amount. - Sandbox — on
https://sandbox-api.absolutepay.iono real funds move; batches auto-settle after a short delay and fire the samepayout.*webhooks, so you can test the full loop.
Full request/response fields are on POST /v1/payouts in the API reference.
Next
- Off-ramp — pay out to a bank account (crypto → fiat).
- Webhooks — verify and handle
payout.*events. - Balances & reconciliation — pre-check funds, reconcile suborders.
- API reference · Authentication