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.
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:
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.
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:
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Here is the full list of tasks, in order. Check each one off as you complete it.
/webhooks/paystack.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:
phone@placeholder.com, but real emails are better for Paystack's transaction notifications.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:
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.
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.
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:
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.
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:
provider: 'paystack', and you can reconcile them against Paystack's records at any time.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.
Be clear-eyed about the trade-offs before committing to this migration.
What you gain:
What you lose:
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.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course