Migrating from Paystack to Direct Daraja
To migrate from Paystack to Daraja, you apply for Safaricom Daraja API credentials, set up your paybill or till number, replace Paystack Charge API calls with Daraja STK Push calls, replace Paystack webhooks with Daraja callbacks, and build or source a separate solution for card payments. The M-Pesa customer experience stays the same. The engineering and operational complexity increases.
Why You Might Go Direct
Moving from Paystack to Daraja goes against the typical direction. Most teams add a gateway, not remove one. So why would you do this?
Cost at volume. Paystack charges a per-transaction fee for M-Pesa payments. Safaricom's Daraja has its own fee structure, which is set directly by Safaricom. At high transaction volumes (thousands of M-Pesa charges per day), the difference between Paystack's fee and Daraja's fee compounds into real money. If M-Pesa is 90% or more of your transactions, and your volume is high, the fee savings from going direct can be significant.
C2B Validation. This is the feature most people do not know about until they need it. With Daraja's C2B API, when a customer pays your paybill, Safaricom sends a validation request to your server before completing the transaction. You can approve or reject the payment in real time. Use cases: reject payments from unregistered customers, reject incorrect amounts, reject payments to wrong account references. Paystack does not expose this because Paystack sits between you and Safaricom.
Your own paybill branding. When a customer pays through Paystack, the STK push shows a Paystack-related shortcode. When they pay through Daraja, it shows your own paybill number and business name. For some industries (banking, insurance, government services), having your own business name on the M-Pesa prompt matters for trust and brand recognition.
B2C and B2B APIs. Daraja gives you direct access to Business-to-Customer (send money to a customer's M-Pesa), Business-to-Business (pay another business), Account Balance, and Transaction Status APIs. Paystack offers transfers, but through a different API with its own abstractions. If you need granular control over these operations, Daraja gives you the raw tools.
UX control. With Daraja, you control exactly when the STK push fires, what the TransactionDesc says, what the AccountReference is, and how you handle timeouts. With Paystack, you work within their abstraction layer. For most products, the abstraction is fine. For some, it is not.
What You Lose
This section is just as important as the previous one. Be honest with yourself about the trade-offs.
Card payments. Daraja does not process Visa, Mastercard, or any card payments. If you accept cards today through Paystack, you need a different plan for cards after migration. Your options:
- Keep Paystack for cards only and use Daraja for M-Pesa. This is the dual-provider architecture described in Running Paystack and Daraja Side by Side.
- Add a different card provider: IntaSend, PesaPal, or Flutterwave.
- Drop card payments entirely. If cards are less than 5% of your revenue and M-Pesa covers the rest, some teams make this trade-off. Not ideal, but honest.
Pesalink and bank transfers. Gone. Daraja does not handle bank-to-bank payments. If you have customers who pay via Pesalink, you need another provider for those.
Airtel Money. Daraja is Safaricom's API. It does not process Airtel Money. You need Airtel's own API or another provider that supports it.
Apple Pay. Only available through payment gateways. No Daraja equivalent.
The Paystack dashboard. Paystack gives you transaction search, settlement reports, customer management, and webhook delivery logs. With Daraja, your dashboard is the Safaricom portal (limited), your own M-Pesa statement downloads, and whatever admin tools you build yourself.
Webhook signature verification. Paystack webhooks include an HMAC-SHA512 signature that you verify against your secret key. This is a robust security mechanism. Daraja callbacks do not have a signature. You secure them through IP whitelisting (if Safaricom publishes their callback IPs), or by validating the CheckoutRequestID against your database. It is less clean.
Simpler auth. Paystack uses a secret key in the header. Daraja requires OAuth token generation (consumer key + consumer secret), token refresh (tokens expire), and passkey-based password generation for STK Push. More moving parts.
Multi-country support. If you ever plan to expand beyond Kenya, Daraja will not help you. Each country has its own mobile money API. Paystack gives you one API across multiple markets.
Getting Daraja Credentials
If you are migrating to Daraja, you need these credentials. Getting them takes time, so start the process well before you plan to migrate.
Step 1: Register on the Safaricom Developer Portal.
Go to developer.safaricom.co.ke and create an account. This gives you access to the sandbox environment and documentation.
Step 2: Create a sandbox app.
In the portal, create an app. You get sandbox Consumer Key and Consumer Secret. Use these to build and test your integration before going live.
Step 3: Apply for a paybill or till number.
If you do not already have one, apply through Safaricom Business. A paybill number is used for payments with an account reference (useful for matching payments to customers). A till number is used for simple payments without a reference (common in retail). The application process involves business registration documents and takes days to weeks.
Step 4: Go live.
Once you have a paybill or till, request live API access through the Safaricom portal. You submit your app for review. Safaricom verifies your callback URLs, tests your integration, and approves you. This process is not instant. Budget at least a week, sometimes longer.
Step 5: Get your live credentials.
After approval, you receive:
- Consumer Key and Consumer Secret for OAuth token generation.
- Passkey for your shortcode, used to generate the STK Push password.
- Shortcode (your paybill or till number).
Store these securely. They are the keys to your M-Pesa integration. Treat them like you treat your Paystack secret key.
For the full Daraja setup process, see the M-Pesa integration hub.
Replacing Paystack Charges with Daraja STK Push
Here is what your Paystack M-Pesa charge code looks like today, and what the Daraja replacement looks like.
Before: Paystack Charge API
async function chargeMpesaPaystack({ phone, amount, email, reference }) {
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, // Already in cents
currency: 'KES',
reference,
mobile_money: { phone, provider: 'mpesa' },
}),
});
return res.json();
}
After: Daraja STK Push
async function getDarajaToken() {
const credentials = Buffer.from(
`${process.env.DARAJA_CONSUMER_KEY}:${process.env.DARAJA_CONSUMER_SECRET}`
).toString('base64');
const res = await fetch(
'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
{ headers: { Authorization: `Basic ${credentials}` } }
);
const data = await res.json();
return data.access_token;
}
async function chargeMpesaDaraja({ phone, amount, reference }) {
const token = await getDarajaToken();
const timestamp = new Date()
.toISOString()
.replace(/[-T:.Z]/g, '')
.slice(0, 14);
const password = Buffer.from(
`${process.env.DARAJA_SHORTCODE}${process.env.DARAJA_PASSKEY}${timestamp}`
).toString('base64');
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: Math.round(amount / 100), // Convert cents to 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();
}
Immediate things that hit you:
- OAuth token management. Every Daraja API call requires a bearer token. Tokens expire. You need to either request a fresh token for each call (simple but slower) or cache the token and refresh before expiry (faster but more code).
- Password generation. The STK Push password is a base64-encoded combination of your shortcode, passkey, and timestamp. Get any part wrong and the request fails with a cryptic error.
- Amount conversion. If your database stores amounts in cents (common for Paystack-first products), you need to divide by 100 for Daraja. Daraja works in whole shillings. An off-by-100x bug here means charging a customer KES 150,000 instead of KES 1,500.
- Callback URL in every request. Unlike Paystack where you set the webhook URL once in the dashboard, Daraja requires the CallBackURL in each STK Push request. This is actually useful if you want different callbacks for different payment types, but it is one more thing to configure.
Replacing Paystack Webhooks with Daraja Callbacks
Paystack sends a charge.success event to your webhook URL with an HMAC signature for verification. Daraja sends a completely different JSON structure to your callback URL with no signature.
Before: Paystack webhook handler
app.post('/webhooks/paystack', express.json(), (req, res) => {
const hash = crypto
.createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
if (hash !== req.headers['x-paystack-signature']) {
return res.status(401).send('Unauthorized');
}
if (req.body.event === 'charge.success') {
const { reference, amount } = req.body.data;
confirmPayment(reference, amount);
}
res.status(200).send('OK');
});
After: Daraja STK callback handler
app.post('/webhooks/daraja/stk', express.json(), (req, res) => {
const callback = req.body?.Body?.stkCallback;
if (!callback) {
return res.status(400).json({ ResultCode: 1, ResultDesc: 'Bad request' });
}
const { CheckoutRequestID, ResultCode, ResultDesc } = callback;
if (ResultCode === 0) {
// Success
const items = callback.CallbackMetadata?.Item || [];
const amount = items.find(i => i.Name === 'Amount')?.Value;
const receipt = items.find(i => i.Name === 'MpesaReceiptNumber')?.Value;
const phone = items.find(i => i.Name === 'PhoneNumber')?.Value;
confirmDarajaPayment(CheckoutRequestID, {
amount: amount * 100, // Convert back to cents for your database
receipt,
phone,
});
} else {
// Failed: customer cancelled, insufficient funds, timeout, etc.
failDarajaPayment(CheckoutRequestID, ResultCode, ResultDesc);
}
// Daraja expects this exact response format
res.json({ ResultCode: 0, ResultDesc: 'Accepted' });
});
The big differences:
- No signature verification. Daraja does not sign its callbacks. You cannot cryptographically verify that the callback came from Safaricom. Mitigations: validate the CheckoutRequestID against your database (reject any ID you did not initiate), and consider IP whitelisting if Safaricom publishes their callback source IPs.
- Different response format. Paystack wants HTTP 200 with any body. Daraja wants a JSON response with
ResultCode: 0. If you return the wrong format, Daraja may retry the callback or log an error. - Nested structure. The payment data is buried in
Body.stkCallback.CallbackMetadata.Item, an array of key-value pairs. It is not a clean flat object like Paystack's webhook payload. You have to fish out each field by name. - Amount is in shillings. The callback returns the amount in whole shillings. If your database stores cents, convert on the way in.
Handling Cards Without Paystack
If you currently accept card payments through Paystack and you are migrating M-Pesa to Daraja, you still need a solution for cards. Here are your realistic options.
Option 1: Keep Paystack for cards only.
This is the simplest approach. You do not need to find a new card provider. You already have the integration. Just stop routing M-Pesa through Paystack and keep routing cards. Your checkout offers "Pay with M-Pesa" (goes to Daraja) and "Pay with Card" (goes to Paystack). This is the dual-provider setup from Running Paystack and Daraja Side by Side.
The downside: you are still paying Paystack fees on card transactions, and you still maintain the Paystack integration. But card volume is usually much lower than M-Pesa in Kenya, so the fee impact is small.
Option 2: Switch to another card provider.
If you want to fully remove Paystack, you need another provider that handles card payments in Kenya. Options include IntaSend, PesaPal, and Flutterwave. Each has different API designs, fee structures, and onboarding requirements. See Choosing Between Paystack, IntaSend, PesaPal, and Flutterwave for a comparison.
Option 3: Drop cards.
If card transactions are a tiny fraction of your revenue and the integration maintenance is not worth it, you can remove card payments entirely. This is a business decision. For some products (mass-market consumer apps in Kenya), M-Pesa covers 95%+ of payments and cards are not worth the complexity. For others (products with international or corporate customers), dropping cards is not an option.
Most teams migrating from Paystack to Daraja choose Option 1. It preserves card support with minimal additional work.
Migration Steps
Here is the step-by-step plan. Timing: budget 4 to 8 weeks depending on how quickly you get Daraja go-live approval.
- Get Daraja credentials. Register, create an app, get sandbox credentials, apply for live access. Start this first because it takes the longest.
- Build the Daraja charge function in your codebase. Test against the sandbox. Verify the STK push works, the callback arrives, and you can parse the response.
- Build the Daraja callback handler. Set up a new endpoint (
/webhooks/daraja/stk). Test it with sandbox callbacks. - Add a provider column to your payments table if you do not have one.
- Build the payment router. A function that checks the payment method and routes M-Pesa to Daraja and cards to Paystack (or whatever you decided for cards).
- Test end to end with live Daraja credentials on small amounts. Verify the full flow: STK push fires, customer enters PIN, callback arrives, database updates, receipt sends.
- Deploy with a traffic split. Route 10% of M-Pesa to Daraja, 90% to Paystack. Monitor for a few days.
- Gradually increase. 25%, 50%, 75%, 100%. At each stage, verify success rates, callback delivery, and reconciliation.
- At 100% Daraja for M-Pesa, keep Paystack active for cards (if using Option 1) and keep your Paystack webhook handler running for any in-flight charges.
- After 30 days, you can safely remove the Paystack M-Pesa code path if no more Paystack M-Pesa webhooks are arriving.
The Added Complexity: An Honest Assessment
Going from Paystack to Daraja is going from a managed service to a self-managed integration. Here is what that means in practice.
Token management. Daraja OAuth tokens expire (typically after one hour). You need to either cache the token and refresh it before expiry, or request a new token for every API call. Paystack does not have this problem. Its secret key never expires.
Callback reliability. Paystack retries webhook deliveries and logs every attempt in the dashboard. Daraja's callback retry behavior is less predictable. If your server is briefly unreachable, you may miss a callback entirely. You need a polling mechanism (Transaction Status Query API) to catch missed callbacks.
Reconciliation. Paystack gives you settlement reports that match transactions to settlements. With Daraja, you download M-Pesa statements from the Safaricom portal and match them against your database manually or with custom code. This is more work.
Error handling. Daraja error responses are less descriptive than Paystack's. You will see cryptic error codes and messages that require cross-referencing Safaricom documentation. Build a mapping of common error codes to human-readable messages.
Testing environment. Paystack's test mode uses test keys and processes fake transactions cleanly. Daraja's sandbox works, but it has its quirks: different response formats than production, callbacks that sometimes do not fire, and test credentials that occasionally stop working. Test thoroughly, but expect the sandbox to surprise you.
Security. Without HMAC signature verification on callbacks, you need alternative security measures. IP whitelisting, CheckoutRequestID validation against your database, and HTTPS are your tools. None of them are as clean as Paystack's signature verification, but together they provide reasonable security.
None of this is insurmountable. Thousands of Kenyan businesses run on Daraja. But if you are coming from Paystack, the operational overhead increase is real. Make sure the benefits (cost savings, C2B validation, branding) justify the complexity for your specific situation.
Testing Strategy and Rollback
Testing before migration:
- Test STK Push in the Daraja sandbox with multiple phone numbers.
- Test callback handling for success, failure (insufficient funds), and timeout scenarios.
- Test with live credentials on small amounts (KES 1 or KES 10) before routing real customer traffic.
- Verify that your amount conversion (cents to shillings) is correct. Off-by-100x is catastrophic.
- Verify that your callback URL is reachable from Safaricom's servers. Some hosting providers block incoming requests from certain IP ranges.
Testing during migration (parallel period):
- Compare success rates between Paystack and Daraja for the same traffic profile.
- Verify that reconciliation works across both providers.
- Have your support team monitor for customer complaints related to payment issues.
Rollback plan:
During the parallel period, rollback is trivial: set DARAJA_PERCENTAGE to 0 and all traffic goes back to Paystack. After full cutover, rollback means switching the router back to Paystack for M-Pesa. Keep your Paystack credentials active. Keep your Paystack webhook handler deployed. The cost of maintaining a dormant Paystack integration is near zero.
If you discover post-migration that the complexity is not worth the benefit, that is a legitimate outcome. Roll back to Paystack, accept the higher per-transaction cost, and spend your engineering time on something else. There is no shame in trying a migration, learning from it, and deciding the old approach was better for your situation.
Key Takeaways
- ✓The main reasons to migrate from Paystack to Daraja: lower per-transaction costs at high volume, C2B validation (approve or reject each payment), your own paybill branding, and direct access to B2C and B2B APIs.
- ✓What you lose: card payments (you need a separate provider), Pesalink, Airtel Money, Apple Pay, the Paystack dashboard, built-in reconciliation, webhook signature verification, and multi-country support.
- ✓Daraja requires more infrastructure: OAuth token management, passkey-based password generation, callback URL setup, and manual reconciliation against M-Pesa statements.
- ✓You cannot migrate cards to Daraja. If you need to keep accepting cards, you need a second provider (IntaSend, PesaPal, Flutterwave, or keep Paystack just for cards).
- ✓C2B validation is the feature that most often drives this migration. It lets you approve or reject each M-Pesa payment before it completes, which is critical for products that need to verify customer identity or validate payment amounts before accepting money.
- ✓The honest assessment: going from Paystack to Daraja is trading simplicity for control. Every feature that Paystack handled for you now becomes your responsibility.
Frequently Asked Questions
- How much can I save by going from Paystack to Daraja?
- The savings depend on your transaction volume and the fee structures of both providers, which change over time. Check Paystack's current pricing at paystack.com/pricing and Safaricom's Daraja fee schedule. Calculate the difference per transaction and multiply by your monthly volume. For low-volume products, the savings may not justify the added complexity.
- Can I migrate back to Paystack if Daraja does not work out?
- Yes. The reverse migration (Daraja to Paystack) is covered in the Migrating from Direct Daraja to Paystack article. If you kept your Paystack account active, switching back is straightforward. This is why we recommend keeping Paystack credentials alive during and after the migration.
- Do my customers need to do anything during the migration?
- No. The customer experience barely changes. They still get an STK push on their phone and enter their M-Pesa PIN. The only visible difference is the business name or paybill number shown on the push notification. With Paystack, they see a Paystack shortcode. With Daraja, they see your own paybill. Most customers do not notice or care.
- What about Paystack Transfers? How do I replace them?
- If you use Paystack Transfers to send money to M-Pesa wallets (B2C), you can replace them with Daraja's B2C API. For bank transfers, Daraja does not help. You would need to use your bank's API, manual bank transfers, or keep Paystack Transfers just for bank payouts. Some teams keep Paystack active only for bank payouts and use Daraja for everything M-Pesa related.
- Is C2B validation really worth migrating for?
- It depends on your product. If you need to validate payment amounts before accepting them (preventing overpayments or underpayments), or if you need to verify the payer before completing the transaction, C2B validation is powerful and Paystack does not offer it. For standard e-commerce or SaaS payments where you initiate the charge and know the amount, C2B validation is not needed. If you are building a school fees portal, utility payment system, or any product where customers pay arbitrary amounts to an account reference, C2B validation is worth the migration.
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