Stripe Connect's Transfers API moves funds from your platform's Stripe balance to connected accounts. You can create transfers tied to a specific charge (source_transaction) or from your available balance. This guide covers direct transfers, destination charges with automatic transfers, reversals, and handling transfer failures with proper error handling.
Moving Money Between Accounts with the Stripe Transfers API
The Transfers API is the backbone of multi-party payments in Stripe Connect. It lets your platform move funds from its Stripe balance to any connected account. Transfers can be linked to specific charges (for reconciliation) or created independently. Understanding when to use transfers versus destination charges is key to building a clean payment architecture.
Prerequisites
- Stripe account with Connect enabled and at least one connected account
- Node.js 18 or later installed
- Stripe Node.js SDK: npm install stripe
- Positive platform balance in test mode (create test charges first)
Step-by-step guide
Create a basic transfer to a connected account
Create a basic transfer to a connected account
Transfer funds from your platform's available balance to a connected account. The amount is in cents.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23async function createTransfer() {4 const transfer = await stripe.transfers.create({5 amount: 5000, // $50.00 in cents6 currency: 'usd',7 destination: 'acct_connectedAccountId',8 description: 'Payment for order #1234',9 metadata: {10 order_id: '1234',11 },12 });13 console.log('Transfer created:', transfer.id);14 console.log('Amount:', transfer.amount, 'cents');15 return transfer;16}1718createTransfer();Expected result: A transfer object is returned with a unique ID. The funds move from your platform balance to the connected account.
Link a transfer to a specific charge
Link a transfer to a specific charge
Use source_transaction to tie a transfer to a specific charge. This ensures the transfer amount cannot exceed the charge amount and improves reconciliation.
1async function transferFromCharge(chargeId, connectedAccountId) {2 // First, create a charge on your platform3 const paymentIntent = await stripe.paymentIntents.create({4 amount: 10000, // $100.005 currency: 'usd',6 payment_method: 'pm_card_visa', // Test card7 confirm: true,8 automatic_payment_methods: { enabled: true, allow_redirects: 'never' },9 });1011 // Then transfer a portion to the connected account12 const transfer = await stripe.transfers.create({13 amount: 8500, // $85.00 — platform keeps $15.0014 currency: 'usd',15 destination: connectedAccountId,16 source_transaction: paymentIntent.latest_charge,17 });1819 console.log('Transfer:', transfer.id, 'linked to charge:', paymentIntent.latest_charge);20 return transfer;21}Expected result: A transfer linked to the charge is created. The platform retains the difference as revenue.
Create multiple transfers from one charge (multi-seller)
Create multiple transfers from one charge (multi-seller)
For marketplace orders involving multiple sellers, create one charge and multiple transfers.
1async function multiSellerTransfer() {2 const paymentIntent = await stripe.paymentIntents.create({3 amount: 20000, // $200.00 total order4 currency: 'usd',5 payment_method: 'pm_card_visa',6 confirm: true,7 automatic_payment_methods: { enabled: true, allow_redirects: 'never' },8 });910 const chargeId = paymentIntent.latest_charge;1112 const transfers = await Promise.all([13 stripe.transfers.create({14 amount: 7500, // $75.00 to Seller A15 currency: 'usd',16 destination: 'acct_sellerA',17 source_transaction: chargeId,18 metadata: { seller: 'A', order: '5678' },19 }),20 stripe.transfers.create({21 amount: 9500, // $95.00 to Seller B22 currency: 'usd',23 destination: 'acct_sellerB',24 source_transaction: chargeId,25 metadata: { seller: 'B', order: '5678' },26 }),27 ]);2829 // Platform keeps $30.00 ($200 - $75 - $95)30 console.log('Transfers:', transfers.map(t => t.id));31}Expected result: Two transfers are created from a single charge. Total transferred cannot exceed the charge amount.
Reverse a transfer
Reverse a transfer
If you need to claw back funds from a connected account (e.g., after a refund), create a transfer reversal.
1async function reverseTransfer(transferId, amount) {2 const reversal = await stripe.transfers.createReversal(transferId, {3 amount: amount, // Partial reversal in cents, omit for full reversal4 description: 'Refund for order #1234',5 });6 console.log('Reversal created:', reversal.id);7 console.log('Amount reversed:', reversal.amount, 'cents');8 return reversal;9}1011reverseTransfer('tr_transferId', 2500); // Reverse $25.00Expected result: A reversal object is created. The specified amount is returned from the connected account to your platform balance.
Handle transfer failures and insufficient balance
Handle transfer failures and insufficient balance
Transfers can fail if your platform has insufficient balance. Always wrap transfers in error handling and monitor transfer.failed webhook events.
1async function safeTransfer(amount, destination, sourceCharge) {2 try {3 const balance = await stripe.balance.retrieve();4 const available = balance.available.find(b => b.currency === 'usd');56 if (!available || available.amount < amount) {7 throw new Error(`Insufficient balance: ${available?.amount || 0} < ${amount}`);8 }910 const transfer = await stripe.transfers.create({11 amount,12 currency: 'usd',13 destination,14 source_transaction: sourceCharge,15 });1617 return { success: true, transfer };18 } catch (err) {19 console.error('Transfer failed:', err.message);20 return { success: false, error: err.message };21 }22}Expected result: The function checks balance before transferring and returns a clean success/error response.
Complete working example
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);2const express = require('express');3const app = express();45app.use(express.json());67app.post('/api/transfers', async (req, res) => {8 try {9 const { amount, destination, chargeId, metadata } = req.body;10 const transferData = {11 amount,12 currency: 'usd',13 destination,14 metadata: metadata || {},15 };16 if (chargeId) {17 transferData.source_transaction = chargeId;18 }19 const transfer = await stripe.transfers.create(transferData);20 res.json({ id: transfer.id, amount: transfer.amount, status: 'created' });21 } catch (err) {22 res.status(400).json({ error: err.message });23 }24});2526app.post('/api/transfers/:id/reverse', async (req, res) => {27 try {28 const reversal = await stripe.transfers.createReversal(29 req.params.id,30 { amount: req.body.amount }31 );32 res.json({ id: reversal.id, amount: reversal.amount });33 } catch (err) {34 res.status(400).json({ error: err.message });35 }36});3738app.get('/api/transfers/:accountId', async (req, res) => {39 try {40 const transfers = await stripe.transfers.list({41 destination: req.params.accountId,42 limit: 20,43 });44 res.json(transfers.data);45 } catch (err) {46 res.status(400).json({ error: err.message });47 }48});4950app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {51 const sig = req.headers['stripe-signature'];52 let event;53 try {54 event = stripe.webhooks.constructEvent(55 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET56 );57 } catch (err) {58 return res.status(400).send(`Webhook Error: ${err.message}`);59 }60 if (event.type === 'transfer.created') {61 console.log('Transfer created:', event.data.object.id);62 } else if (event.type === 'transfer.reversed') {63 console.log('Transfer reversed:', event.data.object.id);64 }65 res.json({ received: true });66});6768app.listen(3000, () => console.log('Transfer server on port 3000'));Common mistakes when transfering funds between Stripe accounts using Connect
Why it's a problem: Transferring more than the source_transaction amount
How to avoid: When using source_transaction, the total transfers linked to a charge cannot exceed the charge amount. Stripe will reject the transfer.
Why it's a problem: Not checking platform balance before creating transfers
How to avoid: If your platform balance is insufficient, the transfer fails. Check balance.available first or use source_transaction to tie transfers to charges.
Why it's a problem: Creating transfers before the charge settles
How to avoid: Funds from charges are available after the settlement period (usually 2 days). Use source_transaction or wait for charge.succeeded webhook.
Why it's a problem: Not using idempotency keys for transfers
How to avoid: Network retries can create duplicate transfers. Always include an idempotency key: stripe.transfers.create({...}, { idempotencyKey: 'order_1234_sellerA' }).
Best practices
- Always use source_transaction when the transfer relates to a specific charge for better reconciliation
- Use idempotency keys to prevent duplicate transfers on network retries
- Monitor transfer.created and transfer.reversed webhook events
- Check platform balance before creating large transfers not tied to charges
- Use metadata to link transfers to your internal order and seller IDs
- For complex multi-party transfers, teams like RapidDev can help design the optimal fund flow architecture
- Keep a ledger in your database mapping charges to transfers for financial reconciliation
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
How do I transfer funds between Stripe accounts using the Transfers API in Stripe Connect? Show me examples of basic transfers, transfers linked to charges, multi-seller splits, and reversals in Node.js.
Build me a Node.js API for managing Stripe Connect transfers. I need endpoints for creating transfers linked to charges, splitting payments between multiple sellers, reversing transfers, and listing transfers by account.
Frequently asked questions
What is the difference between a transfer and a destination charge?
A destination charge automatically creates a transfer when the charge succeeds. Separate transfers give you more control — you decide when and how much to transfer, and can split one charge across multiple accounts.
Can I reverse a transfer after the connected account has paid out?
Yes, but the connected account must have sufficient balance. If their balance is negative after the reversal, Stripe will attempt to recoup the funds from future payments.
Is there a fee for transfers between accounts?
Transfers themselves are free. Stripe charges processing fees on the original charge. Some Connect pricing plans charge additional fees per payout to connected accounts.
Can I transfer funds in a different currency than the charge?
Yes, but currency conversion applies. Stripe handles the conversion automatically and charges a conversion fee (typically 1-2%).
How long does it take for a transfer to arrive in the connected account?
Transfers are instant within Stripe — the funds appear in the connected account's Stripe balance immediately. The connected account's payout schedule then determines when funds reach their bank.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation