Payflow Docs
Payments

ACH Transfers

Tokenize ACH customers, preview transfer cost, and create provider-routed ACH transfers through Payflow.

ACH transfers are server-side flows. They require secret API keys, ACH routing, provider readiness, and valid wallet availability for the target environment. Payflow can route ACH transfers through supported providers such as Dwolla or Moov.

Endpoints

MethodPathPurpose
POST/client/v1/ach/customer-tokensCreate an encrypted customer token.
POST/client/v1/ach/transfers/previewPreview routing, fees, and total debit.
POST/client/v1/ach/transfersCreate an ACH transfer.

All three endpoints require a secret key.

The examples assume:

export PAYFLOW_CLIENT_API_URL="https://api.ftosllc.co/client/v1"
export PAYFLOW_SECRET_KEY="sk_test_your_key_id_your_secret"

Create a customer token

Customer tokens let Payflow bind customer identity details to an encrypted token before the transfer call.

curl -X POST "$PAYFLOW_CLIENT_API_URL/ach/customer-tokens" \
  -H "Authorization: Bearer $PAYFLOW_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalCustomerId": "cus_1001",
    "email": "[email protected]",
    "firstName": "Jane",
    "lastName": "Stone",
    "phone": "+15555550100",
    "metadata": {
      "accountTier": "business"
    }
  }'

Response:

{
  "success": true,
  "data": {
    "customerToken": "eyJ...",
    "tokenType": "JWE",
    "expiresAt": null
  },
  "message": null
}

Preview a transfer

curl -X POST "$PAYFLOW_CLIENT_API_URL/ach/transfers/preview" \
  -H "Authorization: Bearer $PAYFLOW_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amountMinor": "12500",
    "currency": "USD",
    "metadata": {
      "invoiceId": "INV-ACH-1001"
    }
  }'

The preview evaluates ACH routing and returns the combined service fee when routing and provider readiness checks pass. The fee combines Payflow's provider-cost recovery fee and the organization's configured fee.

Example response:

{
  "success": true,
  "data": {
    "amountMinor": "12500",
    "currency": "USD",
    "routing": {
      "provider": "moov",
      "channel": "standard_ach",
      "channelLabel": "Standard ACH (1 - 3 days)",
      "fallback": false
    },
    "fee": {
      "platformFeeMinor": "25",
      "organizationFeeMinor": "100",
      "feeMinor": "125"
    },
    "totalDebitMinor": "12625"
  },
  "message": null
}

Relevant response fields:

FieldMeaning
amountMinorPrincipal transfer amount.
fee.platformFeeMinorPayflow provider-cost recovery fee.
fee.organizationFeeMinorOrganization-configured fee.
fee.feeMinorCombined service fee.
totalDebitMinorPrincipal amount plus combined service fee.

Create a transfer

curl -X POST "$PAYFLOW_CLIENT_API_URL/ach/transfers" \
  -H "Authorization: Bearer $PAYFLOW_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotencyKey": "invoice-INV-ACH-1001-v1",
    "amountMinor": "12500",
    "currency": "USD",
    "customerToken": "eyJ...",
    "routingNumber": "222222226",
    "accountNumber": "123456789",
    "bankAccountType": "checking",
    "bankName": "Test Bank",
    "accountName": "Jane Stone",
    "description": "ACH invoice payment",
    "metadata": {
      "invoiceId": "INV-ACH-1001"
    },
    "remittanceData": {
      "invoice": "INV-ACH-1001"
    }
  }'

Important fields:

FieldNotes
idempotencyKeyRequired, 8 to 160 characters. Reuse it for safe retries of the same business action.
amountMinorPositive smallest currency unit.
currencyCurrently expected to be USD for ACH transfers.
customerTokenToken returned by /ach/customer-tokens.
routingNumberNine digits.
accountNumberFour to seventeen digits.
bankAccountTypechecking or savings.
bankNameHuman-readable bank name.
remittanceDataOptional string or object.

Response:

{
  "success": true,
  "data": {
    "transaction": {
      "payflowReference": "pf_test_ach_69f14d7e1d3b4d5a9cfbb887adfc34c0",
      "status": "processing",
      "amountMinor": "12500",
      "platformFeeMinor": "25",
      "organizationFeeMinor": "100",
      "feeMinor": "125",
      "totalDebitMinor": "12625",
      "currency": "USD",
      "paymentMethod": "ach",
      "provider": "moov",
      "channel": "standard_ach"
    },
    "replayed": false,
    "bankAccount": {
      "bankName": "Test Bank",
      "accountType": "checking",
      "last4": "6789"
    }
  },
  "message": null
}

Idempotency

ACH references are deterministic for an organization, application environment, and idempotencyKey. If your service retries after a timeout, reuse the same key rather than creating a new one.

Failure cases

ConditionTypical statusNotes
Missing secret key403ACH endpoints require secret credentials.
Invalid body400Field validation failed.
Routing unavailable409ACH routing is disabled or incomplete.
Provider not configured503ACH provider credentials or source funding details are missing.
Insufficient wallet balance409Payflow-held wallet does not have enough available funds.
Provider transfer rejected422Dwolla, Moov, or provider validation rejected the transfer.

Operational notes

  • Store only the Payflow reference, bank last four, provider references, and status in your app.
  • Display the previewed fee and total debit before creating the transfer.
  • Do not log full routing or account numbers.
  • Make sure provider funding source settings point to a specific usable source account or funding source.
  • Configure ACH provider webhooks so transfer status changes can update Payflow transactions.

On this page