Bonaventure OgetoBy Bonaventure Ogeto|

Building a Freelance Marketplace on Paystack

Collect the full project amount from the client. Hold the freelancer's share using a subaccount with manual settlement enabled. When the client approves the work, call POST /settlement/initiate (or trigger transfer from your balance) to release funds to the freelancer. Your platform commission is the percentage not allocated to the subaccount. Handle disputes by withholding settlement until resolved.

Hold Freelancer Payment on Charge

// Client pays for a project
app.post('/api/projects/:id/pay', async (req, res) => {
  var project = await db.projects.findById(req.params.id);
  var freelancer = await db.freelancers.findById(project.freelancer_id);
  var commissionRate = 0.10; // 10% platform commission
  var freelancerShare = Math.round((1 - commissionRate) * 100); // 90%

  var reference = 'proj_' + project.id + '_' + Date.now();

  var response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: req.user.email,
      amount: project.budget * 100,
      subaccount: freelancer.subaccount_code,
      percentage_charge: freelancerShare,
      bearer: 'account',
      reference,
      metadata: { project_id: project.id, action: 'escrow_hold' },
    }),
  });

  var data = await response.json();
  res.json({ authorization_url: data.data.authorization_url });
});

Important: For true delayed settlement, create the freelancer subaccount with settle_interval: "manual". This holds the funds until you explicitly trigger settlement. Contact Paystack support to enable manual settlement on your account.

Release After Client Approval

// Client approves the delivered work
app.post('/api/projects/:id/approve', async (req, res) => {
  var project = await db.projects.findById(req.params.id);

  await db.projects.update({ status: 'approved', approved_at: new Date() }, { where: { id: project.id } });

  // If using manual settlement: initiate settlement for the subaccount
  // Or: transfer from your balance to the freelancer recipient
  await fetch('https://api.paystack.co/transfer', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      source: 'balance',
      recipient: project.freelancer_recipient_code,
      amount: Math.round(project.freelancer_earnings * 100),
      reason: 'Project payment: ' + project.title,
      reference: 'release_' + project.id,
    }),
  });

  res.json({ released: true });
});

Learn More

Key Takeaways

  • Use subaccount with manual settlement (settle_interval: "manual") to hold the freelancer's share until client approval.
  • Your platform commission stays on your main account — only the freelancer's share goes to the subaccount.
  • After client approval, release funds via Paystack settlement initiation or a transfer from your balance.
  • Set a dispute window (e.g., 7 days after delivery) before auto-releasing if no dispute is raised.
  • Store project_id in transaction metadata for webhook handlers to match payments to projects.
  • Paystack does not have native escrow — build the hold logic in your database with status flags.

Frequently Asked Questions

Does Paystack support true escrow?
No. Paystack does not offer true third-party escrow. The hold-and-release pattern approximates escrow using manual settlement or platform-held balances, but you are not a licensed escrow agent. For large project values or regulated escrow, work with a licensed escrow provider.
How long can I hold funds before releasing to a freelancer?
With manual settlement, Paystack can hold subaccount settlement for days to weeks. However, extended holds may raise compliance questions. Document your hold period in your terms of service and keep it reasonable (7-14 days for most freelance disputes).
What happens to the commission if a project is refunded?
Paystack refunds the full amount charged to the customer. The commission you held is also refunded. Your platform absorbs the refund from its balance. Design your refund policy carefully and build contingency into your commission rates to cover refund scenarios.
Can freelancers from other African countries use their local bank accounts?
Paystack supports subaccounts in GHS (Ghana), ZAR (South Africa), KES (Kenya), and other supported currencies in addition to NGN. Create subaccounts in the appropriate currency for each freelancer's country. Settlement goes to their local bank in their local currency.

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