Bonaventure OgetoBy Bonaventure Ogeto|

M-Pesa Through Paystack vs Direct Daraja STK Push

Paystack M-Pesa uses a checkout popup or redirect where the customer enters their phone number, then receives an STK push from Paystack's shortcode. Direct Daraja triggers the STK push immediately from your own paybill with no redirect. The Daraja path has fewer steps and keeps the customer on your site, but Paystack gives you multi-method checkout and managed error handling out of the box.

Two Paths to the Same STK Push

Both Paystack and Daraja end at the same place: a green M-Pesa popup on the customer's phone asking them to enter their PIN. The difference is everything that happens before and after that popup.

With direct Daraja, the flow is short:

  1. Customer clicks "Pay" on your checkout page.
  2. Your backend sends a Lipa na M-Pesa Online (STK push) request to Safaricom's API.
  3. Safaricom pushes the payment prompt to the customer's phone within seconds.
  4. Customer enters their M-Pesa PIN.
  5. Safaricom sends a callback to your server with the result.
  6. Your server updates the order and shows a confirmation to the customer.

The customer never leaves your page. There is no redirect, no popup from another domain, no extra form to fill out. You already have their phone number (they entered it during checkout or registration), and the STK push fires from your code the moment they click "Pay."

With Paystack Checkout (the standard flow), the path has more steps:

  1. Customer clicks "Pay" on your checkout page.
  2. Your backend calls Paystack's Initialize Transaction endpoint. Paystack returns a checkout URL.
  3. The customer is redirected to the Paystack checkout page (or it opens in a popup via Inline JS).
  4. On the Paystack checkout page, the customer selects "Mobile Money" or "M-Pesa" as their payment method.
  5. The customer enters their phone number on the Paystack page.
  6. Paystack sends the STK push to their phone.
  7. Customer enters their M-Pesa PIN.
  8. Paystack sends a webhook to your server and redirects the customer back to your site.

That is three extra steps: the redirect, the payment method selection, and the phone number entry. Each extra step is a chance for the customer to drop off.

The Redirect vs Inline Experience

Paystack gives you two ways to present the checkout:

Redirect checkout: The customer leaves your site entirely and lands on a checkout.paystack.com page. After payment, they are redirected back to your callback URL. This is the simplest to implement (just redirect to the URL Paystack gives you), but it is also the most disruptive to the customer experience. The customer sees a different domain, a different design, and has to trust that they are still in the right place.

Inline checkout (Paystack Inline JS): A modal pops up over your page. The customer stays on your site, and the Paystack checkout appears as an overlay. This is better for conversion because the customer does not feel like they have left your store. But it is still a Paystack-branded modal, and the customer still needs to select a payment method and enter their phone number.

With direct Daraja, there is no redirect and no modal. The payment happens in the background. You can show whatever UI you want while the customer waits for the STK push. A custom loading spinner, a countdown timer, a "check your phone" message with your own branding. You control every pixel.

There is a third Paystack option that gets closer to the Daraja experience: the Charge API. With the Charge API, you collect the phone number in your own form and send it directly to Paystack's API. No redirect, no popup. The STK push fires from Paystack's end, and you handle the "waiting" UI yourself. This is the best option if you want to use Paystack but need a seamless checkout experience. The trade-off is that the STK push still shows Paystack's shortcode on the customer's phone, not your paybill.

What the Customer Sees on Their Phone

This is where customers get confused, and where business owners sometimes have strong feelings.

Direct Daraja STK push: The M-Pesa popup on the customer's phone shows your business name and your paybill number. If your paybill is 123456 and your business name is "Kwetu Store," the customer sees:

Pay KES 1,500.00 to
Kwetu Store
Paybill: 123456
Account: ORD-20260720-001

Enter M-PESA PIN

This is familiar. The customer knows they are paying your business. If they have paid your paybill before, they recognize it.

Paystack STK push: The M-Pesa popup shows Paystack's aggregator shortcode and Paystack's business name (or a variation registered with Safaricom). The customer sees something like:

Pay KES 1,500.00 to
Paystack Payments
Paybill: XXXXXX

Enter M-PESA PIN

