DEV Community

Daniel Keya
Daniel Keya

Posted on

How to Integrate M-Pesa Daraja STK Push Using Golang

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"
)

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode


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"}`))
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

}



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 🚀
Enter fullscreen mode Exit fullscreen mode

Top comments (0)