Bonaventure OgetoBy Bonaventure Ogeto|

M-Pesa Daraja API Tutorial for Absolute Beginners

The Daraja API is Safaricom's gateway for integrating M-Pesa payments into your applications. To get started, register at developer.safaricom.co.ke, create an app to get your consumer key and secret, generate an OAuth token, then make your first API call to the sandbox. The whole process takes about an hour if you follow the steps carefully.

What Is the Daraja API, Actually?

"Daraja" means "bridge" in Swahili, and that is exactly what it does. It is Safaricom's official API that lets your application talk to the M-Pesa platform. Before Daraja existed (it launched in 2018), integrating M-Pesa was a painful process involving manual paybill setups, SMS parsing, and a lot of back-and-forth with Safaricom's enterprise team. Daraja replaced all of that with a standard REST API that any developer can use.

Here is what you can do with the Daraja API:

  • STK Push (Lipa Na M-Pesa Online): Trigger a payment prompt on a customer's phone. This is the most common integration. When you buy something on an app and get that M-Pesa PIN prompt, that is STK Push.
  • C2B (Customer to Business): Receive payments from customers to your paybill or till number and get notified via callback.
  • B2C (Business to Customer): Send money from your business account to a customer's M-Pesa. Used for refunds, salary payments, or payouts.
  • Transaction Status: Check whether a specific transaction went through, failed, or is still pending.
  • Account Balance: Query your M-Pesa business account balance programmatically.

If you have ever made an HTTP request to any API (GitHub, weather APIs, anything), you already understand the core pattern. Daraja uses standard HTTP methods, JSON payloads, and OAuth 2.0 authentication. The M-Pesa-specific parts are mostly about understanding what each endpoint expects and how callbacks work. That is what this tutorial walks you through.

Step 1: Register on the Safaricom Developer Portal

Everything starts at the Safaricom developer portal. This is where you create apps, get credentials, and access the sandbox for testing.

  1. Go to developer.safaricom.co.ke
  2. Click "Sign Up" in the top right corner
  3. Fill in your details: name, email, and a password. Use an email you check regularly because Safaricom sends verification and notification emails here
  4. Verify your email by clicking the link Safaricom sends you
  5. Log in to the portal

Once you are logged in, you will see a dashboard with options for "My Apps," documentation links, and API references. The portal interface can be a bit clunky (it has improved over the years, but it is still not the most polished developer experience), so do not be surprised if pages load slowly or the layout feels dated. What matters is the API itself, which works reliably.

One thing to note: your developer portal account is separate from your personal M-Pesa account. Having a developer account does not give you a business shortcode or the ability to receive real payments. For the sandbox (which is where you will spend all your time while learning), you do not need any of that. Safaricom provides test shortcodes and credentials that simulate the entire payment flow without touching real money.

If the portal is temporarily down (it happens occasionally), just wait an hour and try again. Do not try to find workarounds or alternative portals. The official Safaricom developer portal is the only legitimate source for Daraja credentials.

Step 2: Create an App and Get Your Credentials

With your account set up, you need to create an "app" on the portal. This is not a real application you upload somewhere. It is just a container that holds your API credentials.

  1. Click "My Apps" in the portal navigation
  2. Click "Add a New App"
  3. Give your app a name. Something like "My First Daraja App" works fine. The name is just for your own reference
  4. Under "Select APIs," check the APIs you want to use. For now, select "Lipa Na M-Pesa Sandbox" at minimum. You can also check the others if you want to experiment later
  5. Click "Create App"

After creating the app, you will see two critical values on the app's detail page:

  • Consumer Key: A long alphanumeric string. Think of it as your username for the API.
  • Consumer Secret: Another long string. This is your password. Do not share it, do not commit it to GitHub, do not post it on Stack Overflow.

Copy both values and store them somewhere safe. A .env file in your project is the standard approach:

DARAJA_CONSUMER_KEY=your_consumer_key_here
DARAJA_CONSUMER_SECRET=your_consumer_secret_here
DARAJA_ENV=sandbox

For the sandbox environment, Safaricom also provides test credentials that you will need for specific API calls. These include a test shortcode (usually 174379), a passkey, and test phone numbers. You can find these in the API documentation section of the portal, or in our dedicated sandbox setup guide.

A common mistake at this stage: creating multiple apps for the same project. You only need one app. Each app gets one consumer key and secret pair, and that pair works for all the Daraja APIs you selected when creating the app.

Step 3: Generate Your OAuth Access Token

