# How to transfer funds between Stripe accounts using Connect

- Tool: Stripe
- Difficulty: Advanced
- Time required: 20 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

async function createTransfer() {
  const transfer = await stripe.transfers.create({
    amount: 5000, // $50.00 in cents
    currency: 'usd',
    destination: 'acct_connectedAccountId',
    description: 'Payment for order #1234',
    metadata: {
      order_id: '1234',
    },
  });
  console.log('Transfer created:', transfer.id);
  console.log('Amount:', transfer.amount, 'cents');
  return transfer;
}

createTransfer();
```

**Expected result:** A transfer object is returned with a unique ID. The funds move from your platform balance to the connected account.

### 2. 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.

```
async function transferFromCharge(chargeId, connectedAccountId) {
  // First, create a charge on your platform
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 10000, // $100.00
    currency: 'usd',
    payment_method: 'pm_card_visa', // Test card
    confirm: true,
    automatic_payment_methods: { enabled: true, allow_redirects: 'never' },
  });

  // Then transfer a portion to the connected account
  const transfer = await stripe.transfers.create({
    amount: 8500, // $85.00 — platform keeps $15.00
    currency: 'usd',
    destination: connectedAccountId,
    source_transaction: paymentIntent.latest_charge,
  });

  console.log('Transfer:', transfer.id, 'linked to charge:', paymentIntent.latest_charge);
  return transfer;
}
```

**Expected result:** A transfer linked to the charge is created. The platform retains the difference as revenue.

### 3. Create multiple transfers from one charge (multi-seller)

For marketplace orders involving multiple sellers, create one charge and multiple transfers.

```
async function multiSellerTransfer() {
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 20000, // $200.00 total order
    currency: 'usd',
    payment_method: 'pm_card_visa',
    confirm: true,
    automatic_payment_methods: { enabled: true, allow_redirects: 'never' },
  });

  const chargeId = paymentIntent.latest_charge;

  const transfers = await Promise.all([
    stripe.transfers.create({
      amount: 7500, // $75.00 to Seller A
      currency: 'usd',
      destination: 'acct_sellerA',
      source_transaction: chargeId,
      metadata: { seller: 'A', order: '5678' },
    }),
    stripe.transfers.create({
      amount: 9500, // $95.00 to Seller B
      currency: 'usd',
      destination: 'acct_sellerB',
      source_transaction: chargeId,
      metadata: { seller: 'B', order: '5678' },
    }),
  ]);

  // Platform keeps $30.00 ($200 - $75 - $95)
  console.log('Transfers:', transfers.map(t => t.id));
}
```

**Expected result:** Two transfers are created from a single charge. Total transferred cannot exceed the charge amount.

### 4. Reverse a transfer

If you need to claw back funds from a connected account (e.g., after a refund), create a transfer reversal.

```
async function reverseTransfer(transferId, amount) {
  const reversal = await stripe.transfers.createReversal(transferId, {
    amount: amount, // Partial reversal in cents, omit for full reversal
    description: 'Refund for order #1234',
  });
  console.log('Reversal created:', reversal.id);
  console.log('Amount reversed:', reversal.amount, 'cents');
  return reversal;
}

reverseTransfer('tr_transferId', 2500); // Reverse $25.00
```

**Expected result:** A reversal object is created. The specified amount is returned from the connected account to your platform balance.

### 5. 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.

```
async function safeTransfer(amount, destination, sourceCharge) {
  try {
    const balance = await stripe.balance.retrieve();
    const available = balance.available.find(b => b.currency === 'usd');

    if (!available || available.amount < amount) {
      throw new Error(`Insufficient balance: ${available?.amount || 0} < ${amount}`);
    }

    const transfer = await stripe.transfers.create({
      amount,
      currency: 'usd',
      destination,
      source_transaction: sourceCharge,
    });

    return { success: true, transfer };
  } catch (err) {
    console.error('Transfer failed:', err.message);
    return { success: false, error: err.message };
  }
}
```

**Expected result:** The function checks balance before transferring and returns a clean success/error response.

## Complete code example

File: `transfer-funds.js`

```javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const express = require('express');
const app = express();

app.use(express.json());

app.post('/api/transfers', async (req, res) => {
  try {
    const { amount, destination, chargeId, metadata } = req.body;
    const transferData = {
      amount,
      currency: 'usd',
      destination,
      metadata: metadata || {},
    };
    if (chargeId) {
      transferData.source_transaction = chargeId;
    }
    const transfer = await stripe.transfers.create(transferData);
    res.json({ id: transfer.id, amount: transfer.amount, status: 'created' });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.post('/api/transfers/:id/reverse', async (req, res) => {
  try {
    const reversal = await stripe.transfers.createReversal(
      req.params.id,
      { amount: req.body.amount }
    );
    res.json({ id: reversal.id, amount: reversal.amount });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.get('/api/transfers/:accountId', async (req, res) => {
  try {
    const transfers = await stripe.transfers.list({
      destination: req.params.accountId,
      limit: 20,
    });
    res.json(transfers.data);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  if (event.type === 'transfer.created') {
    console.log('Transfer created:', event.data.object.id);
  } else if (event.type === 'transfer.reversed') {
    console.log('Transfer reversed:', event.data.object.id);
  }
  res.json({ received: true });
});

app.listen(3000, () => console.log('Transfer server on port 3000'));
```

## Common mistakes

- **Transferring more than the source_transaction amount** — undefined Fix: When using source_transaction, the total transfers linked to a charge cannot exceed the charge amount. Stripe will reject the transfer.
- **Not checking platform balance before creating transfers** — undefined Fix: If your platform balance is insufficient, the transfer fails. Check balance.available first or use source_transaction to tie transfers to charges.
- **Creating transfers before the charge settles** — undefined Fix: Funds from charges are available after the settlement period (usually 2 days). Use source_transaction or wait for charge.succeeded webhook.
- **Not using idempotency keys for transfers** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-transfer-funds-between-stripe-accounts-using-connect
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-transfer-funds-between-stripe-accounts-using-connect
