> ## Documentation Index
> Fetch the complete documentation index at: https://docs.straddle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payouts: send funds to bank accounts

> Use Straddle payouts to send ACH disbursements, refunds, and marketplace settlements to customer bank accounts with multi-rail processing and tracking.

Payouts are credit transactions that push funds to a customer's bank account. Unlike charges, which pull funds from customers, payouts require Straddle to withdraw funds from your account before sending them to recipients. This distinction affects both funding timing and cancellation windows.

## What You'll Learn

This guide provides complete coverage of the payout workflow:

* Creating payouts with proper authentication and device tracking
* Understanding the payout status lifecycle and the critical `pending` threshold
* Managing payouts through updates, holds, and cancellations before network submission
* Handling payout-specific failures like recipient refusals and frozen accounts
* Working with multiple payment rails (ACH, Same Day ACH, RTP, FedNow)
* Testing payout scenarios using sandbox outcomes

Use payouts for marketplace settlements, refunds, rewards, disbursements, or withdrawals—any scenario where you need to send money to verified bank accounts.

<Note>
  Payouts require a `paykey` - a token representing a verified bank account. Learn about connecting bank accounts in the [Bridge guide](/guides/bridge/overview).
</Note>

## The Payout Object

A payout represents a credit transaction with comprehensive status tracking and metadata:

```json theme={null}
{
  "id": "po_abc123def",
  "paykey": "pk_xyz789abc",
  "amount": 25000,
  "currency": "USD",
  "description": "Marketplace payout - October sales",
  "payment_date": "2024-10-15",
  "status": "paid",
  "status_details": {
    "message": "Payout successfully completed",
    "reason": "ok",
    "source": "system",
    "code": null,
    "changed_at": "2024-10-16T09:00:00Z"
  },
  "external_id": "payout_789",
  "device": {
    "ip_address": "192.168.1.1"
  },
  "config": {
    "sandbox_outcome": null
  },
  "metadata": {
    "seller_id": "seller_123",
    "period": "2024-10"
  },
  "payment_rail": "ACH",
  "paykey_details": {
    "bank_name": "Wells Fargo",
    "last4": "4321",
    "account_type": "checking"
  },
  "customer_details": {
    "name": "Jane Smith",
    "email": "jane@example.com"
  },
  "trace_number": "071000301234568",
  "funding_ids": ["fe_def456"],
  "status_history": [...],
  "created_at": "2024-10-15T10:00:00Z",
  "updated_at": "2024-10-16T09:00:00Z",
  "processed_at": "2024-10-15T14:00:00Z"
}
```

### Core Fields

| Field            | Type    | Description                        |
| ---------------- | ------- | ---------------------------------- |
| `id`             | string  | Unique payout identifier           |
| `paykey`         | string  | Token for recipient's bank account |
| `amount`         | integer | Amount in cents (25000 = \$250.00) |
| `currency`       | string  | Currency code (USD only)           |
| `description`    | string  | Statement descriptor               |
| `payment_date`   | string  | Processing date (YYYY-MM-DD)       |
| `status`         | string  | Current payment status             |
| `status_details` | object  | Detailed status information        |
| `external_id`    | string  | Your unique reference              |
| `processed_at`   | string  | When sent to payment network       |
| `trace_number`   | string  | ACH network tracking number        |
| `funding_ids`    | array   | Related funding events             |

## Creating a Payout

