# Low Level Design: Payout Service

Source: https://www.techinterview.org/post/3233471082/lld-payout-service/
Updated: 2026-06-30 · techinterview.org

## 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 `pending` ACH 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 = settled` and set `external_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 = failed` immediately, populate `failure_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.

See also: [Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems](https://www.techinterview.org/post/3233460268/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](https://www.techinterview.org/post/3233460275/scale-ai-interview-guide-2026-data-infrastructure-rlhf-pipelines-and-ml-engineering/)

See also: [Coinbase Interview Guide](https://www.techinterview.org/post/3233459920/coinbase-interview/)