For customers who have used Paystack before, this is fine. For customers who have never heard of Paystack, this can cause a moment of hesitation. "I was buying from Kwetu Store. Why does it say Paystack?" That hesitation can cause a drop-off, especially for first-time online shoppers or less tech-savvy customers.

How much this matters depends on your customer base. If your customers are tech-savvy urban professionals who have used multiple online payment platforms, the Paystack branding is not a problem. If your customers are in smaller towns, less familiar with online payments, and trust your brand specifically, the unfamiliar name on the STK push could cause them to cancel the payment.

There is no technical fix for this. It is a consequence of using an aggregator model. Paystack collects the payment on their paybill and settles it to you later. Daraja lets the payment go to your paybill directly.

Timeout Handling

M-Pesa STK push has a timeout. If the customer does not enter their PIN within a certain window (typically around 30 to 60 seconds, depending on Safaricom's current configuration), the request expires. How you handle that timeout differs between the two approaches.

Direct Daraja timeout handling:

You have full control. After sending the STK push request, you can:

  • Poll Safaricom's Transaction Status API to check if the payment completed.
  • Show a countdown timer on your page.
  • Offer a "Resend" button if the timeout expires.
  • Fall back to a manual paybill payment flow if the STK push keeps failing.

Your callback URL receives the result from Safaricom, and you decide what to show the customer based on the result code. If the result code indicates a timeout, you can retry immediately or prompt the customer to try again.

Paystack timeout handling:

Paystack manages the timeout internally. When you use Paystack Checkout (redirect or inline), the checkout page shows a "waiting for payment" state and eventually times out. The customer sees a message on the Paystack page. With the Charge API, Paystack returns a pay_offline status and you need to wait for the webhook. If the payment times out, you receive a webhook indicating the failure, or you can poll the Verify Transaction endpoint.

The difference is control. With Daraja, you decide the timeout duration, the polling interval, the retry logic, and the customer-facing messages. With Paystack, you accept Paystack's timeout behavior and respond to the webhook events they send you.

For a deep dive into timeout patterns across different providers, see M-Pesa STK Push Timeout Handling Compared Across Providers.

Error Messages and Failure Modes

When a payment fails, the quality of the error information determines how quickly you can debug it and how clearly you can communicate the problem to the customer.

Daraja error codes: Safaricom returns specific result codes in the callback. These codes tell you exactly what went wrong:

  • 1032: Request cancelled by user (they tapped "Cancel" on the STK push).
  • 1037: Timeout. The customer did not respond in time.
  • 2001: Wrong PIN.
  • 1: Insufficient funds.
  • 1025: Invalid or inactive number.

With these codes, you can show precise messages: "You cancelled the payment. Would you like to try again?" or "The payment timed out. Tap Pay to get another prompt." This level of specificity improves the customer experience and your support team's ability to help.

Paystack error handling: Paystack abstracts Safaricom's result codes into its own status system. A failed M-Pesa payment comes back as a failed charge with a gateway_response message. These messages are cleaner and more user-friendly, but they lose some specificity. You might see "Transaction failed" where Daraja would have told you "Insufficient funds." Paystack's error documentation covers the possible responses, but you are working with Paystack's interpretation of the underlying failure, not the raw Safaricom code.

For debugging, the Daraja path gives you more information. For customer-facing messages, Paystack's abstractions are often good enough. The question is whether "good enough" is enough for your product. If you are building a payments product where understanding exactly why each transaction failed matters (analytics, fraud detection, automated retry logic), the raw Daraja codes are more useful.

Conversion Rate Implications

Every step in a payment flow is a potential drop-off. This is not theory. It is measurable.

The Daraja advantage: The customer stays on your page. They click "Pay," they see the STK push, they enter their PIN. Three steps, no context switching, no new domains, no payment method selection (because M-Pesa is the only option). If your entire customer base pays with M-Pesa, this is the shortest possible path to completed payment.

The Paystack overhead: Redirect checkout adds at least two extra steps (navigate to Paystack page, select payment method). Inline checkout reduces the redirect friction but still adds the payment method selection and phone number entry steps. The Charge API eliminates the redirect entirely but still requires you to build the waiting UI.

When does the extra redirect not matter?

  • Multi-method checkout. If you offer M-Pesa, cards, and Pesalink, the customer needs a method selection step regardless. Paystack's checkout page handles this well. Building your own multi-method payment page on top of Daraja plus a card processor is more work for a similar result.
  • High-trust brands. If your brand is well-known and the customer trusts the checkout process, the redirect to Paystack does not cause hesitation.
  • Repeat customers. A customer who has paid through Paystack before knows the flow. The drop-off risk is highest for first-time customers.

When does the redirect matter a lot?

  • Mobile users on slow connections. The redirect loads a new page. On 3G or spotty connections, this takes time. The customer might think the page is broken and leave. Daraja skips this entirely because there is no new page to load.
  • Low-tech customers. Customers who are not comfortable with online payments may not trust a redirect to an unfamiliar domain.
  • Impulse purchases. Speed matters. The faster you get from "I want to buy this" to "I have bought this," the higher the conversion. Every extra step erodes impulse.

For strategies on optimizing Kenyan checkout conversion, see Why Kenyan Checkout Conversion Depends on Mobile Money Placement.

Webhook and Callback Differences

After the customer enters their PIN, both systems need to tell your server what happened. The mechanisms are different.

Daraja callbacks: Safaricom sends a POST request to your callback URL with the transaction result. This happens once. If your server is down, you miss it. There is no built-in retry mechanism. You need to build your own polling logic using the Transaction Status API to catch missed callbacks. The callback payload includes the Safaricom receipt number, the result code, and the transaction details.

Paystack webhooks: Paystack sends a POST request to your webhook URL with the charge.success event (or charge.failed). Paystack retries failed webhook deliveries on a schedule. You verify the webhook using the x-paystack-signature header. As a backup, you can also call the Verify Transaction endpoint to check the payment status.

Paystack's webhook system is more robust out of the box. The signature verification is well-documented, the retry behavior is predictable, and the event payload is consistent across all payment methods. With Daraja, you need to build that reliability yourself: a callback endpoint that handles failures gracefully, a sweeper job that polls for missed callbacks, and validation logic for the callback payload.

If you are using Paystack's Charge API (no redirect), there is a middle ground. You submit the charge, receive a pay_offline status, and then either wait for the webhook or poll the Verify endpoint. This gives you more control over the user experience while still benefiting from Paystack's webhook infrastructure.

Code Comparison: Triggering the STK Push

Here is what the initiation looks like in code for each approach.

Direct Daraja STK Push (Node.js):

// Daraja: Lipa na M-Pesa Online (STK Push)
const timestamp = new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14);
const password = Buffer.from(
  shortcode + passkey + timestamp
).toString('base64');

