Bonaventure OgetoBy Bonaventure Ogeto|

Paystack Split Not Applied to Transaction

Paystack splits fail to apply for four main reasons: the split_code was not included in the transaction initialization call, the subaccount in the split is inactive or has invalid bank details, the split was created but not set as the default split for your integration, or the transaction was initialized before the split was created. Verify the split exists via the API, confirm the subaccount is active, and include the split_code in every transaction initialization request.

How Paystack Transaction Splits Work

Paystack transaction splits let you automatically divide payment proceeds between multiple parties. A marketplace that takes a commission, a platform that pays vendors, a service that shares revenue with partners. Splits handle the accounting at the payment layer so you do not have to calculate and transfer funds manually.

The flow works like this:

  1. You create one or more subaccounts. Each subaccount represents a party that receives a share of the payment.
  2. You create a split configuration that defines how money is divided: percentage-based, flat amount, or a combination.
  3. When initializing a transaction, you include the split_code in the request. Paystack applies the split when the payment succeeds.
  4. After settlement, each party receives their share directly from Paystack. You do not need to transfer funds yourself.

When a split "does not apply," it means the full amount settled to your main account as if the split did not exist. The customer paid the correct amount, but the money was not divided. This is a configuration problem, not a payment problem.

Cause 1: split_code Not Passed in Transaction Initialization

This is the most common reason. You created the split in the dashboard, but you did not include the split_code when initializing the transaction. Paystack does not know to apply it.

Unless you have set a default split (more on that below), you must explicitly pass the split_code in every transaction initialization request.

const axios = require('axios');

// WRONG: No split_code - entire amount goes to main account
async function initializeWithoutSplit() {
  const response = await axios.post(
    'https://api.paystack.co/transaction/initialize',
    {
      email: 'customer@example.com',
      amount: 1000000, // 10,000 NGN in kobo
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
    }
  );
  return response.data;
}

// CORRECT: split_code included - payment will be split
async function initializeWithSplit() {
  const response = await axios.post(
    'https://api.paystack.co/transaction/initialize',
    {
      email: 'customer@example.com',
      amount: 1000000, // 10,000 NGN in kobo
      split_code: 'SPL_xxxxxxxxxxxxxxx', // Your split code
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
        'Content-Type': 'application/json',
      },
    }
  );
  return response.data;
}

If you are using the Popup or Inline JS to initialize (not the server-side API), you cannot pass split_code from the frontend. You must initialize the transaction on your server, get the access_code, and pass that to the popup. The split_code goes in the server-side initialization call, not in the frontend JavaScript.

Cause 2: Subaccount Is Inactive or Has Invalid Bank Details

Every subaccount in a split must be active and have valid, verified bank details. If a subaccount is inactive (because bank verification failed, or it was manually deactivated), the split will not apply.

Check the status of your subaccounts:

async function checkSubaccountStatus(subaccountCode) {
  try {
    const response = await axios.get(
      `https://api.paystack.co/subaccount/${subaccountCode}`,
      {
        headers: {
          Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
        },
      }
    );

    const sub = response.data.data;
    console.log('Subaccount:', sub.business_name);
    console.log('Active:', sub.is_verified);  // Must be true
    console.log('Bank:', sub.settlement_bank);
    console.log('Account number:', sub.account_number);
    console.log('Percentage charge:', sub.percentage_charge);

    if (!sub.is_verified) {
      console.warn('This subaccount is NOT verified. Splits using it will fail.');
    }

    return sub;
  } catch (error) {
    if (error.response?.status === 404) {
      console.error('Subaccount not found. Check the subaccount code.');
    }
    throw error;
  }
}

Common bank detail problems:

  • The account number does not match the account name at the bank. Paystack verifies this during subaccount creation.
  • The bank code is wrong. Each bank has a specific code (e.g., 057 for Zenith, 033 for UBA). Using the wrong code causes verification failure.
  • The account was closed or frozen at the bank after the subaccount was created in Paystack.

Cause 3: Split Configuration Errors

Even if you pass the split_code and the subaccounts are active, the split can fail if the configuration itself is invalid.

Percentage splits must add up correctly. If you create a percentage split where the shares exceed 100%, or where there is an unaccounted remainder, the split behavior may be unpredictable.

Flat splits must not exceed the transaction amount. If you have a flat split that takes 5,000 NGN from each transaction, but a customer pays only 3,000 NGN, the split cannot be applied because there is not enough money.

