Overview
A payout service disburses funds to recipients via multiple rails: ACH (batch, 1-2 day settlement), wire (same-day, high fee), instant (real-time, via push-to-card or RTP), and third-party wallets like PayPal. Each rail has different latency, cost, and compliance requirements.
Core Schema
Recipient Table
Recipient (
id BIGINT PRIMARY KEY,
user_id BIGINT,
method ENUM('ach','wire','instant','paypal'),
account_details_encrypted TEXT,
verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP
)
Payout Table
Payout (
id BIGINT PRIMARY KEY,
recipient_id BIGINT REFERENCES Recipient(id),
amount_cents BIGINT,
currency CHAR(3),
method ENUM('ach','wire','instant','paypal'),
status ENUM('pending','processing','sent','settled','failed'),
scheduled_at TIMESTAMP,
processed_at TIMESTAMP,
external_txn_id VARCHAR(128),
failure_reason TEXT
)
ACH Batch Processing
- Collect all
pendingACH payouts scheduled for the current processing window. - Generate a NACHA-formatted file grouping entries by originating account.
- Submit the file to the originating bank via SFTP or API.
- Poll for settlement confirmation over 1-2 business days.
- On settlement, update
status = settledand setexternal_txn_id.
Instant Payout Flow
Call the payment processor API synchronously. On success, mark status = settled immediately. Instant payouts carry a higher per-transaction fee and are subject to per-recipient velocity limits to control cost.
Compliance Checks
OFAC Sanctions Screening
Before the first payout to any recipient, screen their name and account details against the OFAC SDN list. Block and flag any match for manual review. Re-screen periodically and on any recipient data change.
KYC/AML
Require verified = TRUE on the Recipient record before processing payouts above a threshold (e.g., $500 lifetime or $200 single transaction). Verification integrates with an identity provider (e.g., Persona, Stripe Identity).
Velocity Limits
Enforce per-recipient limits checked at payout creation time:
SELECT SUM(amount_cents)
FROM Payout
WHERE recipient_id = ?
AND status IN ('pending','processing','sent','settled')
AND scheduled_at >= NOW() - INTERVAL 1 DAY;
Reject payouts that would breach the daily or weekly cap.
Failure Handling and Retry
- Transient failures (network timeout, processor 5xx): exponential backoff retry with jitter, up to a configured maximum (e.g., 5 attempts over 24 hours).
- Permanent failures (invalid account number, closed account): set
status = failedimmediately, populatefailure_reason, notify recipient.
ACH Return Handling
Banks return ACH transactions with R-codes when settlement fails:
- R01 (insufficient funds): notify recipient, allow retry after 5 business days.
- R02 (account closed): mark recipient account as invalid, require recipient to update banking details.
- R10 (unauthorized): escalate to compliance for review.
Ledger Integration
Each payout event generates journal entries in the ledger service:
On payout creation:
DEBIT payout_liability amount_cents
CREDIT bank_account amount_cents
On ACH return / failure reversal:
DEBIT bank_account amount_cents
CREDIT payout_liability amount_cents
Reconciliation
A daily job matches all payouts in status = sent against the bank statement. Unmatched payouts after 3 business days trigger an alert and are escalated for manual investigation. See the Payment Reconciliation Service design for the full matching algorithm.
Scheduling and Idempotency
Payouts are inserted with a unique (recipient_id, amount_cents, scheduled_at) constraint to prevent duplicates from retry storms. The worker locks a batch of pending payouts using SELECT ... FOR UPDATE SKIP LOCKED for safe concurrent processing across multiple worker instances.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What payout methods does a payout service support?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A payout service typically supports ACH (1-2 business day settlement, low cost), wire transfer (same-day, higher fee), instant payout via push-to-card or RTP (real-time settlement, highest fee), and third-party wallets such as PayPal. Method selection depends on recipient preference, urgency, and cost tolerance.”
}
},
{
“@type”: “Question”,
“name”: “How does ACH batch processing work in a payout service?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “ACH payouts are collected into a batch, formatted as a NACHA file grouping entries by originating bank account, and submitted to the originating bank via SFTP or API. The service then polls for settlement confirmation over 1-2 business days. On settlement, each payout record is updated to settled status with the external transaction ID from the bank.”
}
},
{
“@type”: “Question”,
“name”: “How do you handle ACH return codes in a payout service?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Banks return failed ACH transactions with R-codes. R01 (insufficient funds) triggers a notification to the recipient and allows retry after 5 business days. R02 (account closed) marks the recipient's banking details as invalid and prompts them to update their information. R10 (unauthorized transaction) is escalated to the compliance team for manual review.”
}
},
{
“@type”: “Question”,
“name”: “What compliance checks does a payout service require?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Before the first payout, the recipient's name and account details are screened against the OFAC SDN sanctions list. Payouts above a threshold (for example '$500 lifetime or '$200 per transaction) require the recipient to be KYC-verified. Velocity limits cap per-recipient daily and weekly payout totals to detect and prevent abuse patterns.”
}
}
]
}
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Coinbase Interview Guide