On-Ramping USD via ACH Pulls (Plaid)
What Is ACH Pull?
An ACH Pull lets a user authorize Bakkt to withdraw USD from a linked bank account. Plaid manages the secure bank connection, so your system never handles bank login credentials.
USD On-Ramp Flow
- Create a linked bank account profile for the user.
- Create a bank link and receive a hosted Plaid Link URL.
- Open the hosted Plaid Link URL so the user can choose and confirm the bank account.
- Wait until the linked bank account is
ACTIVE. - Initiate the ACH pull from a specific linked bank account.
- Monitor webhooks as the transfer and conversion progress.
Prerequisites
- The user must be verified as
FULL_USER. - For US users, required applicant data includes date of birth, country, phone number, and TIN or SSN.
- The user must accept the required terms.
- Create the linked bank account profile before starting the bank linking flow.
Integration Steps
Step 1: Create the Linked Bank Account Profile
Create the profile that allows the user to link a USD bank account for ACH pulls.
curl --request POST \
--url https://sandbox.api.bakkt.com/stablecoin/user/linked-bank-account/profile \
--header 'Authorization: API-Key <YOUR_API_KEY>' \
--header 'bakkt-session-id: <USER_SESSION_ID>' \
--header 'content-type: application/json' \
--data '{
"currency": "USD",
"investment_objective": "INCOME",
"annual_income_range": "UNDER_25K"
}'Step 2: Create Bank Link and Get Plaid Link URL
Start the bank linking process. The response includes hosted_link_url, a hosted Plaid Link URL that the user opens in your frontend.
curl --request PUT \
--url https://sandbox.api.bakkt.com/stablecoin/user/linked-bank-account/link \
--header 'Authorization: API-Key <YOUR_API_KEY>' \
--header 'bakkt-session-id: <USER_SESSION_ID>' \
--header 'content-type: application/json' \
--data '{
"currency": "USD"
}'{
"hosted_link_url": "https://..."
}Step 3: Open Plaid Link
Open the hosted_link_url on your frontend. Plaid handles authentication and consent, and your system does not receive bank credentials.
If the user selects an invalid account, such as an account that is not CHECKING, the link fails and the linked bank account status becomes FAILED. In that case, create a new bank link and send the user through Plaid again.
Step 4: Verify Link Status
After Plaid confirmation, the linked bank account moves from PENDING to a final status.
- Webhook (recommended): Subscribe to
linkBankAccountwebhooks. Whendata.statusisACTIVE, the account is ready for ACH pulls. Whendata.statusisFAILED, usedata.errorto explain the failure and restart the link flow if appropriate. - Polling: Poll
GET /stablecoin/user/linked-bank-accountuntil the linked bank account status isACTIVE.
curl --request GET \
--url https://sandbox.api.bakkt.com/stablecoin/user/linked-bank-account \
--header 'Authorization: API-Key <YOUR_API_KEY>' \
--header 'bakkt-session-id: <USER_SESSION_ID>'Successful response example:
{
"profile_uuid": "5594401c-0072-4df2-be9c-d491c0754c21",
"status": "ACTIVE",
"linked_bank_accounts": [
{
"uuid": "abcdef01-1234-5678-90ab-cdef01234567",
"status": "ACTIVE",
"currency": "USD",
"account_number": "******7890",
"routing_number": "021000021",
"account_type": "CHECKING",
"bank_name": "Chase Bank"
}
]
}Step 5: Initiate ACH Pull
Once the linked bank account status is ACTIVE, initiate the ACH pull for the desired USD amount.
You must specify which linked bank account to pull from using source_account_uuid. Include the user's ip_address in the request unless Bakkt has confirmed it is not required for your merchant configuration. Only 3 ACH pulls per day are allowed per user.
curl --request POST \
--url https://sandbox.api.bakkt.com/stablecoin/user/linked-bank-account/pull \
--header 'Authorization: API-Key <YOUR_API_KEY>' \
--header 'bakkt-session-id: <USER_SESSION_ID>' \
--header 'content-type: application/json' \
--data '{
"currency": "USD",
"amount": 100.00,
"source_account_uuid": "<LINKED_BANK_ACCOUNT_UUID>",
"ip_address": "203.0.113.42"
}'The response includes process_uuid, which you can use to track the pull process.
{
"process_uuid": "2b2f5c5c-7d77-47a5-9b3a-2c0fd8f8d5a1"
}Limits and Account Management
ACH Pull Limits
Users are limited to 3 ACH pulls per day in a rolling 24-hour period. If a user exceeds this limit, the pull request may be rejected. Plan your UI messaging so users understand when they need to wait before trying again.
Bank Linking Limits
ACH pulls are only possible from accounts that are already linked and ACTIVE. Bank linking is intentionally limited so users do not frequently switch funding sources.
Current limits per user:
- Maximum 2 linked bank accounts at any time
- Maximum 1 bank linking action per 24 hours
Because of these limits, a user should link one bank account and keep using it for ACH pulls. Do not build flows that encourage linking multiple accounts and switching between them in the same day.
For example:
- The user links Bank Account A.
- The user tries to link Bank Account B the same day.
- The second link attempt may be blocked by the 24-hour linking limit.
- ACH pulls from Bank Account A continue to work while the account is
ACTIVE.
Unlink a Bank Account
Use the unlink endpoint when the user needs to remove a linked bank account. You must provide the linked bank account account_uuid.
curl --request DELETE \
--url https://sandbox.api.bakkt.com/stablecoin/user/linked-bank-account/unlink \
--header 'Authorization: API-Key <YOUR_API_KEY>' \
--header 'bakkt-session-id: <USER_SESSION_ID>' \
--header 'content-type: application/json' \
--data '{
"account_uuid": "<LINKED_BANK_ACCOUNT_UUID>"
}'Webhooks
Use linkBankAccount webhooks to react to bank link status changes without polling. The webhook includes the owner UUID, linked bank account UUID, status, and an error message when the status is FAILED.
Typical statuses include PENDING, CHECK_COMPLETED, ACTIVE, FAILED, and DISABLED. When status becomes ACTIVE, the linked account is ready for ACH pulls. See the Webhooks guide for the full payload shape and webhook setup details.
For transfer and conversion progress after the ACH pull is initiated, monitor the standard on-ramp webhook events. See the On-ramp guide for the broader fiat-to-stablecoin lifecycle.
Settlement Time
ACH transfers take several business days to complete. Use fiatToCrypto webhooks to track when Bakkt has acknowledged receipt of fiat, issued the stablecoin transfer, or completed the on-ramp process.
Error Handling
A FAILED linked bank account status usually means the user selected an unsupported or invalid account. Show the error returned in the linkBankAccount webhook when it is safe to display, then create a new bank link so the user can select a valid checking account.
If an ACH pull request is rejected, check whether the user has reached the daily pull limit, whether the linked account is still ACTIVE, and whether ip_address is required for your merchant configuration.
Sandbox Testing
- In Plaid, select the bank account that ends with
0000. - Use the Sandbox host shown in the examples.
Updated 3 days ago