const response = await fetch(
  'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      BusinessShortCode: shortcode,
      Password: password,
      Timestamp: timestamp,
      TransactionType: 'CustomerPayBillOnline',
      Amount: 1500,
      PartyA: '254712345678',
      PartyB: shortcode,
      PhoneNumber: '254712345678',
      CallBackURL: 'https://yoursite.com/api/mpesa/callback',
      AccountReference: 'ORD-20260720-001',
      TransactionDesc: 'Payment for order ORD-20260720-001',
    }),
  }
);

// Response tells you if the push was sent, not if payment succeeded
const data = await response.json();
// data.ResponseCode === '0' means the push was sent successfully

Paystack Charge API (Node.js):

// Paystack: Charge API for M-Pesa
const response = await fetch('https://api.paystack.co/charge', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk_live_YOUR_SECRET_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'customer@example.com',
    amount: 150000, // KES 1,500 in cents
    currency: 'KES',
    mobile_money: {
      phone: '0712345678',
      provider: 'mpesa',
    },
  }),
});

const data = await response.json();
// data.data.status === 'pay_offline' means the push was sent
// Wait for the charge.success webhook

Notice the differences:

  • Authentication: Daraja requires an OAuth token (you call the auth endpoint first, get a token, and include it in each request). Paystack uses a secret key directly in the Authorization header. Simpler setup.
  • Amount format: Daraja uses whole shillings (1500). Paystack uses cents (150000). Both trip developers up. See the hub article for details on amount handling.
  • Phone number format: Daraja requires the 2547XXXXXXXX format. Paystack accepts 07XXXXXXXX or international format.
  • Password generation: Daraja requires a Base64-encoded password from your shortcode, passkey, and timestamp. Paystack handles this internally.