All payouts require these fields. For a complete list of fields, visit the [Payouts API reference](/api-reference/payouts/create).

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://sandbox.straddle.com/v1/payouts \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "paykey": "pk_xyz789abc",
      "amount": 25000,
      "currency": "USD",
      "description": "Marketplace payout - October sales",
      "payment_date": "2024-10-15",
      "device": {
        "ip_address": "192.168.1.1"
      },
      "external_id": "payout_789"
    }'
  ```

  ```javascript Node.js theme={null}
  const payout = await straddle.payouts.create({
    paykey: "pk_xyz789abc",
    amount: 25000,
    currency: "USD",
    description: "Marketplace payout - October sales",
    payment_date: "2024-10-15",
    device: {
      ip_address: "192.168.1.1"
    },
    external_id: "payout_789"
  });
  ```

  ```python Python theme={null}
  payout = straddle.payouts.create(
      paykey="pk_xyz789abc",
      amount=25000,
      currency="USD",
      description="Marketplace payout - October sales",
      payment_date="2024-10-15",
      device={
          "ip_address": "192.168.1.1"
      },
      external_id="payout_789"
  )
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "meta": {
      "api_request_id": "3a2b1c4d-0e6f-4a88-9876-123456abcdef",
      "api_request_timestamp": "2024-10-15T10:00:00Z"
    },
    "response_type": "object",
    "data": {
      "id": "po_abc123def",
      "paykey": "pk_xyz789abc",
      "amount": 25000,
      "currency": "USD",
      "description": "Marketplace payout - October sales",
      "payment_date": "2024-10-15",
      "status": "created",
      "status_details": {
        "message": "Payout successfully created and awaiting validation.",
        "reason": "ok",
        "source": "system",
        "changed_at": "2024-10-15T10:00:00Z"
      },
      "external_id": "payout_789",
      "device": {
        "ip_address": "192.168.1.1"
      },
      "config": {},
      "metadata": {},
      "created_at": "2024-10-15T10:00:00Z",
      "updated_at": "2024-10-15T10:00:00Z"
    }
  }
  ```

  ```json 400 Bad Request theme={null}
  {
    "meta": {
      "api_request_id": "f1b1b1b1-1b1b-1b1b-1b1b-1b1b1b1b1b1b"
    },
    "response_type": "error",
    "error": {
      "status": 400,
      "type": "validation_error",
      "title": "Invalid Input Data",
      "detail": "The request contains invalid field values.",
      "items": [
        {
          "reference": "paykey",
          "detail": "The provided paykey is invalid or expired."
        }
      ]
    }
  }
  ```
</ResponseExample>

## Required Parameters

<ParamField body="paykey" type="string" required>
  Payment key for the recipient's verified bank account. Obtained via Bridge flow.
</ParamField>

<ParamField body="amount" type="integer" required>
  Amount to send in cents. Must be positive. Example: `25000` for \$250.00
</ParamField>

<ParamField body="currency" type="string" required>
  ISO 4217 currency code. Currently only `"USD"` is supported.
</ParamField>

<ParamField body="description" type="string" required>
  Description appearing on bank statements. Maximum 80 characters. Be clear and recognizable.
</ParamField>

<ParamField body="payment_date" type="string" required>
  Date to process payment (YYYY-MM-DD). Can be today or future-dated up to 90 days.
</ParamField>

<ParamField body="device" type="object" required>
  Device information for compliance tracking.

  <Expandable title="Device object">
    <ParamField body="ip_address" type="string" required>
      IP address when payout was authorized. Use `"0.0.0.0"` for server-initiated payouts.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="external_id" type="string" required>
  Your unique identifier for this payout. Must be unique across all payouts. Used for idempotency.
</ParamField>

## Optional Parameters

<ParamField body="config" type="object">
  Processing configuration options.

  <Expandable title="Config object">
    <ParamField body="sandbox_outcome" type="string">
      For testing only. Simulates specific outcomes in sandbox environment.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="metadata" type="object">
  Custom key-value pairs. Maximum 20 keys, 40 characters per key/value.
</ParamField>

## Payment Status Lifecycle

Payouts progress through the same statuses as charges:

| Status      | Description                          | Can Modify? |
| ----------- | ------------------------------------ | ----------- |
| `created`   | Initial state, awaiting verification | ✅ Yes       |
| `scheduled` | Verified and queued for processing   | ✅ Yes       |
| `pending`   | Sent to payment network              | ❌ No        |
| `paid`      | Successfully completed               | ❌ No        |
| `failed`    | Declined before completion           | ❌ Terminal  |
| `reversed`  | Returned after completion            | ❌ Terminal  |
| `cancelled` | Stopped before network submission    | ❌ Terminal  |
| `on_hold`   | Paused for review                    | ⚠️ Depends  |

<Warning>
  **Critical**: Once a payout reaches `pending` status, it has been submitted to the payment network and CANNOT be stopped, held, or cancelled.
</Warning>

<Note>
  **Important Distinction**: Unlike charges which pull funds, payouts require Straddle to withdraw funds from your account BEFORE sending them to recipients. This affects funding event timing.
</Note>

### Status Details

Every status change includes detailed information:

```json theme={null}
"status_details": {
  "message": "Payout failed - recipient refused the payment",
  "reason": "payout_refused",
  "source": "bank_decline",
  "code": "R23",
  "changed_at": "2024-10-16T14:30:00Z"
}
```

### Payout-Specific Failure Reasons

| Reason                | Description                           | Source         |
| --------------------- | ------------------------------------- | -------------- |
| `payout_refused`      | Recipient declined the credit         | `bank_decline` |
| `invalid_routing`     | Routing number doesn't accept credits | `bank_decline` |
| `frozen_bank_account` | Recipient account frozen              | `bank_decline` |
| `risk_review`         | AML/compliance review required        | `watchtower`   |
| `amount_too_large`    | Exceeds payout limits                 | `system`       |

For complete status details, see [Payment Statuses Guide](/guides/payments/statuses).

## Common Operations

### [Retrieve a Payout](/api-reference/payouts/retrieve)

```bash theme={null}
curl -X GET https://sandbox.straddle.com/v1/payouts/{payout_id} \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### [Update a Payout](/api-reference/payouts/update)

Update payouts in `created` or `scheduled` status:

```bash theme={null}
curl -X PUT https://sandbox.straddle.com/v1/payouts/{payout_id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 30000,
    "description": "Updated payout amount"
  }'
```

