Payments & Transfers
For safe retries on POST/PUT (optional Idempotency-Key header, cached responses, TTL), see Idempotent requests.
Recipient Management
Add and manage recipients (external bank accounts) for payouts in supported currencies (EUR, GBP, USD, NGN).
Note:
Available in Both APIs: The same concept is exposed in the Stablecoin API (where it is called "beneficiaries") for backwards compatibility. Use Accounts API for same-currency transfers.
ACH Pull Use Case
ACH Pulls allow users to deposit funds from their external bank account to their Bakkt account:
- Pull funds from bank → Keep in user's account balance
- Use case: User wants to deposit funds to their account
Create Recipient
POST /user/bank-account/remote
Add an external bank account with details for payouts. Works for both domestic and international destinations.
List Recipients
GET /user/bank-account/remote
View all saved recipients.
Get Recipient Details
GET /user/bank-account/remote/{uuid}
View specific recipient information.
Update
PATCH /user/bank-account/remote
Set a specific recipient as the default destination for payouts.
Delete
DELETE /user/bank-account/remote/{uuid}
Remove a saved recipient.
Payments & Transfers
Send and receive money:
Transaction History
GET /user/ledger/transactions
View all payment and transfer history for a user, including deposits and payouts.
Info:
Note: Sending money to recipients happens when you use payment or remittance endpoints. Direct "send" endpoint coming in future release.
Money OUT (Payouts)
Domestic Payout
Send to same-currency recipient:
POST /user/bank-account/remote
{
"account_name": "Main Bank Account",
"main_recipient": true,
"account_details": {
"currency": "USD",
"transfer_method": "ACH",
"account_number": "12365498",
"routing_number": "026009593",
"account_type": "Checking",
"recipient_relationship": "Customer"
}
}After saving the recipient, the user initiates a payment via your UI — debiting their Bakkt account and crediting the recipient. Funds arrive via ACH in 2-3 business days.
Info:
International Currencies: To send money to other currencies like PKR, INR, or TRY, use the Stablecoin API third-party off-ramp feature.
Recipient Types
The Accounts API handles all types of recipients:
Domestic Recipients
Same currency, same country - fast settlement:
- US (USD → USD): ACH push, 2-3 business days
- Europe (EUR → EUR): SEPA, same day to 1 business day
- UK (GBP → GBP): FPS, same day
Supported Recipient Currencies
Recipients can be created in the same currencies as bank accounts:
- EUR (SEPA - Europe-wide)
- GBP (FPS - United Kingdom)
- USD (ACH - United States)
- NGN (Local - Nigeria)
Info:
Other Currencies: For PKR, INR, TRY, and other cross-border destinations, use the Stablecoin API (its equivalent feature is called "beneficiaries").
ACH Operations
ACH Push (via Recipient)
Credit recipient's external bank account:
POST /user/bank-account/remote
{
"account_name": "Payroll Account",
"main_recipient": false,
"account_details": {
"currency": "USD",
"transfer_method": "ACH",
"account_number": "987654321",
"routing_number": "026009593",
"account_type": "Checking",
"recipient_relationship": "Customer"
}
}The user then initiates a payment to this recipient — debiting their Bakkt account and crediting the recipient. Settlement in 2-3 business days.
ACH Pull via Plaid
Overview
This section explains how to enable ACH Pulls via Plaid with Bakkt for an individual user. It covers prerequisites, the end‑to‑end flow, API calls, error handling, and troubleshooting.
Prerequisites
Before you begin, ensure the following requirements are met:
- The user must be in status
FULL_USER - User is fully created and eligible for ACH pulls in your system
- Feature flags and environment variables for Plaid and Bakkt are configured in your backend
- You have the user identifier needed by the APIs below
- This functionality works only for individual users. Joint or business users are not supported
Warning:
Individual Users Only: ACH Pull functionality is currently only supported for individual users. Joint or business users cannot use this feature.
High‑level Flow
The ACH Pull process follows these key steps:
Step 1: Ensure User Eligibility
Create or ensure the user exists and is in a state eligible for bank linking
Step 2: Get Plaid Link URL
Call the link endpoint to receive a Plaid link_url for the user
Step 3: User Completes Plaid Flow
Direct the user to the link_url where they complete the Plaid authentication and select their bank account
Step 4: Verify the Link
After Plaid completion, verify the link status via the linked bank account endpoint
Step 5: Initiate ACH Pull
Once verified, initiate ACH Pulls to debit the linked external account
Step 1 — Ensure User is Ready
Make sure the user is fully provisioned in your system. If your workflow requires any KYC or profile completion, complete that first.
Create or Ensure User Endpoint
Use this endpoint to create or verify the user profile before proceeding with bank linking.
Step 2 — Get Plaid Link URL
Call the link endpoint to receive a Plaid link_url that the user will use to complete the bank account linking process.
Link Bank Account
Use this endpoint to initiate the bank account linking process and receive a Plaid link URL.
Example Request:
PUT /user/linked-bank-account/linkExample Response:
{
"link_url": "https://plaid.com/link?token=link-sandbox-abc123...",
"status": "pending"
}Info:
Thelink_urlis a unique URL that directs the user to Plaid's secure authentication flow.
Step 3 — User Completes Plaid Flow
Direct the user to the link_url received in Step 2. The user will:
- Authenticate with Plaid - Securely log into their bank
- Select their bank account - Choose the account for ACH Pulls
- Grant permission - Authorize ACH debits from the selected account
What Happens During Plaid Flow:
Bank Authentication
User securely logs into their bank through Plaid's interface
Account Selection
User selects which checking or savings account to link
Permission Grant
User authorizes ACH debits from the selected account
Account Verification
Plaid verifies the account details and ownership
Warning:
User Action Required: The user must complete the entire Plaid flow for the account to be successfully linked. If they close the window or fail authentication, the link will not complete.
Step 4 — Verify the Link / Check Link Status
Use the linked bank account read endpoint to confirm the account is linked and active.
Read Linked Bank Account
Check the status and details of the linked bank account.
What to Check:
Account Status
status: should indicate linked/active- Verify the account is ready for ACH pulls
Error Validation
lastError: should be empty or null- Check for any error messages if linking failed
Account Details
- Verify account details are present (last 4 digits of account, bank name, etc.)
- Confirm the linked account is the one the user selected in Plaid
Info:
Single Account Limitation: Only one linked external bank account is supported per user at a time. If you need to change it, you must unlink the current account first.
Unlink Bank Account
Use this endpoint to unlink an external bank account before linking a new one.
Step 5 — Initiate ACH Pull
Once the external account is linked and verified, create the ACH pull request.
Initiate ACH Pull
Create an ACH pull request to debit the user's external bank account.
Accounts API (Bakkt checking, General Ledger)
The Accounts API exposes ACH pull against the user's Bakkt checking balance via General Ledger (CreateAchPull):
POST /accounts/user/account/bakkt/{account_uuid}/ach-pull
Destination checking is {account_uuid} in the path. Auth: userSessionOrUuidAuthorizer (session or user-uuid header plus API key).
Request body (JSON only — do not send account_uuid in the body; it is taken from the path):
{
"amount": 100.5,
"currency": "USD",
"source_account_uuid": "<linked-bank-account-uuid>",
"ip_address": "203.0.113.1"
}Optional fields: receiver_type (defaults to BANK_ACCOUNT), target_account_uuid, user_note (max 500 characters). amount must be positive with at most two decimal places; ip_address must be a valid IPv4 or IPv6 string.
Success (201) — GL result shape, plus process_uuid set equal to transaction_id for compatibility with older integrations:
{
"transaction_id": "<gl-transaction-id>",
"process_uuid": "<same-as-transaction_id>",
"user_uuid": "<end-user-uuid>",
"amount": 100.5,
"currency": "USD"
}Full OpenAPI: see ach-pull-request / ach-pull-response and the POST operation under Payouts & Transfers in docs/accounts_api.json.
Linked-bank account endpoint
POST /user/linked-bank-account/pull
Example Payload:
{
"amount": 25000
}Example Response:
{
"uuid": "<ach-pull-uuid>",
"status": "PENDING"
}Webhooks and Status Updates
After initiating an ACH Pull, monitor payment and ACH transfer webhooks from the provider to track processing states.
Webhook Events to Monitor:
ach_pull.initiated- ACH pull has been createdach_pull.pending- ACH pull is being processedach_pull.completed- ACH pull has settled successfullyach_pull.failed- ACH pull has failedach_pull.returned- ACH pull was returned by the bank
Info:
Update your ledger or transaction store from these webhook events to maintain accurate records.
Typical Lifecycle:
Success Path
created → pending → settled
This is the normal flow when everything processes successfully. Funds typically arrive in 2-3 business days.
Failure Path
created → pending → failed/returned
Failed or returned transactions include reason codes that indicate why the ACH pull was unsuccessful.
Error Handling and Common Failure Reasons
Common Issues:
Plaid Flow Not Completed
Problem: The user closed the Plaid window, failed authentication, or didn't complete the linking process.
Solution: Check the link status via the linked bank account endpoint. If status is not ACTIVE, generate a new link URL and have the user retry the Plaid flow.
Account Not Eligible
Problem: Some accounts may not accept ACH debits.
Solution: Verify with the user that their bank account supports ACH debits. Some accounts (like savings accounts) may have restrictions.
Insufficient Permissions
Problem: Feature flag disabled or server configuration issues.
Solution: Ensure server configuration enables Plaid linking and ACH pulls. Check that all required environment variables are set.
Compliance Checks Failed
Problem: Pulls may be blocked if the user fails internal rules or KYC.
Solution: Verify user is in FULL_USER status and has completed all required KYC checks. Review compliance rules that may be blocking the transaction.
Merchant Limit Exceeded
Problem: For unverified merchants, transaction limits may apply and be exceeded.
Solution: Complete merchant verification to increase limits, or ensure individual transactions stay within the allocated limits. Monitor your remaining transaction capacity.
Recommended Approach:
Always Verify
Always read back link status before initiating pulls to ensure the account is properly linked
Clear Error Messages
Surface clear errors to the client and offer unlink → relink flow when appropriate
Constraints and Limits
Single Account
Only one linked external account per user. Linking a new account requires unlinking the current one first using the unlink endpoint.
Individual Users Only
Only supported for individual users. Joint or business users cannot use ACH Pull functionality.
Minimum Amount
ACH pulls must be at least $10.00 USD. Transfers below this minimum will be automatically rejected and refunded.
Currency
ACH Pulls are only available for USD transactions.
Troubleshooting
| Issue | Solution |
|---|---|
| Link says successful but pulls fail | Verify the account supports ACH debits and that name matching or other compliance checks pass |
| User didn't complete Plaid flow | Check link status. If not ACTIVE, generate a new link URL and have the user retry |
| Transfer rejected - below minimum | Ensure transfer amount is at least $10.00 USD. Update your UI to prevent amounts below this limit |
| Transfer rejected - missing crypto address (onramp only) | For onramp transactions, configure user's target crypto address via onboarding endpoints before initiating pulls. Bank transfers do not require crypto addresses |
| Transfer rejected - path restricted | Certain currency/token paths may be temporarily unavailable. Check system status or contact support |
| Need to switch accounts | Call unlink, then repeat link flow with the new account |
| Merchant limit exceeded | Complete merchant verification or wait for limit reset. Monitor transaction capacity |
When troubleshooting, always check the
lastErrorfield in the linked bank account status response for detailed error information.
Sandbox vs. Production
Sandbox
Development Environment
- Use sandbox credentials and test accounts for development
- Validate the full webhook lifecycle in sandbox before moving to production
- Test all error scenarios and edge cases
- ACH pulls typically complete instantly or within minutes in sandbox
Production
Live Environment
- Monitor webhook delivery and set alerts for failures and returns
- ACH pulls take 2-3 business days to settle
- Implement proper retry logic for failed webhooks
- Set up monitoring and alerting for all ACH operations
Warning:
Always Test First: Thoroughly test your integration in the sandbox environment before deploying to production.
Security Considerations
Sensitive Data
Treat account and routing numbers as sensitive data. Do not log them in plaintext.
Encryption
Use TLS everywhere, and store secrets in a secure vault (e.g., AWS Secrets Manager, HashiCorp Vault).
Idempotency
Enforce idempotency for transfer creation to avoid duplicates. Always use unique idempotency keys.
Access Control
Implement proper authentication and authorization for all API calls.
Warning:
PCI DSS Compliance: If you're handling financial data, ensure your infrastructure meets PCI DSS compliance requirements.
Complete Integration Example
End-to-end ACH Pull flow:
Step 1 — Ensure user is FULL_USER
GET /userThe response status field must be FULL_USER. Otherwise, complete KYC before linking a bank account.
Step 2 — Get Plaid link URL
PUT /user/linked-bank-account/linkResponse:
{
"link_url": "https://plaid.com/link?token=link-sandbox-abc123...",
"status": "pending"
}Step 3 — Direct user to Plaid flow
Redirect the user to the link_url (or open it in Plaid's modal/iframe). After the user completes the flow, you'll receive a webhook or can poll the status.
Step 4 — Verify the link
GET /user/linked-bank-accountThe response status must be ACTIVE. If not, inspect lastError and have the user retry the Plaid flow.
Step 5 — Validate amount
Reject locally before calling the API if the requested amount is below the $10.00 USD minimum.
Step 6 — Initiate ACH Pull
POST /user/linked-bank-account/pull{
"amount": 25000
}Quick Reference
| Action | Endpoint |
|---|---|
| Create user profile | POST /user/linked-bank-account/profile |
| Link external bank account | PUT /user/linked-bank-account/link |
| Read linked bank account | GET /user/linked-bank-account |
| Unlink external bank account | DELETE /user/linked-bank-account/unlink |
| Create ACH Pull | POST /user/linked-bank-account/pull |
Payment Rails Supported
United States
Money IN:
- Bank transfers (wire)
- ACH pull (Plaid)
Money OUT:
- ACH push (2-3 days)
Europe
Money IN:
- SEPA transfers
- Local bank transfers
Money OUT:
- SEPA (1-2 business days)
- SEPA Instant (minutes, where available)
United Kingdom
Money IN:
- Faster Payments (FPS)
- BACS
- CHAPS
Money OUT:
- FPS (same day)
- BACS (3 business days)
Note:
Cross-Border Remittance: The Accounts API remittance flow (POST /user/remittance/quote→POST /user/remittance/start) supports any-to-any conversion between the following 20 currencies: USD, EUR, GBP, NGN, PKR, INR, TRY, MXN, BRL, ARS, IDR, PHP, AED, OMR, QAR, VND, COP, GHS, JPY, GTQ.
Warning:
Currently disabled: The remittance flow is not yet enabled in production. Reach out to your Bakkt contact for rollout timing.
Transaction Monitoring
Get Payment History
View all payment and transfer transactions:
GET /user/ledger/transactionsRequired headers:
Authorization: <your_api_key>bakkt-session-id: <session_id>
The response is an array of transactions, each with type, amount, currency, status, and timestamp fields.
Get Transaction History
Warning:
TODO - New Feature: The/ledger/transactionsendpoint is currently under development. This section describes the planned functionality.
Use the ledger endpoints to view deposits and withdrawals:
GET /user/ledger/transactions [TODO]
Get all fiat transactions for user's accounts
GET /user/ledger/transactions?limit=50&offset=0Response:
[
{
"transaction_id": "txn-123",
"account_uuid": "account-uuid",
"type": "CREDIT",
"amount": 1000.00,
"currency": "EUR",
"timestamp": "2024-01-15T10:30:00Z",
"status": "COMPLETED",
"description": "Bank transfer from external account"
}
]Next Steps
Bank Accounts
Learn about creating checking and savings accounts and account management
Advanced Features
Exchange rates, webhooks, compliance, and testing
API Reference
Explore all endpoints with interactive examples
