Migrating from Direct Daraja to Paystack
To migrate from Daraja to Paystack, you replace your STK Push calls with Paystack Charge API calls, replace your Daraja callbacks with Paystack webhooks, and update your database to track Paystack references instead of Daraja CheckoutRequestIDs. Run both in parallel for at least two weeks before cutting over. The customer experience barely changes. The backend changes are significant but well-defined.
Why Migrate from Daraja to Paystack
You built your product on Daraja. It works. M-Pesa payments come in through STK Push, your callbacks process them, and money lands in your paybill. So why change?
Here are the concrete reasons teams migrate:
- You need card payments. A corporate client wants to pay by Visa. An international customer cannot use M-Pesa. Daraja does not handle cards. Paystack does. Rather than integrating a separate card provider alongside Daraja, some teams consolidate everything on Paystack.
- You need Pesalink. High-value transactions hit M-Pesa's per-transaction limit. Pesalink supports larger amounts through bank-to-bank transfer. Paystack offers Pesalink as a payment channel. Daraja does not.
- You want to expand beyond Kenya. Daraja is Safaricom's API. It only works in Kenya. Paystack operates in Nigeria, Ghana, South Africa, and other markets. If your product is expanding across Africa, Paystack gives you one integration for multiple countries.
- You want better tooling. Paystack's dashboard, settlement reports, webhook logs, and API documentation are generally considered more developer-friendly than Daraja's. If your team spends significant time debugging Daraja callbacks, reconciling M-Pesa statements, or managing OAuth token refreshes, Paystack simplifies the operational side.
- You want simpler infrastructure. Daraja requires managing OAuth tokens (they expire), certificate updates, callback URL configuration, and IP whitelisting. Paystack uses a simpler auth model (secret key in the header) and HMAC webhook verification. Fewer moving parts means fewer things to break.
These are valid reasons. But there are also valid reasons not to migrate. If M-Pesa is your only payment method, your volume is low, and you are happy with Daraja's feature set, the migration adds work without clear benefit. See Migrating from Paystack to Direct Daraja for the case in the other direction.
What Changes and What Stays the Same
Before diving into the migration steps, here is a clear picture of what is different and what is not.
What changes:
| Component | Daraja (Before) | Paystack (After) |
|---|---|---|
| Charge initiation | Daraja STK Push API (OAuth + passkey) | Paystack Charge API (secret key header) |
| Payment confirmation | Daraja callback (custom JSON format) | Paystack webhook (charge.success event) |
| Authentication | OAuth token (expires, needs refresh) | Secret key in Authorization header |
| Webhook security | IP whitelisting (or nothing) | HMAC-SHA512 signature verification |
| Amount format | Whole shillings (1500 = KES 1,500) | Cents (150000 = KES 1,500) |
| Settlement | Directly to your paybill/till | Paystack settles to your bank/M-Pesa wallet |
| Paybill on customer phone | Your own paybill number | Paystack's shortcode |
| Dashboard | Safaricom portal | Paystack dashboard |
What stays the same:
- The customer experience. They still get an STK push on their phone. They still enter their M-Pesa PIN. The payment goes through. From the customer's perspective, almost nothing changes.
- Your business logic. Order fulfillment, receipt generation, subscription management. These depend on knowing a payment succeeded, not on which provider processed it.
- Your database schema (mostly). You add a provider column if you do not have one. The payments table structure stays the same.
Migration Checklist
Here is the full list of tasks, in order. Check each one off as you complete it.
- Create a Paystack account at paystack.com. Complete business verification. This takes a few days, so start early.
- Get your API keys. You get a test secret key and a live secret key. Start with the test key.
- Set up your webhook URL in the Paystack dashboard. This should be a new endpoint, not your existing Daraja callback URL. Something like
/webhooks/paystack. - Add a provider column to your payments table. Default to 'daraja' for existing records. New Paystack payments will use 'paystack'.
- Build the Paystack charge function. Replace your Daraja STK Push code with a Paystack Charge API call. Watch the amount conversion (whole shillings to cents).
- Build the Paystack webhook handler. Parse the charge.success event, verify the HMAC signature, and call your existing payment confirmation logic.
- Test end to end on Paystack's test environment. Use test credentials and verify the full flow: initiate charge, receive webhook, update database.
- Build the payment router. A function that decides whether to send a charge to Paystack or Daraja. Start with routing 0% to Paystack (Daraja handles everything, just like before).
- Deploy to production with the router still at 0% Paystack. Verify the new code does not break existing Daraja payments.
- Switch to live Paystack keys and route 5-10% of M-Pesa traffic to Paystack. Monitor closely.
- Gradually increase the percentage. 25%, then 50%, then 75%, then 100%. Wait at least a few days at each stage. Watch for webhook delivery issues, amount mismatches, and settlement discrepancies.
- Once at 100% Paystack, keep Daraja credentials active and the callback URL live for at least 30 days. Some Daraja callbacks for old transactions may still arrive.
- After 30 days with no Daraja traffic, you can decommission the Daraja integration. But honestly, many teams keep it dormant as a fallback. The cost of keeping the credentials is zero.
The Code Changes
Here is what the migration looks like in code. We will compare the Daraja version and the Paystack version side by side.
Before: Daraja STK Push
// Daraja charge (what you have now)
async function chargeMpesaDaraja({ phone, amount, reference }) {
const token = await getDarajaToken(); // OAuth flow
const timestamp = formatTimestamp();
const password = generateDarajaPassword(timestamp);
const res = await fetch(
'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
BusinessShortCode: process.env.DARAJA_SHORTCODE,
Password: password,
Timestamp: timestamp,
TransactionType: 'CustomerPayBillOnline',
Amount: amount, // Whole shillings
PartyA: phone,
PartyB: process.env.DARAJA_SHORTCODE,
PhoneNumber: phone,
CallBackURL: `${process.env.BASE_URL}/webhooks/daraja/stk`,
AccountReference: reference,
TransactionDesc: `Payment ${reference}`,
}),
}
);
return res.json();
}
After: Paystack Charge API
// Paystack charge (what you are migrating to)
async function chargeMpesaPaystack({ phone, amount, reference, email }) {
const res = await fetch('https://api.paystack.co/charge', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
amount: amount * 100, // Convert shillings to cents
currency: 'KES',
reference,
mobile_money: {
phone,
provider: 'mpesa',
},
}),
});
return res.json();
}
Key differences to note:
- No OAuth. Paystack uses a static secret key. No token refresh, no expiry, no password generation.
- Email is required. Paystack requires a customer email. If your Daraja integration did not collect emails, you need to add this to your checkout form. Some teams use a placeholder like
phone@placeholder.com, but real emails are better for Paystack's transaction notifications. - Amount is in cents. If your codebase stores amounts in whole shillings (which is common for Daraja-first products), you need to multiply by 100 when calling Paystack. This is the most common source of bugs during migration.
- Webhook URL is configured in the dashboard, not in the API call. With Daraja, you pass the CallBackURL in every STK Push request. With Paystack, you set it once in the dashboard.
The Parallel Running Period
Do not do a hard cutover. Run both providers in parallel for at least two weeks, gradually shifting traffic from Daraja to Paystack.
// paymentRouter.js - gradual migration
const PAYSTACK_PERCENTAGE = parseInt(process.env.PAYSTACK_PERCENTAGE || '0');
async function chargeMpesa({ phone, amount, reference, email }) {
const usePaystack = Math.random() * 100 < PAYSTACK_PERCENTAGE;
if (usePaystack) {
try {
const result = await chargeMpesaPaystack({ phone, amount, reference, email });
await savePayment({ reference, provider: 'paystack', providerRef: result.data?.reference });
return result;
} catch (err) {
// If Paystack fails during migration, fall back to Daraja
console.error('Paystack charge failed during migration, using Daraja:', err.message);
const result = await chargeMpesaDaraja({ phone, amount, reference });
await savePayment({ reference, provider: 'daraja', providerRef: result.CheckoutRequestID });
return result;
}
}
const result = await chargeMpesaDaraja({ phone, amount, reference });
await savePayment({ reference, provider: 'daraja', providerRef: result.CheckoutRequestID });
return result;
}
The PAYSTACK_PERCENTAGE is an environment variable you change without redeploying. Start at 0. Bump to 10. Watch for a day. Bump to 25. Watch for a few days. Go to 50, then 75, then 100.
During the parallel period, monitor these things:
- Success rate. Are Paystack charges succeeding at the same rate as Daraja charges? A significant difference means something is wrong with the Paystack integration.
- Webhook delivery. Are Paystack webhooks arriving reliably? Check your webhook endpoint logs. Paystack shows delivery attempts in the dashboard.
- Amount accuracy. Verify that the amounts in your database match what Paystack reports. The cents-vs-shillings conversion is the most likely source of discrepancies.
- Settlement. Confirm that Paystack settlements are arriving in your bank account as expected. Compare settlement amounts against your database totals.
- Customer complaints. If customers report issues ("I paid but did not get my order"), check which provider processed their payment and investigate.
The fallback to Daraja during the migration is intentional. If Paystack fails for any reason, the customer still gets charged through Daraja. No lost sales during the migration window.
Handling Existing Subscribers and Recurring Payments
If you run a subscription product, migrating the charge provider is trickier than migrating one-time payments.
Daraja does not have built-in subscriptions. If you were running subscriptions on Daraja, you almost certainly have a cron job or scheduler that initiates STK Push charges on a schedule. Your subscriber records live in your database, and your code controls when to charge them.
When you migrate to Paystack, you have two options:
Option 1: Keep your own subscription logic. Replace the Daraja STK Push call in your cron job with a Paystack Charge API call. Everything else stays the same. Your database still controls who to charge and when. This is the simplest migration path because you are only changing the charge function, not the subscription logic.
Option 2: Migrate to Paystack Subscriptions. Paystack has a Plans and Subscriptions system. You create a Plan (e.g., "Monthly Pro - KES 2,000/month") and subscribe customers to it. Paystack handles the scheduling and charge attempts. This simplifies your code, but the migration is more involved because you need to create subscriptions for all existing customers and handle the transition period where some are on your old system and some are on Paystack's system.
For M-Pesa subscriptions specifically, remember that each M-Pesa charge requires the customer to enter their PIN. Paystack cannot auto-debit M-Pesa. So even with Paystack Subscriptions, the customer gets an STK push each billing cycle and has to actively approve it. This is the same as Daraja. The customer experience does not change.
My recommendation: use Option 1 for the migration. Keep your subscription logic. Just swap the charge function. Once the migration is complete and stable, you can evaluate whether Paystack Subscriptions offer enough benefit to justify a second migration of the subscription logic itself.
Settlement Flow Changes
This is where the migration has the biggest impact on your finance team.
Before (Daraja): Customer pays. Money goes to your paybill or till number. It is in your M-Pesa business account immediately (or very quickly). You can check your balance, download statements, and withdraw to your bank on your own schedule.
After (Paystack): Customer pays. Money goes to Paystack's pool account. Paystack settles to your registered bank account (or M-Pesa wallet, if available) on their settlement schedule. You do not have the money until settlement happens.
This is a significant change for businesses that rely on immediate access to funds. With Daraja and your own paybill, you can access the money right away. With Paystack, there is a settlement delay.
Things your finance team needs to know:
- Settlement timing. Check Paystack's settlement schedule for Kenya. It varies by account type and payment method. [TODO: verify current figure on paystack.com/pricing]
- Fees are deducted before settlement. The amount you receive is the transaction amount minus Paystack's fee. Your accounting needs to track the gross amount, the fee, and the net settlement.
- Settlement reports replace M-Pesa statements as the source of truth for Paystack transactions. The dashboard shows exactly which transactions are in each settlement batch.
- Bank account details must be correct. If your bank details are wrong, settlement fails. Verify this during onboarding, not after your first batch of customer payments.
Some teams are surprised by the settlement change and want to go back to Daraja just for this reason. That is a legitimate consideration. If immediate access to funds is critical for your cash flow, factor it into your migration decision. See the Paystack Kenya Settlement guide for details.
Rollback Plan
Every migration needs a rollback plan. Here is yours.
During the parallel running period: Rollback is trivial. Set PAYSTACK_PERCENTAGE to 0. All new charges go to Daraja. Existing Paystack charges continue to be confirmed via webhooks. No data is lost.
After full cutover to Paystack: Rollback is still possible if you kept your Daraja credentials active and the callback URL live. Update your charge function to call Daraja instead of Paystack. Deploy. New charges go through Daraja. Paystack webhooks for in-flight charges still arrive and get processed. You are back on Daraja.
What you cannot roll back:
- Payments already processed by Paystack stay in Paystack. You cannot retroactively move them to Daraja. But they are recorded in your database with
provider: 'paystack', and you can reconcile them against Paystack's records at any time. - If you migrated to Paystack Subscriptions (Option 2 above), rolling back means recreating your subscription scheduler and migrating all active subscribers back to your own system. This is harder. Another reason to use Option 1 for the initial migration.
Keep Daraja credentials alive for at least 90 days after full cutover. There is no cost to having dormant Daraja credentials. The cost of not having them when you need to roll back is significant.
Some teams keep Daraja permanently as a fallback, even after full migration. If Paystack ever has a prolonged outage, they can switch M-Pesa traffic back to Daraja within minutes. This is the architecture described in Building an M-Pesa Fallback When Paystack Is Unavailable.
What You Gain and What You Lose
Be clear-eyed about the trade-offs before committing to this migration.
What you gain:
- Card payments, Pesalink, Airtel Money, Apple Pay, and bank transfers through the same integration.
- A better developer experience: simpler auth, cleaner API, better docs, webhook signature verification.
- Paystack's dashboard for your operations team: transaction search, settlement reports, customer management.
- Multi-country support if you expand beyond Kenya.
- Built-in support for recurring payments, split payments, and transfer payouts.
- Less infrastructure to manage. No OAuth token rotation, no certificate updates, no IP whitelisting.
What you lose:
- Your own paybill/till number on the customer's phone. With Paystack, they see Paystack's shortcode.
- C2B validation (the ability to approve or reject each M-Pesa payment before it completes).
- Direct B2C and B2B APIs. You can still do payouts via Paystack Transfers, but the API is different.
- Immediate access to funds. Money goes through Paystack's settlement cycle instead of landing in your paybill instantly.
- Full control over the M-Pesa flow. Paystack abstracts the Daraja layer, which is simpler but means you cannot customize every parameter.
For most products, the gains outweigh the losses. But "most" is not "all." If C2B validation is central to your product, or if immediate fund access is a business requirement, think carefully before migrating.
Key Takeaways
- ✓The main reasons to migrate from Daraja to Paystack: add card and Pesalink support, simplify your payment infrastructure, get a better dashboard and reconciliation tools, or expand to other African countries.
- ✓What changes: your charge initiation code, your webhook/callback handler, your settlement flow, and your checkout UI. What stays the same: the customer M-Pesa experience (they still get an STK push and enter their PIN).
- ✓Run both providers in parallel for at least two weeks. Route a percentage of traffic to Paystack while keeping Daraja as the default. Increase the percentage gradually as you verify everything works.
- ✓Your database migration is straightforward. Add a provider column to your payments table if you do not have one. New payments get provider = "paystack". Old payments keep provider = "daraja". Nothing is deleted.
- ✓Settlement changes significantly. With Daraja, money went to your paybill. With Paystack, money settles to your bank account or M-Pesa wallet on Paystack's schedule. Your finance team needs to know about this before you switch.
- ✓Always have a rollback plan. Keep your Daraja credentials active and your callback URL live until you are fully confident in the Paystack integration. Rollback means switching the router back to Daraja.
- ✓You will lose some Daraja-specific features: C2B validation, your own paybill branding, direct B2C/B2B APIs. Make sure you do not need these before migrating.
Frequently Asked Questions
- How long does the full migration take?
- Plan for 4 to 6 weeks total. One week for Paystack account setup and verification. One week for development and testing. Two weeks minimum for the parallel running period. And a buffer for unexpected issues. Do not try to rush this. Payment migrations affect real money.
- Do I need to tell my customers about the migration?
- For most products, no. The customer experience barely changes. They still get an STK push, enter their PIN, and get a receipt. The only visible difference is the paybill number on the STK push prompt. If your customers are particularly observant or if your support team references the paybill number in documentation, a brief notice might help.
- What happens to historical payment data after migration?
- Nothing. Your existing payment records stay in your database with provider set to "daraja." New payments get provider set to "paystack." You do not delete or modify historical data. Your reports and analytics should query by date range, not by provider, unless you specifically need a per-provider breakdown.
- Can I migrate only M-Pesa to Paystack and keep other Daraja features?
- Yes. Many teams migrate inbound M-Pesa payments to Paystack for the multi-method checkout while keeping Daraja for B2C disbursements or B2B transfers. This is the dual-provider architecture described in the Running Paystack and Daraja Side by Side article. You get the best of both.
- What if Paystack is not approved in my business category?
- Paystack has compliance requirements and may not support every business type. Check during account registration whether your business category is accepted. If Paystack cannot onboard you, the migration is not possible and you should stay on Daraja. Consider adding cards through a different provider like IntaSend or PesaPal instead.
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