curl -X POST https://sandbox.straddle.io/v1/charges \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paykey": "pk_abc123xyz",
    "amount": 10000,
    "currency": "USD",
    "description": "Monthly subscription - October 2024",
    "payment_date": "2024-10-01",
    "consent_type": "internet",
    "device": {
      "ip_address": "192.168.1.1"
    },
    "external_id": "inv_12345",
    "config": {
      "balance_check": "enabled"
    }
  }'
{
  "meta": {
    "api_request_id": "3a2b1c4d-0e6f-4a88-9876-123456abcdef",
    "api_request_timestamp": "2024-10-01T10:00:00Z"
  },
  "response_type": "object",
  "data": {
    "id": "ch_def456abc",
    "paykey": "pk_abc123xyz",
    "amount": 10000,
    "currency": "USD",
    "description": "Monthly subscription - October 2024",
    "payment_date": "2024-10-01",
    "status": "created",
    "status_details": {
      "message": "Payment successfully created and awaiting validation.",
      "reason": "ok",
      "source": "system",
      "changed_at": "2024-10-01T10:00:00Z"
    },
    "external_id": "inv_12345",
    "consent_type": "internet",
    "device": {
      "ip_address": "192.168.1.1"
    },
    "config": {
      "balance_check": "enabled"
    },
    "metadata": {},
    "created_at": "2024-10-01T10:00:00Z",
    "updated_at": "2024-10-01T10:00:00Z"
  }
}
Charges are debit transactions that pull funds from a customer’s bank account through the ACH network. This guide covers the complete charge lifecycle—from creation through settlement—including status management, balance verification, and handling returns.

What You’ll Learn

This comprehensive guide will walk you through:
  • Creating charges with required fields and optional configurations
  • Understanding the payment status lifecycle and when charges can be modified
  • Implementing balance verification to reduce NSF failures
  • Handling failures and returns with specific reason codes
  • Managing charges through hold, release, and cancellation operations
  • Testing charge scenarios in sandbox before production
Whether you’re implementing subscription billing, one-time purchases, invoice collection, or account funding, this guide provides the patterns and best practices for successful charge implementation.
Charges require a paykey - a token representing a verified bank account. Learn about connecting bank accounts in the Bridge guide.

The Charge Object

A charge represents a debit transaction with comprehensive status tracking and metadata:
{
  "id": "ch_def456abc",
  "paykey": "pk_abc123xyz",
  "amount": 10000,
  "currency": "USD",
  "description": "Monthly subscription - October 2024",
  "payment_date": "2024-10-01",
  "status": "paid",
  "status_details": {
    "message": "Payment successfully completed",
    "reason": "ok",
    "source": "system",
    "code": null,
    "changed_at": "2024-10-03T09:00:00Z"
  },
  "external_id": "inv_12345",
  "consent_type": "internet",
  "device": {
    "ip_address": "192.168.1.1"
  },
  "config": {
    "balance_check": "enabled"
  },
  "metadata": {
    "order_id": "ord_789",
    "customer_ref": "cust_456"
  },
  "payment_rail": "ACH",
  "paykey_details": {
    "bank_name": "Chase Bank",
    "last4": "6789",
    "account_type": "checking"
  },
  "customer_details": {
    "name": "John Doe",
    "email": "john@example.com"
  },
  "trace_number": "071000301234567",
  "funding_ids": ["fe_abc123"],
  "status_history": [...],
  "created_at": "2024-10-01T10:00:00Z",
  "updated_at": "2024-10-03T09:00:00Z"
}

Core Fields

FieldTypeDescription
idstringUnique charge identifier
paykeystringToken for customer’s bank account
amountintegerAmount in cents (10000 = $100.00)
currencystringCurrency code (USD only)
descriptionstringStatement descriptor
payment_datestringProcessing date (YYYY-MM-DD)
statusstringCurrent payment status
status_detailsobjectDetailed status information
external_idstringYour unique reference
consent_typestringHow consent was obtained
trace_numberstringACH network tracking number
funding_idsarrayRelated settlement events

Creating a Charge

