Testing payment workflows in a sandbox environment traditionally requires navigating complex setup processes and waiting for simulated payment network processing delays. Our sandbox simulation feature transforms this experience by allowing you to define specific outcomes for your test scenarios, making integration testing both faster and more reliable.

This guide walks you through using sandbox simulations to test various payment scenarios efficiently. You’ll learn how to simulate successful payments, fraud detection, insufficient funds, and other real-world scenarios without complex manual setup or extended wait times.

Understanding Sandbox Simulations

In production environments, payment processing involves asynchronous workflows, network processing windows, and various unpredictable outcomes. While our sandbox maintains this asynchronous behavior to ensure realistic testing, we’ve introduced a powerful simulation feature that gives you control over test transaction outcomes.

The core of this system is the config object with its sandbox_outcome field. Think of this as your way to instruct the sandbox exactly how a test transaction should behave, while still maintaining the proper asynchronous flow that mirrors production behavior.

Sandbox simulations maintain asynchronous processing to match production behavior. While outcomes are deterministic, they still follow realistic timing patterns to ensure your integration handles asynchronous workflows correctly.

How Simulations Work

When you create an entity (customer, paykey, charge, or payout) in the sandbox, you can include a config object that specifies your desired outcome:

{
  "config": {
    "sandbox_outcome": "paid"
  }
}

This configuration tells the sandbox to simulate a specific scenario. For example, setting "sandbox_outcome": "paid" on a charge causes it to:

  1. Process normally through the initial states
  2. Transition to scheduled ->pending status
  3. Automatically move to paid status within a few minutes

In the sandbox environment, simulated payments process every minute rather than waiting for standard network windows. This accelerates your testing cycle while maintaining realistic state transitions.

Available Simulation Outcomes

Understanding the available simulation options helps you create comprehensive test scenarios that cover all possible production behaviors. Let’s explore what you can simulate for each entity type.

Control customer verification status during creation:

OutcomeDescriptionUse Case
standardCustomer undergoes normal review processTesting standard onboarding flow
verifiedCustomer automatically becomes verifiedTesting post-verification features
rejectedCustomer automatically gets rejectedTesting rejection handling
reviewCustomer enters manual review statusTesting review queue workflows

Reversal scenarios are particularly valuable for testing reconciliation logic, as they simulate payments that initially succeed but are later reversed—a common real-world scenario that can be challenging to handle correctly.

Practical Examples

Let’s walk through complete testing scenarios, starting from creating test entities to processing payments. These examples demonstrate how to build realistic test cases from start to finish.

Prerequisites

Before testing payment flows, ensure you have:

  • Created a sandbox organization and account
  • Obtained your sandbox API credentials
  • Set up webhook endpoints for receiving notifications

Example 1: Testing Successful Payment Flow

This example demonstrates the complete happy path for payment processing.

1

Create a verified customer

First, create a customer that’s pre-verified to skip the review process:

curl --request POST \
  --header 'Authorization: Bearer YOUR_SECRET_API_KEY' \
  --url https://sandbox.straddle.io/v1/customers \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Ron Swanson",
  "type": "individual",
  "email": "user@example.com",
  "phone": "+12128675309",
  "device": {
    "ip_address": "192.168.1.1"
  },
  "config": {
     "sandbox_outcome": "verified"
  }
}'

Save the customer_id from the response—you’ll need it for the next steps.

2

Create an active paykey

Create a payment method that’s immediately active:

curl https://sandbox.straddle.io/v1/bridge/bank_account \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --header 'Straddle-Account-Id: YOUR_ACCOUNT_ID' \
  --data '{
    "customer_id": "YOUR_CUSTOMER_ID",
    "account_number": "123456789",
    "routing_number": "021000021",
    "account_type": "checking",
    "config": {
      "sandbox_outcome": "active"
    }
  }'

The response includes a paykey that you’ll use to create charges.

3

Create a successful charge

Now create a charge that will automatically succeed:

curl https://sandbox.straddle.io/v1/charges \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --data '{
  	"paykey": "<string>",
  	"description": "Monthly subscription fee",
  	"amount": "10000",
  	"currency": "<string>",
 	"payment_date": "2019-12-27",
  	"consent_type": "internet",
  	"device": {
    	"ip_address": "192.168.1.1"
  	},
  	"external_id": "<string>",
  	"config": {
    	"balance_check": "enabled",
		"sandbox_outcome": "paid"
 	}
  }'

This charge will:

  • Create with status created
  • Move to pending as it enters processing
  • Transition to paid within a few minutes
4

Monitor webhook events

Your webhook endpoint should receive these status events:

  1. created - Initial charge creation
  2. scheduled - Transaction verified and queued for originaton
  3. pending - Charge delivered to network
  4. paid - Funding complete

Example 2: Testing Fraud Detection

This scenario helps you verify your system properly handles fraud prevention measures.

1

Create test entities

Use a new customer and paykey creation process from Example 1, or reuse existing test entities.

2

Create a charge flagged for fraud

curl https://sandbox.straddle.io/v1/charges \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --data '{
  	"paykey": "<string>",
  	"description": "Monthly subscription fee",
  	"amount": "10000",
  	"currency": "USD",
 	"payment_date": "2025-05-27",
  	"consent_type": "internet",
  	"device": {
    	"ip_address": "192.168.1.1"
  	},
  	"external_id": "ABCD-4567",
  	"config": {
    	"balance_check": "enabled",
		"sandbox_outcome": "cancelled_for_fraud_risk"
 	}
  }'
3

Handle the cancellation

The charge will be immediately cancelled. Your system should:

  • Receive a cancelled webhook status
  • Display appropriate messaging to users
  • Log the fraud detection for analysis

