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.

What This Reference Covers

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 20+ failure reasons and their sources
  • 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.

Payment Lifecycle Overview

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.

Status Values

Complete Status Reference

StatusDescriptionCan Modify?Terminal?
createdPayment created, awaiting verificationNo
scheduledVerified and queued for processingNo
pendingSent to networkNo
on_holdPaused for review✅*No
paidSuccessfully fundedNo†
failedDeclined/returned before fundingYes
reversedReturned after funding completedYes
cancelledTerminated before network submissionYes
*Only user-initiated holds can be released
†Can still transition to reversed if return occurs post-funding

Understanding Failed vs Reversed

StatusWhen It OccursMoney MovementExample
failedBefore funding completesNo money movedNSF during processing
reversedAfter funding completesMoney moved, then returnedDispute filed after payment

Status Reasons

Complete Reason Reference

Reason CodeDescriptionTypical Source
okNormal/successful statussystem
insufficient_fundsCustomer’s account has insufficient fundsbank_decline
closed_bank_accountBank account is closedbank_decline
invalid_bank_accountAccount cannot be located or is invalidbank_decline
invalid_routingInvalid ACH routing numberbank_decline
frozen_bank_accountAccount frozen or OFAC holdbank_decline
owner_deceasedAccount owner is deceasedbank_decline
disputedCustomer notified bank payment was unauthorizedcustomer_dispute
payment_stoppedCustomer placed stop payment orderbank_decline
risk_reviewPayment under review by Straddlewatchtower
fraudulentDeclined due to suspected fraudwatchtower
payment_blockedWatchtower rule prevented originationwatchtower
invalid_paykeyPaykey blocked due to previous failureswatchtower
amount_too_largeExceeds maximum allowed amountsystem
too_many_attemptsDaily transaction limit exceededsystem
duplicate_entryDuplicate transaction detectedsystem
internal_system_errorInternal system errorsystem
user_requestUser-requested actionuser_action
other_network_returnOther network return codesbank_decline
payout_refusedReceiver refused creditbank_decline

Status Sources

Sources identify where status changes originate:
SourceDescriptionExamples
systemSystem-generated statusNormal processing, limits
watchtowerInternal fraud/risk systemRisk holds, fraud detection
bank_declineBank or network declinedNSF, closed account
customer_disputeCustomer disputed paymentUnauthorized claim
user_actionManual user actionUser-initiated holds

ACH Return Codes

Standard ACH Returns (R-Codes)

CodeReasonMessagePre/Post Funding
R01insufficient_fundsThe customer’s account has insufficient fundsBoth
R09insufficient_fundsUncollected fundsBoth
R02closed_bank_accountThe bank account is closedBoth
R03invalid_bank_accountThe bank account could not be locatedPre
R04invalid_bank_accountBank account number invalid (missing digits)Pre
R13invalid_routingInvalid ACH routing numberPre
R20invalid_bank_accountAccount not eligible for transaction activityPre
R05disputedDebit to consumer account using corporate consentPost
R07disputedAuthorization revoked by customerPost
R10disputedCustomer advises not authorizedPost
R11disputedCheck truncation entry returnPost
R29disputedCorporate customer notified bank unauthorizedPost
R08payment_stoppedCustomer placed stop payment orderBoth
R14owner_deceasedRepresentative payee deceasedBoth
R15owner_deceasedBeneficiary/account holder deceasedBoth
R16frozen_bank_accountAccount frozen or OFAC holdBoth
R23payout_refusedReceiver refused creditPre
R24duplicate_entryDuplicate entryPre
R06+other_network_returnVarious other return reasonsBoth

Straddle Internal Codes (S-Codes)

CodeReasonMessageWhen Applied
S01payment_blockedInvalid routing numberPre-origination
S02payment_blockedKnown bad account numberPre-origination
S10payment_blockedInvalid account numberPre-origination
S11payment_blockedPrevious R02, R03, R04, R16, or R20Pre-origination
S12payment_blockedPrevious R05, R07, R08, R10, R11, or R29Pre-origination
S13payment_blockedInvalid ODFI credentialsPre-origination

Status Details Structure

Every payment includes detailed status information:
{
  "status": "failed",
  "status_details": {
    "message": "The customer's account has insufficient funds to cover this payment.",
    "reason": "insufficient_funds",
    "source": "bank_decline",
    "code": "R01",
    "changed_at": "2024-10-02T14:30:00Z"
  },
  "status_history": [
    {
      "status": "created",
      "message": "Payment successfully created and awaiting verification.",
      "reason": "ok",
      "source": "system",
      "changed_at": "2024-10-01T10:00:00Z"
    },
    {
      "status": "scheduled",
      "message": "Payment successfully validated and scheduled.",
      "reason": "ok",
      "source": "system",
      "changed_at": "2024-10-01T10:05:00Z"
    },
    {
      "status": "pending",
      "message": "Payment successfully originated to network.",
      "reason": "ok",
      "source": "system",
      "changed_at": "2024-10-01T14:00:00Z"
    },
    {
      "status": "failed",
      "message": "The customer's account has insufficient funds.",
      "reason": "insufficient_funds",
      "source": "bank_decline",
      "code": "R01",
      "changed_at": "2024-10-02T14:30:00Z"
    }
  ]
}

Handling Status Changes

Monitoring for Status Changes

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;
  }
}

Handling Specific Failure Reasons

async function handlePaymentFailure(payment) {
  const { reason, code, source } = payment.status_details;
  
  // Determine if retry is possible
  const retryableReasons = [
    'insufficient_funds',
    'duplicate_entry',
    'internal_system_error'
  ];
  
  const nonRetryableReasons = [
    'closed_bank_account',
    'invalid_bank_account',
    'disputed',
    'payment_stopped',
    'owner_deceased',
    'frozen_bank_account',
    'fraudulent'
  ];
  
  if (retryableReasons.includes(reason)) {
    // Can retry after addressing issue
    await scheduleRetry(payment);
  } else if (nonRetryableReasons.includes(reason)) {
    // Cannot retry - need new payment method or investigation
    await requestNewPaymentMethod(payment.customer_id);
  }
  
  // Log return code for reconciliation
  if (code) {
    await logReturnCode(payment.id, code, reason);
  }
}

Common Scenarios

Successful Payment Flow

  1. created - Payment initialized
  2. scheduled - Verification passed, risk approved
  3. pending - Sent to ACH network
  4. paid - Funds successfully transferred

NSF Before Funding

  1. createdscheduledpending
  2. Bank returns with R01
  3. Status changes to failed with insufficient_funds reason
  4. No money moved

Dispute After Funding

  1. createdscheduledpendingpaid
  2. Customer disputes with their bank
  3. Bank sends R10 return
  4. Status changes from paid to reversed
  5. Money was moved, then returned

Risk Hold and Release

  1. created - Payment initialized
  2. Watchtower flags for review
  3. on_hold with risk_review reason
  4. Manual review in dashboard
  5. If approved: on_holdscheduled → normal flow
  6. If declined: on_holdcancelled

Best Practices

Monitor Pending Status

Once pending, payments cannot be stopped. Plan accordingly.

Check Status Details

Always examine status_details for specific reasons and codes.

Handle Failed vs Reversed

Different workflows for pre-funding failures vs post-funding reversals.

Track Return Codes

Log ACH return codes for reconciliation and pattern analysis.

Next Steps