// Verify your split configuration
async function verifySplitConfig(splitCode) {
  try {
    const response = await axios.get(
      `https://api.paystack.co/split/${splitCode}`,
      {
        headers: {
          Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
        },
      }
    );

    const split = response.data.data;
    console.log('Split name:', split.name);
    console.log('Split type:', split.type); // "percentage" or "flat"
    console.log('Currency:', split.currency);
    console.log('Active:', split.active);
    console.log('Bearer type:', split.bearer_type); // "all", "account", or "subaccount"
    console.log('Bearer subaccount:', split.bearer_subaccount);

    console.log('\nSubaccounts in this split:');
    for (const sub of split.subaccounts) {
      console.log(`  - ${sub.subaccount.business_name}: ${sub.share} (${split.type})`);
    }

    // Check for issues
    if (split.type === 'percentage') {
      const totalShare = split.subaccounts.reduce((sum, s) => sum + s.share, 0);
      console.log(`\nTotal percentage allocated: ${totalShare}%`);
      if (totalShare > 100) {
        console.warn('Total exceeds 100%. This split is misconfigured.');
      }
    }

    return split;
  } catch (error) {
    console.error('Failed to fetch split:', error.response?.data || error.message);
    throw error;
  }
}

Also check the bearer_type. This determines who pays the Paystack transaction fee:

  • "all": The fee is split proportionally among all parties.
  • "account": Your main account absorbs the entire fee.
  • "subaccount": A specific subaccount absorbs the fee.

Using a Default Split Instead of Per-Transaction split_code

If every transaction on your integration should use the same split, you can set a default split in the Paystack dashboard. Go to Settings > Preferences > Transaction Split, and select your split configuration. Once set, every transaction on that integration is automatically split without needing split_code in the API call.

This is convenient but comes with a catch: if you need different splits for different transactions (e.g., different vendor shares for different products), a default split will not work. You need per-transaction split_code values.

When to use a default split:

  • Simple marketplace with one standard commission rate
  • Revenue sharing with a single partner at a fixed percentage
  • Platform fee that is the same for every transaction

When to use per-transaction split_code:

  • Multi-vendor marketplace where each vendor has a different split
  • Variable commission rates based on product category
  • Transactions where some should be split and others should not

If you have a default split set but also pass a split_code in the transaction, the per-transaction split_code takes precedence. This lets you override the default for specific transactions.

Creating a Split Correctly from Scratch

If your split is not working, sometimes the fastest fix is to create a new one from scratch, verifying each step.

const axios = require('axios');

const PAYSTACK_SECRET = process.env.PAYSTACK_SECRET_KEY;
const headers = {
  Authorization: `Bearer ${PAYSTACK_SECRET}`,
  'Content-Type': 'application/json',
};

async function setupSplitFromScratch() {
  // Step 1: Create subaccount (or use existing one)
  const subaccountResponse = await axios.post(
    'https://api.paystack.co/subaccount',
    {
      business_name: 'Vendor Store',
      bank_code: '057',     // Zenith Bank
      account_number: '2190209948',
      percentage_charge: 80, // Vendor gets 80% (used if no split overrides)
    },
    { headers }
  );

  const subaccountCode = subaccountResponse.data.data.subaccount_code;
  console.log('Subaccount created:', subaccountCode);

  // Step 2: Create the split
  const splitResponse = await axios.post(
    'https://api.paystack.co/split',
    {
      name: 'Platform 20/80 Split',
      type: 'percentage',
      currency: 'NGN',
      subaccounts: [
        {
          subaccount: subaccountCode,
          share: 80, // 80% to vendor
        },
      ],
      bearer_type: 'account',    // Main account pays the fee
      bearer_subaccount: '',
    },
    { headers }
  );

  const splitCode = splitResponse.data.data.split_code;
  console.log('Split created:', splitCode);

  // Step 3: Test with a transaction
  const txnResponse = await axios.post(
    'https://api.paystack.co/transaction/initialize',
    {
      email: 'buyer@example.com',
      amount: 1000000, // 10,000 NGN
      split_code: splitCode,
    },
    { headers }
  );

  console.log('Transaction initialized:', txnResponse.data.data.reference);
  console.log('Checkout URL:', txnResponse.data.data.authorization_url);

  return { subaccountCode, splitCode, reference: txnResponse.data.data.reference };
}

setupSplitFromScratch().catch(console.error);

After the test transaction completes (use test card 4084 0840 8408 4081 in sandbox), verify the split was applied by checking the transaction details.

Verifying the Split Was Actually Applied

After a transaction completes, verify the split through the API. Do not assume it worked just because the payment succeeded.