Every Daraja API call requires an access token. You cannot skip this step. The token proves to Safaricom's servers that you are authorized to make requests. Here is how the flow works:

  1. You send your consumer key and consumer secret to the OAuth endpoint
  2. Safaricom validates them and returns an access token
  3. You include that token in the header of every subsequent API call
  4. The token expires after 3600 seconds (one hour), then you need a new one

The OAuth endpoint for the sandbox is:

https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials

You authenticate using HTTP Basic Auth, where the username is your consumer key and the password is your consumer secret. In practice, this means Base64-encoding the string "consumerKey:consumerSecret" and sending it in the Authorization header.

Here is how to do it in Node.js:

async function getAccessToken() {
  const consumerKey = process.env.DARAJA_CONSUMER_KEY;
  const consumerSecret = process.env.DARAJA_CONSUMER_SECRET;

  const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString('base64');

  const response = await fetch(
    'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
    {
      method: 'GET',
      headers: {
        Authorization: `Basic ${auth}`,
      },
    }
  );

  const data = await response.json();
  console.log('Access Token:', data.access_token);
  console.log('Expires in:', data.expires_in, 'seconds');

  return data.access_token;
}

If this works, you will get back a JSON response like this:

{
  "access_token": "cXl6MTIzNDU2Nzg5...",
  "expires_in": "3600"
}

If you get a 400 or 401 error instead, double-check your consumer key and secret. The most common problem is extra whitespace when copying from the portal. Trim your credentials and try again.

In a production application, you would cache this token and refresh it before it expires rather than generating a new one for every request. But while learning, generating a fresh token each time is perfectly fine.

Step 4: Make Your First Real API Call

Now that you have an access token, let us make an actual API call. The simplest one to start with is an STK Push request, which triggers the M-Pesa payment prompt on a phone. In the sandbox, this will not charge any real money. It simulates the entire flow.

Here is a complete STK Push request in Node.js:

async function initiateSTKPush(accessToken) {
  const shortcode = '174379'; // Safaricom sandbox shortcode
  const passkey = 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919';
  const timestamp = new Date()
    .toISOString()
    .replace(/[-T:.Z]/g, '')
    .slice(0, 14);
  const password = Buffer.from(`${shortcode}${passkey}${timestamp}`).toString('base64');

  const payload = {
    BusinessShortCode: shortcode,
    Password: password,
    Timestamp: timestamp,
    TransactionType: 'CustomerPayBillOnline',
    Amount: 1,
    PartyA: '254708374149', // Test phone number
    PartyB: shortcode,
    PhoneNumber: '254708374149',
    CallBackURL: 'https://your-callback-url.com/callback',
    AccountReference: 'Test001',
    TransactionDesc: 'Test payment',
  };

  const response = await fetch(
    'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    }
  );

  const data = await response.json();
  console.log('STK Push Response:', data);
  return data;
}

A successful response looks like this:

{
  "MerchantRequestID": "29115-34620561-1",
  "CheckoutRequestID": "ws_CO_191220191020363925",
  "ResponseCode": "0",
  "ResponseDescription": "Success. Request accepted for processing",
  "CustomerMessage": "Success. Request accepted for processing"
}

A ResponseCode of "0" means the request was accepted. It does not mean the payment is complete. The actual payment result comes through the callback URL, which we will cover in detail in our STK Push deep dive.

If you get an error, check the following: Is your access token fresh (less than one hour old)? Is the timestamp format correct (YYYYMMDDHHmmss)? Is the password correctly Base64-encoded from shortcode + passkey + timestamp? These three things account for the vast majority of first-attempt failures.

Understanding What Just Happened

Let us break down what actually happened when you made that STK Push call, because understanding the flow is more important than memorizing the code:

  1. Your server sent the request to Safaricom. The payload included the amount, the customer's phone number, and a callback URL where you want to receive the result.
  2. Safaricom validated the request. It checked your access token, verified the shortcode and password, and confirmed the phone number format.
  3. Safaricom responded immediately with a "request accepted" message. This is important: the response does NOT mean payment is complete. It only means Safaricom received your request and will process it.
  4. Safaricom sent a push notification to the customer's phone. In production, the customer sees a prompt asking them to enter their M-Pesa PIN. In the sandbox, this step is simulated.
  5. After the customer enters their PIN (or the request times out), Safaricom sends the result to your callback URL. This is a POST request from Safaricom's servers to your server, containing the transaction details.

This is an asynchronous flow. Your initial API call is basically saying "hey Safaricom, please ask this person to pay." The actual result comes later, through a separate channel (the callback). This trips up a lot of beginners who expect the payment result in the response to their initial request.

The callback URL is a real URL on the internet that Safaricom's servers can reach. During development, your localhost is not accessible from the internet, so you need a tool like ngrok to create a tunnel. We cover this in detail in the sandbox setup guide.