### [Cancel a Payout](/api-reference/payouts/cancel)

Cancel payouts before they reach `pending` status:

```bash theme={null}
curl -X POST https://sandbox.straddle.com/v1/payouts/{payout_id}/cancel \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Seller verification failed"
  }'
```

### [Place on Hold](/api-reference/payouts/hold)

Pause a payout for review (only in `created` or `scheduled` status):

```bash theme={null}
curl -X POST https://sandbox.straddle.com/v1/payouts/{payout_id}/hold \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Awaiting seller verification"
  }'
```

### [Release a Hold](/api-reference/payouts/release)

Release user-initiated holds only:

```bash theme={null}
curl -X POST https://sandbox.straddle.com/v1/payouts/{payout_id}/release \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Seller verified"
  }'
```

<Note>
  Compliance holds (`watchtower` source) cannot be released via API. They require manual review in the dashboard.
</Note>

### Resubmit a Payout

[Re-attempt](/guides/payments/statuses#resubmit-failed-payments) a payout using the [resubmit](https://straddle.dev/api-reference#tag/payout/POST/v1/payouts/\{id}/resubmit) endpoint. This creates a new transaction that inherits the original payment's details.

```shellscript theme={null}
curl -X POST https://api.straddle.com/v1/payouts/{id}/resubmit \
     -H "Authorization: Bearer YOUR_SECRET_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "payment_date": "2026-03-01",
       "description": "Retry the payout",
       "external_id": "my-custom-external-id"
     }'
```

## Payment Rails

Straddle automatically selects the optimal payment rail based on amount, timing, and availability:

| Rail         | Speed             | Availability  | Typical Use      |
| ------------ | ----------------- | ------------- | ---------------- |
| ACH          | 1 business days   | Business days | Standard payouts |
| Same Day ACH | Same business day | Business days | Standard payouts |
| RTP          | Seconds           | 24/7/365      | Instant payouts  |
| FedNow       | Seconds           | 24/7/365      | Instant payouts  |

<Tip>
  Straddle automatically optimizes rail selection. You don't need to specify which rail to use - we'll choose the fastest, most cost-effective option available.
</Tip>

## Handling Failures

When payouts fail, check `status_details` for specific information:

```javascript theme={null}
if (payout.status === 'failed') {
  switch(payout.status_details.reason) {
    case 'payout_refused':
      // Recipient declined - investigate
      break;
    case 'invalid_bank_account':
      // Request updated bank details
      break;
    case 'frozen_bank_account':
      // Cannot retry - handle specially
      break;
    case 'risk_review':
      // Compliance review needed
      break;
    case 'amount_too_large':
      // Split into smaller payouts
      break;
  }
}
```

## Testing in Sandbox

Use `sandbox_outcome` in the config to simulate scenarios:

```json theme={null}
{
  "paykey": "pk_test456",
  "amount": 15000,
  "config": {
    "sandbox_outcome": "paid"  // or "failed_closed_bank_account", etc.
  }
  // ... other required fields
}
```

Common test scenarios:

* `"paid"` - Successful payout
* `"failed_closed_bank_account"` - Account closed
* `"on_hold_daily_limit"` - Compliance hold
* `"failed_payout_refused"` - Recipient refusal

## Webhooks

Set up [webhooks](/webhooks/overview/101) to receive real-time payout updates:

```javascript theme={null}
app.post('/webhooks/straddle', (req, res) => {
  const event = req.body;
  
  switch(event.type) {
    case 'payout.created':
      // New payout created
      break;
    case 'payout.paid':
      // Payout successful - update records
      break;
    case 'payout.failed':
      // Check status_details.reason
      break;
    case 'payout.on_hold':
      // Check if compliance hold
      if (event.data.status_details.source === 'watchtower') {
        // Notify compliance team
      }
      break;
  }
  
  res.status(200).send('OK');
});
```

## Best Practices

<CardGroup cols={2}>
  <Card icon="fingerprint" title="Use Idempotency">
    Always include a unique `external_id` to prevent duplicate payouts
  </Card>

  <Card icon="user-check" title="Verify Recipients">
    Confirm recipient details before large payouts
  </Card>

  <Card icon="chart-line" title="Monitor Limits">
    Track daily/monthly payout volumes against limits
  </Card>

  <Card icon="rotate-left" title="Handle Returns">
    Implement workflows for rare payout returns
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card icon="code" href="/api-reference/payouts/create" title="API Reference">
    Complete payout endpoint documentation
  </Card>

  <Card icon="list-check" href="/guides/payments/statuses" title="Payment Statuses">
    Detailed status flows and return codes
  </Card>

  <Card icon="arrow-down-to-line" href="/guides/payments/charges" title="Charges Guide">
    Learn about collecting payments
  </Card>

  <Card icon="building-columns" href="/guides/payments/funding" title="Funding Events">
    Understand settlement and money movement
  </Card>
</CardGroup>
