Bonaventure OgetoBy Bonaventure Ogeto|

Delayed Settlement Patterns for Marketplaces

Create vendor subaccounts with settle_interval: "manual" (requires Paystack support approval). Funds collected through split transactions accumulate in the subaccount without being settled. When the release condition is met (e.g., buyer confirms delivery), call POST /settlement or transfer from your held balance to the vendor. Track hold state in your database with release_at timestamps.

Hold Scheduler Database Pattern

CREATE TABLE payout_holds (
  id SERIAL PRIMARY KEY,
  vendor_id INTEGER NOT NULL,
  reference TEXT NOT NULL,
  amount NUMERIC(12, 2) NOT NULL,
  currency TEXT DEFAULT 'NGN',
  hold_reason TEXT,         -- 'delivery_pending' | 'dispute_window' | 'event_date'
  hold_until TIMESTAMPTZ NOT NULL,
  released_at TIMESTAMPTZ,
  status TEXT DEFAULT 'held', -- 'held' | 'released' | 'disputed' | 'refunded'
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_holds_release ON payout_holds(status, hold_until);
// On charge.success, create a hold record
if (event.event === 'charge.success') {
  var holdUntil = new Date();
  holdUntil.setDate(holdUntil.getDate() + 7); // 7-day dispute window

  await db.payoutHolds.create({
    vendor_id: order.vendor_id,
    reference: event.data.reference,
    amount: event.data.amount / 100 * 0.9, // Vendor share (90%)
    hold_reason: 'dispute_window',
    hold_until: holdUntil,
    status: 'held',
  });
}

// Daily cron: release matured holds
async function releaseMatureHolds() {
  var now = new Date();
  var holds = await db.payoutHolds.findAll({
    where: { status: 'held', hold_until: { lte: now } }
  });

  for (var hold of holds) {
    var vendor = await db.vendors.findById(hold.vendor_id);
    await initiateTransfer(vendor.recipient_code, hold.amount, hold.currency);
    await db.payoutHolds.update(
      { status: 'released', released_at: now },
      { where: { id: hold.id } }
    );
  }
}

Learn More

Key Takeaways

  • Paystack manual settlement holds subaccount funds until you trigger release — enabling hold-and-release patterns.
  • Contact Paystack support to enable manual settlement (settle_interval: "manual") on your account.
  • Build a release_scheduler table: store the split transaction reference, hold_until date, and trigger condition.
  • Run a cron job daily to check for matured holds and trigger payouts for eligible records.
  • Build a dispute window (e.g., 7 days) — if no dispute is raised by the buyer, auto-release on day 8.
  • Communicate hold periods clearly to vendors during onboarding — unexpected holds cause support escalations.

Frequently Asked Questions

Do I need Paystack approval for manual settlement?
Yes. Manual settlement (settle_interval: "manual") is not available by default. Contact Paystack support or your account manager to enable it. Without this, you can simulate delayed settlement by routing funds to your platform account first and manually initiating transfers when conditions are met.
What is the maximum hold period Paystack allows?
Paystack does not publish a fixed maximum hold period for manual settlement. Keep holds reasonable (7-30 days for most use cases). Extremely long holds (months) may attract compliance review. Build your hold logic in your database so you can release at any time regardless of Paystack limits.
How do I handle a hold when a buyer raises a dispute?
Update the payout_hold record to status: "disputed". Pause the auto-release cron for that hold. After dispute resolution — either refund the buyer (refunded) or rule in the vendor's favour (release) — update the record and take the appropriate action.
Can I offer instant payouts as a premium feature for vendors?
Yes. Build a vendor setting: instant_payout: true. When this is enabled and a charge comes in, skip the hold and immediately transfer to the vendor. Charge a higher platform fee for instant payouts to compensate for the dispute risk you absorb.

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