Complete reference for payment status flows, reasons, and return codes
Understanding payment statuses is critical for properly handling charges and payouts. Every payment moves through a defined lifecycle with specific status transitions, and 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.After a resubmit, charge and payout objects include a read-only related_payments object that maps a linked payment’s ID to its role.
original — this payment is a resubmit. The key is the prior payment’s ID.
resubmit — a resubmit was created from this payment. The key is the new payment’s ID.
When no resubmit link exists, related_payments is omitted or null. Straddle sets this field from the link between the two payments; don’t include it in the resubmit POST body. The field also appears on subsequent GET responses and in webhook payloads.
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; }}