async function verifySplitApplied(reference) {
  const response = await axios.get(
    `https://api.paystack.co/transaction/verify/${reference}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
      },
    }
  );

  const data = response.data.data;

  console.log('Transaction status:', data.status);
  console.log('Total amount:', data.amount / 100, 'NGN');
  console.log('Fees:', data.fees / 100, 'NGN');

  if (data.split) {
    console.log('\nSplit applied: YES');
    console.log('Split code:', data.split.split_code);

    // Check each share
    if (data.subaccount) {
      console.log('Subaccount:', data.subaccount.business_name);
      console.log('Subaccount share:', data.subaccount.share);
    }
  } else {
    console.warn('\nSplit applied: NO');
    console.warn('The full amount went to the main account.');
    console.warn('Check that split_code was included in the initialization request.');
  }
}

verifySplitApplied('your_transaction_reference').catch(console.error);

You can also check in the Paystack dashboard. Go to Transactions, find the transaction, and click on it. The transaction details page shows the split breakdown including each party's share and the fee allocation.

Split Debugging Checklist

Run through this list before contacting support.

  1. Is split_code in the initialize request? Log the request body just before sending it. Confirm split_code is present and not undefined or null.
  2. Is the split_code valid? Call GET /split/{split_code} and confirm it returns a valid response. A 404 means the code is wrong.
  3. Are all subaccounts active? For each subaccount in the split, call GET /subaccount/{code} and verify is_verified is true.
  4. Does the split currency match the transaction currency? A split configured for NGN will not apply to a GHS transaction.
  5. For flat splits: is the total share less than the transaction amount? The split cannot take more than the customer paid.
  6. For percentage splits: do the shares add up to 100% or less? Shares exceeding 100% make the split invalid.
  7. Was the split created before the transaction? Splits are not retroactive. The split must exist before the transaction is initialized.
  8. Is there a default split overriding your per-transaction split? Check Settings > Preferences in the dashboard.

How to Verify Your Fix

After fixing the split configuration, run this verification flow.

  1. Fetch the split via GET /split/{split_code} and confirm it is active with the correct subaccounts and shares.
  2. Initialize a test transaction with the split_code included.
  3. Complete the payment using test card 4084 0840 8408 4081.
  4. Call GET /transaction/verify/{reference} and confirm the split field is present in the response.
  5. Check the Paystack dashboard transaction details page. The split breakdown should show each party's share.
  6. If you are using webhooks, verify the charge.success event payload includes split information.

Once confirmed in sandbox, deploy the same configuration to live mode. Remember that live mode subaccounts and splits are separate from test mode ones. You need to recreate them (or create them via API) in live mode with real bank details.

Key Takeaways

  • The most common cause is forgetting to pass split_code when initializing the transaction. Splits do not apply automatically unless you set a default split in the dashboard.
  • Subaccounts in a split must be active with verified bank details. If any subaccount in a multi-split is inactive, the entire split may fail silently.
  • You can set a default split in the Paystack dashboard under Settings > Preferences. This applies the split to all transactions without needing to pass split_code each time.
  • Always verify the transaction after payment to confirm the split was applied. The verify response includes split details and individual settlement amounts.
  • Flat splits use fixed amounts (e.g., 500 NGN to subaccount A). Percentage splits use ratios. Make sure your split type matches your business logic.
  • Splits created after a transaction was initialized will not apply retroactively. The split must exist before the transaction is created.
  • Test splits in sandbox mode first. Create test subaccounts, run a test transaction with the split_code, and verify the settlement breakdown.

Frequently Asked Questions

Can I apply a split to a transaction after it has already been paid?
No. Splits must be configured before the transaction is initialized. Once a payment is completed without a split, the full amount settles to your main account. You would need to manually transfer the split share using the Transfer API.
What happens if a subaccount in my split becomes inactive after I create the split?
The split may fail silently for new transactions. Paystack will not apply a split if any subaccount in it has invalid or unverified bank details. Periodically check your subaccount statuses and update bank details if they change.
Can I have different splits for different products on the same Paystack integration?
Yes. Create multiple split configurations, each with its own split_code. When initializing a transaction, pass the appropriate split_code for that product or vendor. Do not use a default split if you need this flexibility.
How does Paystack handle transaction fees with splits?
The bearer_type field in the split configuration determines who pays the fee. "all" splits the fee proportionally, "account" makes your main account pay the entire fee, and "subaccount" assigns the fee to a specific subaccount. Choose based on your business model.
Do splits work with all payment channels (card, bank transfer, USSD)?
Yes. Splits apply regardless of the payment channel. Whether the customer pays by card, bank transfer, USSD, or mobile money, the split is applied at the settlement level after the payment succeeds.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon