Reference for Straddle ACH payment status flows, transitions, status reasons, and return codes for charges, payouts, and funding events.
Understanding payment statuses is critical for properly handling charges and payouts. Every payment moves through a defined lifecycle with specific status transitions. Knowing when payments can be modified—or when they become unstoppable—directly impacts your application’s payment logic and user experience.
This comprehensive guide provides everything you need to handle payment statuses correctly:
Status lifecycles: The complete flow from creation to terminal states
The pending threshold: Why pending status is the point of no return
Failed vs reversed: Critical distinction between pre-funding and post-funding failures
ACH return codes: All R-codes and S-codes with their meanings and timing
Status reasons: Complete catalog of failure reasons and their sources
Status/Source/Reason matrix: Valid combinations showing what actually occurs
Implementation patterns: Code examples for monitoring and handling status changes
Whether you’re building retry logic, handling disputes, or reconciling returns, this reference ensures you respond appropriately to every payment state.
The happy path for payments follows this progression:
1
Created
Payment successfully created and awaiting verification
2
Scheduled
Payment passed verifications and risk scoring, queued for processing
3
Pending
Payment sent to network - CANNOT BE STOPPED
4
Paid
Payment successfully funded
Critical Understanding: Once a payment reaches pending status, it has been submitted to the payment network and cannot be stopped, held, or cancelled. The payment must complete its network processing.
*Only user-initiated holds can be released by the user; Watchtower holds require Straddle authorization
†Can still transition to reversed if return occurs post-funding
Retry any failed, reversed, or cancelled charge or payout by creating a new payment linked to the original. The new payment carries over the original bank information and payment details.Optional: provide a new payment_date, description, or external_id. Otherwise, Straddle generates these from the original payment.The new payment inherits the amount, paykey, currency, and metadata from the original. Each payment can only be resubmitted once, but if a resubmitted payment also fails, resubmit that one to create a chain.Resubmit a charge with an updated payment date and description.
Return funds to a customer for a paid charge directly from the dashboard or API. The refund is sent back to the customer’s original bank account using the same paykey as the source charge, so there’s no manual lookup or destination-account work.To issue a refund from the dashboard, see the step-by-step walkthrough in the help center.Each charge can be refunded once, full or partial. The refund amount must be greater than $0 and no more than the original charge amount.When you create a refund:
A new payout is created with payment_type: "payout" and a description of Refund for Charge ID: {id}. If you supplied your own description, it’s appended after the system prefix.
The original charge stays in Paid status. Refunds are tracked separately as payouts and linked through related_payments.
The new refund payout follows normal payout statuses (Pending → Paid).
After a refund, the original charge’s related_payments lists the new refund payout, and the refund payout’s related_payments lists the source charge. Each entry includes the linked payment’s id, relationship, and payment_type.Relationship values:
original: the linked payment is the source. Appears on the refund payout, pointing back to the charge.
refund: the linked payment is a refund. Appears on the original charge, pointing to the new refund payout.
Since a refund is created as a payout, the standard payout.* webhook events fire for the refund lifecycle. Existing payout handlers track refunds without changes.
The charge is not in paid status. Use Resubmit for failed, reversed, or cancelled charges.
The charge has already been refunded. Each charge can have one refund; check related_payments before retrying.
Two requests reach the API at the same time. Straddle uses optimistic concurrency to prevent duplicate refunds — one request succeeds, the other gets a 422 with a clear message.
async function handleStatusChange(payment) { const { status, status_details } = payment; switch (status) { case 'created': // Payment created, awaiting verification console.log('Payment created, will be verified soon'); break; case 'scheduled': // Passed verification, queued for processing console.log('Payment verified and scheduled'); // Last chance to cancel if needed break; case 'pending': // CRITICAL: Payment sent to network, cannot stop console.log('⚠️ Payment sent to network - CANNOT BE STOPPED'); await notifyPendingPayment(payment); break; case 'on_hold': // Check source to determine if we can release if (status_details.source === 'user_action') { console.log('User hold - can release when ready'); } else if (status_details.source === 'watchtower') { console.log('Risk hold - cannot release via API'); await notifyCompliance(payment); } break; case 'paid': // Successfully funded console.log('✓ Payment successfully funded'); await fulfillOrder(payment); // Note: Can still reverse later break; case 'failed': // Failed before funding console.log(`✗ Payment failed: ${status_details.reason}`); await handleFailure(payment); break; case 'reversed': // Returned after funding console.log(`⚠️ Payment reversed: ${status_details.reason}`); await handleReversal(payment); break; case 'cancelled': // Terminated before network submission console.log('Payment cancelled'); break; }}