One more thing worth knowing: the sandbox does not always behave identically to production. Response times are different, some edge cases are not perfectly simulated, and the test phone numbers do not actually receive push notifications. The sandbox is good enough for learning the API structure and testing your code logic, but you should expect some differences when you move to production.

Common Beginner Mistakes (and How to Avoid Them)

After teaching M-Pesa integration to hundreds of developers at McTaba Labs, these are the mistakes we see over and over again from people working with Daraja for the first time:

1. Hardcoding credentials in your source code. Never put your consumer key, secret, or passkey directly in your JavaScript files. Use environment variables (.env files with the dotenv package). If you accidentally push credentials to a public GitHub repository, anyone can use them. Safaricom can also disable your app if they detect leaked credentials.

2. Using the wrong base URL. The sandbox URL is https://sandbox.safaricom.co.ke. The production URL is https://api.safaricom.co.ke. Mixing these up means your requests will fail with confusing errors. Store the base URL in an environment variable alongside your credentials so you can switch between sandbox and production cleanly.

3. Malformed timestamp or password. The timestamp must be exactly in the format YYYYMMDDHHmmss (for example, 20260719143022). The password is Base64(shortcode + passkey + timestamp). If any of these three components are wrong, the request fails. A common bug is using the wrong time zone. Use the current time in your server's timezone, or better yet, use UTC and be consistent.

4. Ignoring the callback entirely. The STK Push response only tells you the request was accepted. The actual payment result (success or failure) comes to your callback URL. If you do not set up a callback handler, you will never know whether the customer actually paid. This is probably the single biggest gap we see in beginner implementations.

5. Not handling token expiry. Your OAuth token expires after one hour. If your app runs longer than that without refreshing the token, all API calls will start failing with 401 errors. Build token refresh logic early, even in your first prototype.

6. Testing with real phone numbers in sandbox. The sandbox has specific test phone numbers (like 254708374149). Using your real phone number in the sandbox will not work because the sandbox does not actually send push notifications to real phones. Save real-number testing for when you move to production with live credentials.

If you want to build real confidence with Daraja before deploying to production, our M-Pesa Integration for Developers course (KES 9,999) walks you through every integration type with production-ready patterns, proper error handling, and callback architectures that scale.

Key Takeaways

  • Daraja is just a REST API. If you can make HTTP requests with fetch or axios, you already have the core skill you need.
  • You must register on the Safaricom developer portal and create an app before you can make any API calls. This gives you a consumer key and consumer secret.
  • Every Daraja API call requires an OAuth access token. You get this token by sending your consumer key and secret to the auth endpoint. Tokens expire after one hour.
  • Always start in the sandbox environment. It uses test credentials and fake money, so you cannot accidentally charge a real M-Pesa account while learning.
  • The most common beginner mistake is mixing up sandbox and production URLs or credentials. Keep them in separate environment variables from the start.

Frequently Asked Questions

Do I need a business to use the Daraja API?
No. Anyone can register on the Safaricom developer portal and use the sandbox for free. You only need a registered business (with a paybill or till number) when you want to go live and accept real payments. The sandbox is completely free and requires nothing beyond a developer account.
Is the Daraja API free to use?
The API itself is free. You do not pay Safaricom for API calls. However, when you process real transactions in production, the standard M-Pesa transaction fees apply. These are the same fees any M-Pesa business account pays. In the sandbox, everything is free because no real money moves.
What programming languages can I use with the Daraja API?
Any language that can make HTTP requests works with Daraja. It is a REST API, so JavaScript/Node.js, Python, PHP, Java, Go, Ruby, and C# all work. The examples in this tutorial use Node.js because it is the most common choice among Kenyan developers we work with, but the concepts translate directly to any language.
How long does it take to get Daraja API credentials?
Sandbox credentials are instant. You register, create an app, and your consumer key and secret are available immediately. Production credentials (going live) require a business registration, Safaricom review, and can take anywhere from a few days to a few weeks depending on Safaricom processing times.
Why does my OAuth token request return a 400 error?
The most common causes are: extra whitespace in your consumer key or secret (copy them carefully from the portal), using the wrong URL (make sure you are hitting the sandbox URL, not production), or an incorrectly formatted Authorization header. The header must be "Basic " followed by Base64-encoded "consumerKey:consumerSecret" with the colon between them.
Can I test M-Pesa payments without a real phone?
Yes. The sandbox simulates the entire payment flow without sending real push notifications to any phone. You send requests to the sandbox API, and it returns simulated responses. Your callback URL receives simulated payment results. No real phone, no real money, and no real M-Pesa account required for testing.

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