Easy Labs
TreasuryGuides

Send a payout

Move money from your funding account to a recipient.

Goal

Initiate, confirm, and track a single payout to a US bank account over ACH, same-day ACH, RTP, or wire.

Prerequisites

  • A sandbox or production API key (sk_sandbox_… / sk_live_…).
  • @easylabs/node installed and the client constructed.
  • A linked funding bank account (visible in client.listBankAccounts()).
  • A recipient with at least one payment method — either created up-front with banking populated, or invited via the self-serve invite flow.

Implementation

1. Initiate

const { data: payout } = await client.createPayout({
  recipient_id: "rcp_123…",
  source_account_id: "ba_456…",
  amount: 12_500,         // $125.00, integer USD cents, > 0
  method: "ach",          // "ach" | "same_day_ach" | "rtp" | "wire"
  memo: "Sept. retainer", // ≤500 chars, appears on bank statement (rail-permitting)
  notes: "internal ref 9921", // ≤2000 chars, internal-only
});
// payout.status === "pending"

If your account has security rules requiring 2FA on payouts above a threshold, the response will include approval_required: true and an approval_request_id. Collect a 6-digit code from the operator and pass it on confirm.

2. Confirm

await client.confirmPayout({
  transaction_id: payout.id,
  security_code: "482917", // omit if approval_required was false
});

After confirm, the payout is handed to the rail. From here it cannot be cancelled — see Tradeoffs.

3. Track

const { data } = await client.getTransaction(payout.id);
// data.status: "pending" | "processing" | "completed" | "failed" | "returned" | "cancelled"

Once completed, fetch the rolled-up settlement:

const { data: settlement } = await client.getTransactionSettlement(payout.id);
console.log(settlement.id, settlement.net_amount);

Or subscribe to the settlement.created webhook and post the journal entry on receipt:

import { constructEvent } from "@easylabs/node";

app.post("/webhooks/easy", async (req, res) => {
  const event = constructEvent(req.rawBody, req.headers["easy-signature"], secret);
  if (event.type === "settlement.created") {
    // event.data.id, event.data.net_amount, event.data.status === "FUNDED"
  }
  res.sendStatus(200);
});

4. Cancel (only before confirm)

await client.cancelPayout({ transaction_id: payout.id });

cancelPayout is a no-op once the transaction has moved past pending.

Tradeoffs

  • Rail choice. RTP and wire are irreversible the moment they're confirmed — there is no Easy-side rollback. ACH allows up to two business days for return processing but is slower (1–3 day settlement) and cheaper. Same-day ACH funds same business day for a per-transfer fee.
  • Idempotency. createPayout is not implicitly idempotent. If you retry on a network error without checking, you may double-send. Persist the returned transaction_id before retrying, or check client.listTransactions() first.
  • Funding shortfall. If your funding account lacks sufficient balance at rail submission time, the payout will reach failed rather than completed — listen for the status change rather than assuming success after confirmPayout.

On this page