M-Pesa is the backbone of digital payments in Kenya, and Safaricom’s Daraja API makes it possible for developers to integrate M-Pesa services into their applications.
In this guide, we’ll implement Lipa na M-Pesa Online (STK Push) using Golang, covering authentication, payment initiation, and callback handling.
By the end of this article, you’ll be able to:
Authenticate with the Daraja API
Send an STK Push request
Handle payment callbacks from Safaricom
Prerequisites
Before you start, make sure you have:
Go installed (Go 1.20+ recommended)
A Daraja developer account
Sandbox credentials from the Daraja portal
Basic knowledge of Go and HTTP APIs
⚠️ Security Note
Never hardcode secrets in production. Always use environment variables.
Placeholders are used here for learning purposes.
Project Overview
The STK Push flow works as follows:
Obtain an OAuth access token from Daraja
Generate a password using Shortcode + Passkey + Timestamp
Send an STK Push request
Receive and process the callback from Safaricom
Configuration
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)
const (
consumerKey = "YOUR_CONSUMER_KEY"
consumerSecret = "YOUR_CONSUMER_SECRET"
shortcode = "174379" // Sandbox shortcode
passkey = "YOUR_PASSKEY"
baseURL = "https://sandbox.safaricom.co.ke"
callbackURL = "https://your-public-url/mpesa/callback"
)
Step 1: Getting an OAuth Access Token
Daraja uses OAuth 2.0 for authentication.
We generate an access token using Basic Auth.
func getAccessToken() (string, error) {
url := baseURL + "/oauth/v1/generate?grant_type=client_credentials"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
credentials := base64.StdEncoding.EncodeToString(
[]byte(consumerKey + ":" + consumerSecret),
)
req.Header.Add("Authorization", "Basic "+credentials)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var tokenRes struct {
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(resp.Body).Decode(&tokenRes); err != nil {
return "", err
}
return tokenRes.AccessToken, nil
}
This token is required for all subsequent Daraja API requests.
Step 2: Sending an STK Push Request
To initiate a payment request, we generate a password using:
Base64Encode(Shortcode + Passkey + Timestamp)
func sendSTKPush(token string, amount int, phone string) error {
timestamp := time.Now().Format("20060102150405")
password := base64.StdEncoding.EncodeToString(
[]byte(shortcode + passkey + timestamp),
)
payload := map[string]interface{}{
"BusinessShortCode": shortcode,
"Password": password,
"Timestamp": timestamp,
"TransactionType": "CustomerPayBillOnline",
"Amount": amount,
"PartyA": phone,
"PartyB": shortcode,
"PhoneNumber": phone,
"CallBackURL": callbackURL,
"AccountReference": "Ref123",
"TransactionDesc": "Test Payment",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest(
"POST",
baseURL+"/mpesa/stkpush/v1/processrequest",
bytes.NewBuffer(jsonData),
)
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
go
Step 3: Handling the Callback
After the user completes (or cancels) the payment, Safaricom sends a callback to your endpoint.
func stkCallbackHandler(w http.ResponseWriter, r *http.Request) {
var callback map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
log.Println("Callback received:", callback)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"ResultCode":0,"ResultDesc":"Received successfully"}`))
}
Callback Result Codes
ResultCode == 0 → Payment successful
Any other value → Payment failed or cancelled
To receive callbacks locally, expose your server using ngrok or Cloudflare Tunnel.
Step 4: Running the Application
📌 Phone numbers must be in the format 2547XXXXXXXX
```go{% embed %}
func main() {
http.HandleFunc("/mpesa/callback", stkCallbackHandler)
go func() {
log.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}()
token, err := getAccessToken()
if err != nil {
log.Fatal(err)
}
sendSTKPush(token, 1, "2547XXXXXXXX")
select {}
}
Testing in the Sandbox
Use test phone numbers provided by Safaricom
Ensure your callback URL is publicly reachable
Check logs for successful callback responses
Security Best Practices
Store secrets in environment variables
Validate callback payloads
Persist transactions in a database
Always verify payment status before fulfilling orders
Conclusion
Integrating M-Pesa Daraja STK Push using Golang is straightforward once you understand the authentication flow, request structure, and callback handling.
With this setup, you can build:
E-commerce platforms
SaaS billing systems
Internal payment tools
If you found this useful, feel free to leave a comment or share 🚀
Top comments (0)