ACH Direct Debit
ACH Direct Debit is available to eligible merchants who can implement a custom client-side integration using our JavaScript v3 SDK. It's currently not available in our Drop-in UI.
ACH Direct Debit is a payment method that runs on the ACH (Automated Clearing House) network in the United States. It allows customers to pay for transactions by debiting directly from their bank account, as opposed to processing through a card brand.
At a high level, accepting ACH payments consists of four concepts:
- Tokenizing
- Vaulting
- Verifying
- Transacting
Depending on how you wish to verify your customer's bank account, these concepts will overlap in different ways.
Tokenizing
Tokenizing is the process of exchanging raw payment information for a secure, single-use payment method ID.
Tokenizing without verification
You can tokenize a bank account by collecting a customer's bank details, including account and routing numbers. However, this will not verify the bank account, and the resulting payment method will not be transactable. Before you can create a transaction, you will need to store the tokenized bank account information in your Vault, and then verify it.
To tokenize an US bank account, use the tokenizeUsBankAccount
mutation. Make sure to provide the bank account details in the usBankAccount
input field:
- Mutation
mutation TokenizeUsBankAccount($input: TokenizeUsBankAccountInput!) {
tokenizeUsBankAccount(input: $input) {
paymentMethod {
id
usage
createdAt
details {
... on UsBankAccountDetails {
accountholderName
accountType
bankName
last4
routingNumber
verified
achMandate {
acceptedAt
acceptanceText
}
}
}
}
}
}
- Variables
{
"input": {
"usBankAccount": {
"routingNumber": "a_routing_number",
"accountNumber": "an_account_number",
"accountType": "CHECKING",
"achMandate": "I agree to give away all my money",
"individualOwner": {
"firstName": "Busy",
"lastName": "Bee"
},
"billingAddress": {
"streetAddress": "111 Main St",
"extendedAddress": "#7",
"city": "San Jose",
"state": "CA",
"zipCode": "94085"
}
}
}
}
- Response
{
"data": {
"tokenizeUsBankAccount": {
"paymentMethod": {
"id": "id_of_payment_method",
"usage": "SINGLE_USE",
"createdAt": "created_at_date",
"details": {
"accountholderName": "Busy Bee",
"accountType": "CHECKING",
"bankName": "name_of_bank",
"last4": "last_4_digits_of_an_account_number",
"routingNumber": "a_routing_number",
"verified": false,
"achMandate": null
}
}
}
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
Vaulting
Vaulting is the process of exchanging a single-use payment method ID for a multi-use payment method ID. For our ACH integration, vaulting includes the following verification options:
Vaulting with verification
When vaulting a bank account, you can choose to specify a verification method. This creates a vaulted payment method and initiates a verification in a single step. If the verification fails, you can keep retrying verifications on the same payment method. A vaulted payment method will only be transactable once it has had a successful verification.
To vault an ACH payment method, use the vaultUsBankAccount
mutation. Provide the VaultPaymentMethodInput
input field with at least a single-use ACH paymentMethodId
. Optionally, you can also provide a verificationMethod
which specifies the way in which you want the given bank account verified.
- Mutation
mutation VaultUsBankAccount($input: VaultUsBankAccountInput!) {
vaultUsBankAccount(input: $input) {
paymentMethod {
id
legacyId
details {
... on UsBankAccountDetails {
accountholderName
accountType
bankName
last4
routingNumber
verified
achMandate {
acceptedAt
acceptanceText
}
}
}
}
verification {
id
status
}
}
}
- Variables
{
"input": {
"paymentMethodId": "id_of_payment_method",
"verificationMerchantAccountId": "id_of_merchant_account",
"verificationMethod": "MICRO_TRANSFERS"
}
}
- Response
{
"data": {
"vaultUsBankAccount": {
"paymentMethod": {
"id": "id_of_payment_method",
"legacyId": "legacy_id_of_payment_method",
"details": {
"accountholderName": "Busy Bee",
"accountType": "CHECKING",
"bankName": "name_of_bank",
"last4": "3210",
"routingNumber": "a_routing_number",
"verified": false,
"achMandate": {
"acceptedAt": "accepted_at_date",
"acceptanceText": "I agree to give away all my money"
}
}
},
"verification": {
"id": "id_of_verification",
"status": "PENDING"
}
}
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
Vaulting without verification
You can also vault a bank account without specifying a verification method. This will vault the payment method as usual, but no verification will be attempted. As stated above, this payment method will not be transactable until it has a subsequent successful verification.
If you prefer to perform verification on your own, the verificationMethod
should be set as INDEPENDENT_CHECK
.
Verifying
Braintree’s ACH Direct Debit integration offers several methods for verifying that the customer owns the bank account they provide to you for payment:
- Network check – instantly verifies the bank account details using bank account and routing number. Personal/business information verification can be requested additionally with verification add ons.
- Micro-transfers – issues two separate credits of less than a dollar each to the customer’s bank account and requires the customer to confirm the exact amounts once they’re visible in the account
- Independent check – allows you to use your own verification method that’s not listed above and manually mark payment methods as verified
You can use any combination of these methods in order to meet your business needs. For example, one recommended flow is to use network check as an initial verification method with micro-transfers as a backup option.
Verifying with network check
To verify an US bank account, use the verifyUsBankAccount
mutation.
Provide the VerifyUsBankAccountInput
a paymentMethodId
and verificationMethod
at the minumum.
- Mutation
mutation VerifyUsBankAccount($input: VerifyUsBankAccountInput!) {
verifyUsBankAccount(input: $input) {
verification {
id
legacyId
status
merchantAccountId
createdAt
gatewayRejectionReason
paymentMethod {
id
}
processorResponse {
legacyCode
message
}
}
}
}
- Variables
{
"input": {
"paymentMethodId": "id_of_payment_method",
"verificationMethod": "NETWORK_CHECK",
"verificationAddOns": ["CUSTOMER_VERIFICATION"] // this is optional
}
}
- Response
{
"data": {
"verifyUsBankAccount": {
"verification": {
"id": "id_of_verification",
"legacyId": "legacy_id_of_verification",
"status": "VERIFIED",
"merchantAccountId": "id_of_merchant_account",
"createdAt": "created_at_date",
"gatewayRejectionReason": null,
"paymentMethod": {
"id": "id_of_payment_method"
},
"processorResponse": {
"legacyCode": "1000",
"message": "Approved",
}
}
}
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
Verifying with Micro-deposit
If you inputed MICRO_TRANSFERS
as your verificationMethod
on the vaultUsBankAccount
call, you’ll need to collect the micro-deposit amounts entered by the customer in your own UI and pass them to Braintree in a separate call.
To complete the verification process for a US bank account via micro-transfer, use the confirmMicroTransferAmounts
mutation. Provide ConfirmMicroTransferAmountsInput
with a verificationId
and the previously collected amountInCents
at the minimum.
- Mutation
mutation ConfirmMicroTransferAmounts($input: ConfirmMicroTransferAmountsInput!) {
confirmMicroTransferAmounts(input: $input) {
verification {
id
paymentMethod {
id
usage
}
merchantAccountId
status
processorResponse {
message
}
}
status
}
}
- Variables
{
"input": {
"verificationId": "id_of_verification",
"amountsInCents": [17, 44]
}
}
- Response
{
"data": {
"confirmMicroTransferAmounts": {
"verification": {
"id": "id_of_verification",
"paymentMethod": {
"id": "id_of_payment_method",
"usage": "MULTI_USE"
},
"merchantAccountId": "id_of_merchant_account",
"status": "PENDING",
"processorResponse": {
"message": "Approved",
},
"status": "CONFIRMED"
}
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
Checking verification status
To check for successful verification, you should examine the message
field of the VerificationProcessorResponse
returned by verifyUsBankAccount
and look for "Approved"
. If verification has failed, some other message will be returned, such as "Processor Network Unavailable - Try Again"
.
You can also request additionalInformation
under processorResponse
and depending on the type of processor failure, it will return details about the error. For example, it may say "Invalid routing number"
or "Invalid account type"
.
Looking up individual verification status
This step is required when using the micro-transfers method.
After successfully confirming micro-deposit amounts, the bank account may be ready for transacting, still waiting for transfers to settle, or may eventually report that settlement failed. You can periodically check the state of a verification with its id
like so:
- Graphql
query($input: VerificationSearchInput!) {
search {
verifications(input: $input) {
edges {
node {
id
status
}
}
}
}
}
- Variables
{
"input": {
"id": {
"is": "id_of_verification"
}
}
}
- Response
{
"data": {
"search": {
"verifications": {
"edges": [
{
"node": {
"id": "id_of_verification",
"status": "VERIFIED"
}
}
]
}
}
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
Retrying verification
Sometimes verification will fail. You can retry verification by sending the the previous request again or using the previous request with a different verificationMethod
. For example, if NETWORKCHECK
was used and it failed with the message "No Data Found - Try Another Verification Method"
, you can retry withMICRO_TRANSFERS
as an alternative.
Creating Transactions
From multi-use payment methods
This step is applicable to all verification methods.
You will receive a multi-use payment method id when you successfully call vaultUsBankAccount
using a single-use payment method created from tokenizeUsBankAccount
. You can use the multi-use payment method id to charge the account using chargeUsBankAccount
once the payment method is verified.
Collect device data from the client and include the collected client device data via the deviceData parameter inside riskData. Doing so will help reduce decline rates. Below includes an example call using device data:
- Mutation
mutation ChargeUsBankAccount($input: ChargeUsBankAccountInput!){
chargeUsBankAccount(input: $input) {
transaction {
id
amount {
value
}
paymentMethodSnapshot {
...on UsBankAccountDetails{
accountholderName
accountType
verified
}
}
}
}
}
- Variables
{
"input": {
"paymentMethodId": "id_of_payment_method",
"transaction": {
"amount": "10.00",
"orderId": "id_of_order",
"riskData": {
"customerBrowser": "web_browser_type",
"customerIp": "ip_address",
"deviceData": "device_type"
}
}
}
}
- Response
{
"data": {
"chargeUsBankAccount": {
"transaction": {
"id": "id_of_transaction",
"amount": {
"value": "10.00"
},
"paymentMethodSnapshot": {
"accountholderName": "Busy Bee",
"accountType": "CHECKING",
"verified": false
}
}
}
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
Common errors
Note that each tokenized single-use payment method ID can only be vaulted once. If you attempt to vault the same single-use payment method ID more than once, you will get the following error.
- JSON
{
"errors": [
{
"message": "Cannot use a single-use payment method more than once.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"vaultUsBankAccount"
],
"extensions": {
"errorClass": "VALIDATION",
"errorType": "user_error",
"inputPath": [
"input",
"paymentMethodId"
],
"legacyCode": "93107"
}
}
],
"data": {
"vaultUsBankAccount": null
},
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}
If you do not provide verificationMethod
in the inputs, you will get the following error.
- JSON
{
"errors": [
{
"message": "Variable 'input' has an invalid value: Field 'verificationMethod' has coerced Null value for NonNull type 'UsBankAccountVerificationMethod!'",
"locations": [
{
"line": 1,
"column": 29
}
]
}
],
"extensions": {
"requestId": "a-uuid-for-the-request"
}
}