The Paystack code is shorter and simpler. The Daraja code requires more setup but gives you direct access to Safaricom's parameters.

When the Difference Does Not Matter

For many products, the differences in this article are academic. Here is when you should not spend time agonizing over this choice:

  • You are building an MVP. Use Paystack. Ship faster. You can switch to Daraja later if the conversion data or cost data demands it.
  • M-Pesa is one of several payment methods. If you also need cards and Pesalink, Paystack already needs to be in the stack. Adding Daraja as a second integration just for M-Pesa adds complexity without a clear benefit at low volume.
  • Your customers are comfortable with online payments. The Paystack redirect or popup does not faze them. The conversion difference is marginal.
  • You are processing under 1,000 M-Pesa transactions per month. The fee difference is negligible. The developer time difference is not.

The choice matters when you are processing high volume, when M-Pesa is your dominant or only payment method, when your customers are sensitive to unfamiliar branding on the STK push, or when you need maximum control over the payment experience for competitive reasons.

For the broader decision framework covering cost, channels, and engineering resources beyond the STK push experience, see Paystack vs Daraja: When to Use a Gateway and When to Go Direct.

Key Takeaways

  • Direct Daraja STK Push triggers the M-Pesa prompt on the customer's phone immediately from your backend. No redirect, no popup, no intermediate page. The customer stays on your site throughout.
  • Paystack M-Pesa (via Checkout) adds at least one extra step: a redirect or popup where the customer selects M-Pesa and enters their phone number before the STK push fires.
  • The Paystack Charge API lets you trigger the STK push without a redirect, but the push still comes from Paystack's shortcode, not your paybill.
  • Timeout behavior differs. Direct Daraja gives you full control over how you poll and handle the timeout. Paystack handles the timeout internally and sends you a webhook with the final status.
  • Error messages are more transparent with Daraja. You see the exact Safaricom result code. Paystack abstracts these into its own status messages, which are cleaner but less specific.
  • For checkout conversion, fewer steps means fewer drop-offs. If M-Pesa is your primary payment method, the Daraja flow typically converts better because there is no redirect.
  • If you offer multiple payment methods (M-Pesa plus cards plus Pesalink), the Paystack checkout page handles method selection naturally. Building that yourself on top of Daraja is extra work.

Frequently Asked Questions

Does the customer see a different M-Pesa prompt when paying through Paystack versus Daraja?
Yes. With Daraja, the STK push shows your business name and your paybill number. With Paystack, the STK push shows Paystack's aggregator name and shortcode. The payment amount and the request to enter a PIN are the same in both cases.
Which approach has a faster STK push delivery time?
The difference is typically negligible. With Daraja, you call Safaricom directly. With Paystack, your request goes to Paystack, which then calls Safaricom. The extra network hop adds milliseconds, not seconds. The STK push delivery time is dominated by Safaricom's processing, which is the same in both cases.
Can I avoid the Paystack redirect entirely when accepting M-Pesa?
Yes. Use the Paystack Charge API instead of Initialize Transaction. With the Charge API, you collect the phone number in your own form and send it to Paystack's API directly. No redirect, no popup. The STK push fires from Paystack's end and you handle the waiting UI yourself. See our guide on accepting M-Pesa payments with the Paystack Charge API for the full implementation.
What happens if the customer phone is off or has no signal during the STK push?
With both Daraja and Paystack, the STK push will fail or time out if the customer's phone cannot receive it. The difference is in how you detect and handle this. With Daraja, you get the timeout in the callback and can retry immediately. With Paystack, you get a failed charge webhook or a failed status when you poll the verify endpoint. In both cases, you should prompt the customer to check their phone and try again.
Is the Paystack Charge API flow the same as Daraja STK Push from the customer perspective?
Almost. In both cases, the customer is on your page, clicks pay, and gets an STK push on their phone. The only visible difference is the business name on the M-Pesa prompt: your paybill name with Daraja, Paystack's name with the Charge API. The steps and timing are nearly identical.

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