View detailed schema and request information on the Create a Charge API Reference
curl -X POST https://sandbox.straddle.io/v1/charges \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paykey": "pk_abc123xyz",
    "amount": 10000,
    "currency": "USD",
    "description": "Monthly subscription - October 2024",
    "payment_date": "2024-10-01",
    "consent_type": "internet",
    "device": {
      "ip_address": "192.168.1.1"
    },
    "external_id": "inv_12345",
    "config": {
      "balance_check": "enabled"
    }
  }'
{
  "meta": {
    "api_request_id": "3a2b1c4d-0e6f-4a88-9876-123456abcdef",
    "api_request_timestamp": "2024-10-01T10:00:00Z"
  },
  "response_type": "object",
  "data": {
    "id": "ch_def456abc",
    "paykey": "pk_abc123xyz",
    "amount": 10000,
    "currency": "USD",
    "description": "Monthly subscription - October 2024",
    "payment_date": "2024-10-01",
    "status": "created",
    "status_details": {
      "message": "Payment successfully created and awaiting validation.",
      "reason": "ok",
      "source": "system",
      "changed_at": "2024-10-01T10:00:00Z"
    },
    "external_id": "inv_12345",
    "consent_type": "internet",
    "device": {
      "ip_address": "192.168.1.1"
    },
    "config": {
      "balance_check": "enabled"
    },
    "metadata": {},
    "created_at": "2024-10-01T10:00:00Z",
    "updated_at": "2024-10-01T10:00:00Z"
  }
}

Required Parameters

paykey
string
required
Payment key for the customer’s verified bank account. Obtained via Bridge flow.
amount
integer
required
Amount to charge in cents. Must be positive. Example: 10000 for $100.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.
How customer consent was obtained:
  • "internet" - Online or mobile app authorization
  • "signed" - Signed agreement or contract
  • "telephone" - Phone authorization
device
object
required
Customer device information for fraud prevention.
external_id
string
required
Your unique identifier for this charge. Must be unique across all charges. Used for idempotency.
config
object
required
Processing configuration options.

Optional Parameters

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

Payment Status Lifecycle

Charges progress through these statuses:
StatusDescriptionCan Modify?
createdInitial state, awaiting verification✅ Yes
scheduledVerified and queued for processing✅ Yes
pendingSent to payment network❌ No
paidSuccessfully completed❌ No
failedDeclined before funding❌ Terminal
reversedReturned after funding❌ Terminal
cancelledStopped before network submission❌ Terminal
on_holdPaused for review⚠️ Depends
Critical: Once a charge reaches pending status, it has been submitted to the payment network and CANNOT be stopped, held, or cancelled.

Status Details

Every status change includes detailed information:
"status_details": {
  "message": "Payment failed due to insufficient funds",
  "reason": "insufficient_funds",
  "source": "bank_decline",
  "code": "R01",
  "changed_at": "2024-10-02T14:30:00Z"
}
FieldDescription
messageHuman-readable explanation
reasonMachine-readable reason code
sourceWhere the status change originated
codeACH return code (when applicable)
changed_atWhen the status changed
For complete status reasons and ACH codes, see Payment Statuses Guide.

Common Operations

Retrieve a Charge

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

Update a Charge

Update charges in created or scheduled status:
curl -X PUT https://sandbox.straddle.io/v1/charges/{charge_id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000,
    "description": "Updated amount - Monthly subscription",
...

  }'

Cancel a Charge

Cancel charges before they reach pending status:
curl -X POST https://sandbox.straddle.io/v1/charges/{charge_id}/cancel \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Customer requested cancellation"
  }'

Place on Hold

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

Release a Hold

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

Balance Verification

Balance checks help reduce NSF returns:
ModeBehaviorUse Case
enabledAttempts check, proceeds if unavailableRecommended - Best balance
requiredMust verify balance to proceedHigh-value transactions
disabledNo verification attemptedFuture-dated charges
required will fail charges if:
  • The paykey was created with manual bank account entry
  • The bank doesn’t provide balance information
  • Balance service is temporarily unavailable

Handling Failures

When charges fail, check status_details for specific information:
if (charge.status === 'failed') {
  switch(charge.status_details.reason) {
    case 'insufficient_funds':
      // Retry after notifying customer
      break;
    case 'closed_bank_account':
      // Request new payment method
      break;
    case 'disputed':
      // Do not retry - investigate
      break;
    case 'risk_review':
      // Compliance review needed
      break;
  }
}

Testing in Sandbox

Use sandbox_outcome in the config to simulate scenarios:
{
  "paykey": "pk_test123",
  "amount": 5000,
  "config": {
    "balance_check": "enabled",
    "sandbox_outcome": "paid"  // or "failed_insufficient_funds", etc.
  }
  // ... other required fields
}
Common test scenarios:
  • "paid" - Successful charge
  • "failed_insufficient_funds" - NSF failure
  • "on_hold_daily_limit" - Risk hold
  • "reversed_customer_dispute" - Post-funding reversal

Webhooks

Set up webhooks to receive real-time charge updates

Best Practices

Use Idempotency

Always include a unique external_id to prevent duplicate charges

Monitor Status Changes

Set up webhooks to track payment lifecycle in real-time

Handle Failures Gracefully

Implement retry logic for transient failures like NSF

Test Thoroughly

Use sandbox to test all failure scenarios before production

Next Steps