Example 3: Testing Payment Reversals

Reversals represent a particularly challenging scenario since the payment initially succeeds. This example shows how to test your reversal handling logic.

1

Create a customer with review status

For this test, let’s create a customer that requires review:

curl --request POST \
  --header 'Authorization: Bearer YOUR_SECRET_API_KEY' \
  --url https://sandbox.straddle.io/v1/customers \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Ron Swanson",
  "type": "individual",
  "email": "user2@example.com",
  "phone": "+12128675308",
  "device": {
    "ip_address": "192.168.1.1"
  },
  "config": {
     "sandbox_outcome": "verified"
  }
}'
2

Create a paykey

curl https://sandbox.straddle.io/v1/paykeys \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --header 'Straddle-Account-Id: YOUR_ACCOUNT_ID' \
  --data '{
    "customer_id": "YOUR_CUSTOMER_ID",
    "type": "bank_account",
    "bank_account": {
      "account_number": "987654321",
      "routing_number": "021000021",
      "account_type": "checking"
    },
    "config": {
      "sandbox_outcome": "active"
    }
  }'
3

Create a charge that will reverse

curl https://sandbox.straddle.io/v1/charges \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --data '{
  	"paykey": "<string>",
  	"description": "Monthly subscription fee",
  	"amount": "10000",
  	"currency": "USD",
 	"payment_date": "2025-05-27",
  	"consent_type": "internet",
  	"device": {
    	"ip_address": "192.168.1.1"
  	},
  	"external_id": "ABCD-1234",
  	"config": {
    	"balance_check": "enabled",
		"sandbox_outcome": "reversed_insufficient_funds"
 	}
  }'
4

Track the complete lifecycle

This creates a realistic reversal scenario where:

  1. The charge processes normally
  2. Transitions to paid status
  3. Your webhook receives a success notification
  4. Minutes later, the charge reverses to reversed status
  5. Your webhook receives a reversal notification with code R01

Your system should handle both the initial success and subsequent reversal appropriately.

Example 4: Testing Daily Limit Holds

This scenario tests your ability to handle and release held payments.

# Create a charge that exceeds daily limits
curl https://sandbox.straddle.io/v1/charges \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
  --data '{
  	"paykey": "<string>",
  	"description": "Monthly subscription fee",
  	"amount": "10000",
  	"currency": "USD",
 	"payment_date": "2025-05-27",
  	"consent_type": "internet",
  	"device": {
    	"ip_address": "192.168.1.1"
  	},
  	"external_id": "ABCD-123456789",
  	"config": {
    	"balance_check": "enabled",
		"sandbox_outcome": "on_hold_daily_limit"
 	}
  }'

The charge will be created with on_hold status. You can then test releasing the hold through your admin interface or API endpoints.

Production Behavior

Understanding how sandbox simulations behave in production is crucial for safe deployment. The system is designed with multiple safeguards to prevent any simulation logic from affecting production operations.

In production environments:

  • The sandbox_outcome field is nullable at the API level
  • If included, it must be set to "standard" to pass validation
  • The field is excluded from all API responses
  • No simulation logic runs in production systems

This design ensures that:

  • Test code accidentally deployed to production won’t cause unexpected behavior
  • Production API responses remain clean and consistent
  • There’s no performance impact from simulation logic
  • Your production integration remains secure and predictable

Best Practices

To maximize the value of sandbox simulations, consider these comprehensive testing strategies.

Create Comprehensive Test Suites

Build test suites that cover every possible outcome your integration might encounter:

// Example comprehensive test suite structure
const testScenarios = [
  // Success scenarios
  { name: "Standard processing", outcome: "standard" },
  { name: "Successful payment", outcome: "paid" },
  
  // Failure scenarios
  { name: "Insufficient funds", outcome: "failed_insufficient_funds" },
  { name: "Customer dispute", outcome: "failed_customer_dispute" },
  { name: "Closed account", outcome: "failed_closed_bank_account" },
  
  // Reversal scenarios
  { name: "NSF reversal", outcome: "reversed_insufficient_funds" },
  { name: "Dispute reversal", outcome: "reversed_customer_dispute" },
  
  // Hold and cancellation scenarios
  { name: "Daily limit hold", outcome: "on_hold_daily_limit" },
  { name: "Fraud detection", outcome: "cancelled_for_fraud_risk" },
  { name: "Balance check failure", outcome: "cancelled_for_balance_check" }
];

// Run each scenario and verify your system handles it correctly
for (const scenario of testScenarios) {
  await testPaymentScenario(scenario);
  await verifySystemState(scenario.expectedState);
  await verifyWebhookHandling(scenario.expectedEvents);
}

Document Test Data

Maintain clear documentation of your test scenarios and their purposes:

// Good practice: Self-documenting test data
const createTestCharge = async (scenario) => {
  const charge = await straddle.charges.create({
    amount: scenario.amount || 5000,
    currency: "usd",
    customer_id: scenario.customerId,
    paykey: scenario.paykey,
    description: `Test: ${scenario.description}`,
    metadata: {
      test_scenario: scenario.name,
      expected_outcome: scenario.outcome,
      test_run_id: generateTestRunId(),
      tested_at: new Date().toISOString()
    },
    config: {
      sandbox_outcome: scenario.outcome
    }
  });
  
  // Log test execution for debugging
  console.log(`Created test charge ${charge.id} for scenario: ${scenario.name}`);
  return charge;
};

Troubleshooting

When simulations don’t behave as expected, check these common issues and their solutions.

While sandbox simulations make testing significantly easier, always perform end-to-end testing with standard processing before deploying to production. This ensures your integration handles the full complexity of real-world payment processing, including edge cases and timing variations that simulations might not capture.