Introduction
If you are building a web or mobile app that needs to receive payments from M-PESA customers in Kenya, the Safaricom Daraja API is the tool you need. This guide will walk you through everything from scratch — no prior API experience required.
By the end of this guide, you will understand:
- What the C2B API is and how it works
- How to set up and test it in the sandbox using Postman
- What ValidationURL and ConfirmationURL do
- How to take your integration live
What is C2B?
C2B stands for Customer to Business. It refers to the flow of money from an individual customer to your business. When a customer pays your Paybill or Till number via M-PESA, that is a C2B transaction.
The C2B API allows your application to receive real-time notifications every time a payment comes in, so you can automate things like:
- Marking an order as paid
- Sending a payment receipt to the customer
- Updating your accounting system automatically
What is Daraja?
Daraja (which means "bridge" in Swahili) is Safaricom's developer portal that gives you access to the M-PESA API. It provides a sandbox (testing) environment where you can simulate payments without using real money, and a production environment for real transactions.
Portal URL: https://developer.safaricom.co.ke
PART ONE: SANDBOX TESTING
Before using real money, Daraja gives you a sandbox environment to safely test your integration. This is where you should always start.
STEP 1 — Create a Daraja Account & App
Set up your developer profile
- Go to https://developer.safaricom.co.ke and click Sign Up
- Fill in your details and verify your email address
- Once logged in, click "My Apps" in the top navigation menu
- Click "Add a New App" — give it any name (e.g. "MyShopC2B")
- Under the products section, check "M-Pesa Sandbox" to enable sandbox APIs
- Click "Create App"
Your app will now appear in the "My Apps" section. Click on it and you will see two important values:
| Key | Description |
|---|---|
| Consumer Key | Acts like your app's username for the API |
| Consumer Secret | Acts like your app's password for the API |
💾 Save These! Copy your Consumer Key and Consumer Secret somewhere safe. You will need them for every API call.
STEP 2 — Get an Access Token
Authenticate before making any API call
Every single API request to Daraja requires an access token. Think of it like a temporary password that proves your identity. It expires after 1 hour and you need to generate a new one.
In Postman:
-
Method:
GET -
URL:
https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials
Go to the Authorization tab in Postman:
- Auth Type: Basic Auth
- Username: paste your Consumer Key
- Password: paste your Consumer Secret
Click Send. You should get a response like this:
{
"access_token": "SGWcJPtNtYNPGm1DqBNqZZZZZZ",
"expires_in": "3599"
}
📋 Copy It! Copy the
access_tokenvalue. You will paste it in the Authorization header of every request below.
STEP 3 — Understanding Your Callback URLs
The heart of how C2B notifications work
Before registering your URLs, it is important to understand what each one does. When a customer makes a payment, Safaricom contacts your server at two different points in the payment flow:
📱 Customer Pays → 🔍 ValidationURL → ✅ ConfirmationURL
(Sends money) ("Should I allow ("Payment done,
this?") record it!")
ValidationURL — "Should I allow this payment?"
This is called before the payment is processed. Safaricom sends you the payment details and waits for your response. You can accept or reject the payment based on your own business logic — for example, checking if the account number the customer entered actually exists in your system.
You respond with one of the following:
// Accept the payment
{ "ResultCode": "0", "ResultDesc": "Accepted" }
// Reject the payment
{ "ResultCode": "C2B00012", "ResultDesc": "Rejected" }
⚠️ Note: ValidationURL is optional. If you set
ResponseTypeto"Completed"when registering, Safaricom skips validation and auto-accepts all payments. This is recommended for beginners.
ConfirmationURL — "Payment went through, save the details"
This is called after the payment has been successfully processed. At this point, you cannot reject the payment — it has already gone through. This is where your app should record the transaction, update a database, send a receipt, or trigger any other business logic.
| Feature | ValidationURL | ConfirmationURL |
|---|---|---|
| When called | BEFORE payment is processed | AFTER payment is processed |
| Purpose | Accept or reject the payment | Record and act on the payment |
| Your response matters? | YES — can block the payment | NO — informational only |
| Required? | Optional | Yes, always required |
🏪 Real-world analogy: Think of it like a supermarket checkout. The ValidationURL is the cashier checking if your loyalty card is valid before ringing up. The ConfirmationURL is the receipt printer — it just records that the sale happened.
STEP 4 — Get a Free Callback URL for Testing
Use webhook.site to capture callbacks
Your ValidationURL and ConfirmationURL must be publicly accessible HTTPS endpoints. For sandbox testing, you do not need a real server — you can use a free tool called webhook.site.
- Go to https://webhook.site in your browser
- You will instantly get a unique URL like:
https://webhook.site/abc-123-xyz - Copy that URL — you will use it as both your ValidationURL and ConfirmationURL for testing
- Leave the webhook.site tab open. Callbacks from Safaricom will appear here in real time
🚫 URL Rules: Never use words like "MPesa", "M-Pesa", or "Safaricom" in your URLs — the system will block them. Also, localhost URLs will not work. Always use a proper HTTPS URL.
STEP 5 — Register Your Callback URLs
Tell Safaricom where to send payment notifications
Now you will call the Register URL API to link your callback URLs to your Paybill shortcode.
In Postman:
-
Method:
POST -
URL:
https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl
Headers tab — add these two headers:
| Header Key | Header Value |
|---|---|
| Authorization |
Bearer YOUR_ACCESS_TOKEN (paste the token from Step 2) |
| Content-Type | application/json |
Body tab — select "raw" and "JSON", then paste:
{
"ShortCode": "600584",
"ResponseType": "Completed",
"ConfirmationURL": "https://webhook.site/your-unique-url",
"ValidationURL": "https://webhook.site/your-unique-url"
}
| Field | Explanation |
|---|---|
| ShortCode | The sandbox test Paybill number (600584 is the default sandbox shortcode) |
| ResponseType | "Completed" means skip validation and auto-accept all payments |
| ConfirmationURL | Your webhook.site URL — where Safaricom sends payment confirmations |
| ValidationURL | Your webhook.site URL — where Safaricom asks for approval (optional here) |
A successful response looks like:
{
"OriginatorCoversationID": "...",
"ResponseCode": "0",
"ResponseDescription": "success"
}
STEP 6 — Simulate a C2B Payment
Pretend a customer is paying you
Now for the fun part — you will simulate a customer sending money to your Paybill. Safaricom provides a test phone number you can use.
In Postman:
-
Method:
POST -
URL:
https://sandbox.safaricom.co.ke/mpesa/c2b/v1/simulate
Use the same Authorization header as before, then paste this JSON body:
{
"ShortCode": "600584",
"CommandID": "CustomerPayBillOnline",
"Amount": "100",
"Msisdn": "254708374149",
"BillRefNumber": "INV001"
}
| Field | Explanation |
|---|---|
| ShortCode | Your sandbox Paybill number |
| CommandID | Use "CustomerPayBillOnline" for Paybill, or "CustomerBuyGoodsOnline" for Till |
| Amount | The amount the simulated customer is paying (in KES) |
| Msisdn | The test customer phone number — always use 254708374149 in sandbox |
| BillRefNumber | The account reference — e.g. an invoice number or order ID |
STEP 7 — Check Your Callback
See what your app would receive
After sending the simulation request, go to your webhook.site tab. Within a few seconds, you should see a POST request arrive. This is exactly the payload your real server would receive when a customer pays.
It will look something like this:
{
"TransactionType": "Pay Bill",
"TransID": "UCB030CBG1",
"TransTime": "20260311161727",
"TransAmount": "1.00",
"BusinessShortCode": "600991",
"BillRefNumber": "account001",
"InvoiceNumber": "",
"OrgAccountBalance": "4635316.60",
"ThirdPartyTransID": "",
"MSISDN": "bbff37cea44ac0b2d964ee0dfb8d2df8513dc7ba1b36129a929fc3fbd6dd4af4",
"FirstName": "John"
}
| Field | Value | Explanation |
|---|---|---|
TransactionType |
Pay Bill |
The type of transaction. Will be "Pay Bill" for Paybill or "Buy Goods" for Till numbers |
TransID |
UCB030CBG1 |
Unique M-PESA transaction ID. Use this as your reference to avoid processing the same payment twice |
TransTime |
20260311161727 |
Timestamp of the transaction in YYYYMMDDHHmmss format. This one means 11 March 2026 at 16:17:27 |
TransAmount |
1.00 |
The amount the customer paid in KES |
BusinessShortCode |
600991 |
Your Paybill or Till number that received the payment |
BillRefNumber |
account001 |
The account number the customer entered when paying. Use this to identify which customer or order the payment belongs to |
InvoiceNumber |
(empty) | Optional invoice number. Usually empty for most C2B transactions |
OrgAccountBalance |
4635316.60 |
Your Paybill account balance after this transaction was processed |
ThirdPartyTransID |
(empty) | Used in some integrations for a third-party reference. Usually empty |
MSISDN |
bbff37c... |
The customer's phone number. In sandbox it is returned as a hashed/masked value for privacy. In production it will be the actual number e.g. 254712345678
|
FirstName |
John |
The first name of the customer as registered on M-PESA |
💡 Tip: The three most important fields to save in your database are
TransID(to prevent duplicate processing),BillRefNumber(to identify the customer/order), andTransAmount(to confirm the correct amount was paid).
In a real application, you would read this JSON payload and use the TransAmount, BillRefNumber, and MSISDN fields to update your database and send a receipt to the customer.
⚠️ Sandbox Note: Sandbox callbacks can sometimes be unreliable. If your webhook.site does not receive anything after 30 seconds, try the simulation again. This is a known sandbox issue.
Full C2B Flow Recap
| Step | Action | What Happens |
|---|---|---|
| 1 | Get Token | You authenticate with your Consumer Key & Secret and receive a temporary access token |
| 2 | Register URLs | You tell Safaricom where to send payment notifications |
| 3 | Customer Pays | A real or simulated customer sends money to your Paybill or Till number |
| 4 | Validation | Safaricom hits your ValidationURL asking "should I accept this?" (optional) |
| 5 | Confirmation | Safaricom hits your ConfirmationURL with the full payment details |
| 6 | Your App Acts | Your server reads the payload and updates the database, sends a receipt, etc. |
PART TWO: GOING LIVE
Once your sandbox integration is working correctly, you are ready to go live and process real M-PESA transactions.
Prerequisites Before Going Live
Make sure you have all of the following before applying for live credentials:
- A working sandbox integration — all steps above tested and confirmed working
- A registered and verified Paybill or Till Number from Safaricom
- An active Safaricom G2 Business Admin account (used to verify your shortcode)
- A real publicly accessible HTTPS server URL (not localhost, not webhook.site) for your callback URLs
- Company documentation: Certificate of incorporation, KRA PIN, and directors' IDs (for businesses)
🏢 Get a Paybill First: To get a Paybill number, visit a Safaricom shop with your company registration documents. For a Till number, you can apply via the Safaricom self-onboarding portal. Processing takes 24–72 hours.
Step 1 — Register on the Safaricom G2 Portal
The G2 portal (https://org.ke.mpesa.com) is Safaricom's business management platform. You need an account here so Daraja can verify you own the Paybill or Till number you want to use.
- Send an email to M-PESABusiness@safaricom.co.ke requesting a Business Admin account
- Attach required documents: company registration certificate, KRA PIN, directors' IDs, and a signed board resolution
- Safaricom will create your G2 account and send login credentials
- Log in, change your password, and create an "assistant role" user — these credentials will be used in the next step
Step 2 — Confirm Your Sandbox Integration is Solid
Before applying to go live, make sure all of the following are working in sandbox:
- Access token generation is working
- URL registration returns a success response
- Simulated payments are triggering callbacks to your server
- Your server is responding with the correct
200 OKresponses - Your database or backend is correctly parsing and saving the callback payload
Step 3 — Click "Go Live" on the Daraja Portal
- Log into https://developer.safaricom.co.ke
- Go to "My Apps" and open your app
- Click the "Go Live" button
- Select "Verification by Shortcode" as the method
- Enter your Paybill/Till shortcode and your G2 assistant user credentials
- Select the API products you need (e.g. C2B)
- Upload your test cases — a document showing what you tested and the results
- Submit your request
⏱️ Approval Time: Safaricom typically takes 24–72 hours to review and approve your live request. You will receive your production credentials by email once approved.
Step 4 — Replace Sandbox Credentials with Live Ones
Once approved, update your application to use production values:
| What to Change | Sandbox → Production |
|---|---|
| API Base URL |
sandbox.safaricom.co.ke → api.safaricom.co.ke
|
| Consumer Key | Sandbox key → New live Consumer Key |
| Consumer Secret | Sandbox secret → New live Consumer Secret |
| ShortCode |
600584 (test) → Your actual Paybill/Till number |
| Callback URLs | webhook.site URLs → Your real HTTPS server URLs |
Step 5 — Test a Small Live Transaction
After switching to production credentials, do a small test with real money (e.g. KES 1) to confirm everything works end to end:
- Pay your Paybill from a real M-PESA number
- Confirm your ConfirmationURL receives the callback
- Verify your database is updated correctly
- Check that the customer receives the expected response or receipt
📝 Keep Logs: Always log every transaction in the early stages of going live. Implement retry logic in case of failed callbacks — Safaricom may occasionally retry if your server is slow to respond.
Common Mistakes to Avoid
| # | ❌ Do NOT do this | ✅ Do this instead |
|---|---|---|
| 1 | Include "MPesa" or "Safaricom" in your callback URLs | Use neutral words in your URLs e.g. /payment/confirm
|
| 2 | Use localhost or 127.0.0.1 as your callback URL | Host your app or use a tunneling tool like Ngrok (sandbox only) |
| 3 | Use webhook.site/ngrok URLs in production | Use a real, stable HTTPS server in production |
| 4 | Apply for all products without knowing what you need | Plan ahead — decide which APIs you need before going live |
| 5 | Forget to renew your access token every hour | Generate a fresh token on each session or add auto-renewal logic |
| 6 | Ignore the callback response format | Always respond with 200 OK and valid ResultCode/ResultDesc
|
Useful Resources
| Resource | URL |
|---|---|
| Daraja Developer Portal | https://developer.safaricom.co.ke |
| Daraja API Documentation | https://developer.safaricom.co.ke/APIs |
| Safaricom G2 Business Portal | https://org.ke.mpesa.com |
| Test Callback Tool | https://webhook.site |
| Safaricom Developer Community | https://developer.safaricom.co.ke/community |
| M-PESA Business Email | M-PESABusiness@safaricom.co.ke |
*Happy building! *
Top comments (0)