curl -X POST https://sandbox.straddle.io/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"
  }'
{
  "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"
  }
}
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—a critical distinction that 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.
Payouts require a paykey - a token representing a verified bank account. Learn about connecting bank accounts in the Bridge guide.

The Payout Object

A payout represents a credit transaction with comprehensive status tracking and metadata:
{
  "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

FieldTypeDescription
idstringUnique payout identifier
paykeystringToken for recipient’s bank account
amountintegerAmount in cents (25000 = $250.00)
currencystringCurrency code (USD only)
descriptionstringStatement descriptor
payment_datestringProcessing date (YYYY-MM-DD)
statusstringCurrent payment status
status_detailsobjectDetailed status information
external_idstringYour unique reference
processed_atstringWhen sent to payment network
trace_numberstringACH network tracking number
funding_idsarrayRelated funding events

Creating a Payout

All payouts require these fields. For a complete list of fields, visit the Payouts API reference.
curl -X POST https://sandbox.straddle.io/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"
  }'
{
  "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"
  }
}

Required Parameters

paykey
string
required
Payment key for the recipient’s verified bank account. Obtained via Bridge flow.
amount
integer
required
Amount to send in cents. Must be positive. Example: 25000 for $250.00
currency
string
required
ISO 4217 currency code. Currently only "USD" is supported.
description
string
required
Description appearing on bank statements. Maximum 80 characters. Be clear and recognizable.
payment_date
string
required
Date to process payment (YYYY-MM-DD). Can be today or future-dated up to 90 days.
device
object
required
Device information for compliance tracking.
external_id
string
required
Your unique identifier for this payout. Must be unique across all payouts. Used for idempotency.

Optional Parameters

config
object
Processing configuration options.
metadata
object
Custom key-value pairs. Maximum 20 keys, 40 characters per key/value.

Payment Status Lifecycle

Payouts progress through the same statuses as charges:
StatusDescriptionCan Modify?
createdInitial state, awaiting verification✅ Yes
scheduledVerified and queued for processing✅ Yes
pendingSent to payment network❌ No
paidSuccessfully completed❌ No
failedDeclined before completion❌ Terminal
reversedReturned after completion❌ Terminal
cancelledStopped before network submission❌ Terminal
on_holdPaused for review⚠️ Depends
Critical: Once a payout reaches pending status, it has been submitted to the payment network and CANNOT be stopped, held, or cancelled.
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.

Status Details

Every status change includes detailed information:
"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

ReasonDescriptionSource
payout_refusedRecipient declined the creditbank_decline
invalid_routingRouting number doesn’t accept creditsbank_decline
frozen_bank_accountRecipient account frozenbank_decline
risk_reviewAML/compliance review requiredwatchtower
amount_too_largeExceeds payout limitssystem
For complete status details, see Payment Statuses Guide.

Common Operations

Retrieve a Payout

curl -X GET https://sandbox.straddle.io/v1/payouts/{payout_id} \
  -H "Authorization: Bearer YOUR_API_KEY"

Update a Payout

Update payouts in created or scheduled status:
curl -X PUT https://sandbox.straddle.io/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

Cancel payouts before they reach pending status:
curl -X POST https://sandbox.straddle.io/v1/payouts/{payout_id}/cancel \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Seller verification failed"
  }'

Place on Hold

Pause a payout for review (only in created or scheduled status):
curl -X POST https://sandbox.straddle.io/v1/payouts/{payout_id}/hold \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Awaiting seller verification"
  }'

Release a Hold

Release user-initiated holds only:
curl -X POST https://sandbox.straddle.io/v1/payouts/{payout_id}/release \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Seller verified"
  }'
Compliance holds (watchtower source) cannot be released via API. They require manual review in the dashboard.

Payment Rails

Straddle automatically selects the optimal payment rail based on amount, timing, and availability:
RailSpeedAvailabilityTypical Use
ACH1 business daysBusiness daysStandard payouts
Same Day ACHSame business dayBusiness daysStandard payouts
RTPSeconds24/7/365Instant payouts
FedNowSeconds24/7/365Instant payouts
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.

Handling Failures

When payouts fail, check status_details for specific information:
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:
{
  "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 to receive real-time payout updates:
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

Use Idempotency

Always include a unique external_id to prevent duplicate payouts

Verify Recipients

Confirm recipient details before large payouts

Monitor Limits

Track daily/monthly payout volumes against limits

Handle Returns

Implement workflows for rare payout returns

Next Steps