.NET/C# Integration Guide

Integrate SYSNAV M-Pesa Payment Gateway in your .NET applications

← Back to Platforms

Getting Started

This guide walks you through integrating the SYSNAV M-Pesa Payment Gateway into your .NET applications using C#.

Prerequisites

  • .NET 6.0 or higher
  • NuGet package manager
  • Visual Studio or Visual Studio Code
  • API credentials (clientId and clientSecret)

Installation

Install required NuGet packages:

dotnet add package Newtonsoft.Json
dotnet add package HttpClientFactory
dotnet add package System.Net.Http.Json

1. Register Your Client

First, register your application with the gateway to get API credentials.

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

public class GatewayClient
{
    private readonly HttpClient _httpClient;
    private const string ApiBaseUrl = "https://payments.sysnavtechnologies.com";

    public GatewayClient()
    {
        _httpClient = new HttpClient();
    }

    public async Task<dynamic> RegisterClient(string clientName, string email)
    {
        var payload = new
        {
            client_name = clientName,
            email = email
        };

        var json = JsonConvert.SerializeObject(payload);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        var response = await _httpClient.PostAsync(
            $"{ApiBaseUrl}/api/v1/clients", 
            content
        );

        var responseBody = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject(responseBody);
    }
}

// Usage
var client = new GatewayClient();
var result = await client.RegisterClient("My App", "contact@myapp.com");
var clientId = result["data"]["id"];
var clientSecret = result["data"]["api_key"];

2. Store Daraja Credentials

After registering, store your Daraja credentials securely on the gateway:

public async Task StoreCredentials(
    string clientId, 
    string consumerKey, 
    string consumerSecret, 
    string shortCode, 
    string passkey)
{
    var credentials = new
    {
        consumer_key = consumerKey,
        consumer_secret = consumerSecret,
        shortcode = shortCode,
        passkey = passkey
    };

    var json = JsonConvert.SerializeObject(credentials);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var request = new HttpRequestMessage(
        HttpMethod.Post,
        $"{ApiBaseUrl}/api/v1/clients/{clientId}/credentials"
    )
    {
        Content = content
    };
    
    request.Headers.Add("X-API-Key", "your_api_key_here");

    var response = await _httpClient.SendAsync(request);
    var responseBody = await response.Content.ReadAsStringAsync();
    
    return JsonConvert.DeserializeObject(responseBody);
}

// Usage
await client.StoreCredentials(
    clientId: "client_uuid_here",
    consumerKey: "your_daraja_key",
    consumerSecret: "your_daraja_secret",
    shortCode: "174379",
    passkey: "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919"
);

3. Initiate STK Push Payment

Trigger a payment prompt on the customer's phone:

public async Task<dynamic> InitiateSTKPush(
    string clientId,
    string apiKey,
    string phoneNumber,
    decimal amount,
    string accountReference,
    string transactionDescription)
{
    var payload = new
    {
        phone_number = phoneNumber,
        amount = amount,
        account_reference = accountReference,
        transaction_description = transactionDescription
    };

    var json = JsonConvert.SerializeObject(payload);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
    var request = new HttpRequestMessage(
        HttpMethod.Post,
        $"{ApiBaseUrl}/api/v1/stk-push"
    )
    {
        Content = content
    };
    
    request.Headers.Add("X-API-Key", apiKey);

    var response = await _httpClient.SendAsync(request);
    var responseBody = await response.Content.ReadAsStringAsync();
    
    return JsonConvert.DeserializeObject(responseBody);
}

// Usage
var payment = await client.InitiateSTKPush(
    clientId: "client_uuid",
    apiKey: "api_key_from_registration",
    phoneNumber: "254712345678",
    amount: 100,
    accountReference: "ACCOUNT001",
    transactionDescription: "Payment for Order #123"
);

var checkoutRequestId = payment["data"]["checkout_request_id"];
var customerMessage = payment["data"]["customer_message"];

4. Handle Webhook Callbacks

Listen for payment status updates from the gateway using ASP.NET Core:

// Controllers/WebhookController.cs
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class WebhookController : ControllerBase
{
    private readonly IPaymentService _paymentService;

    public WebhookController(IPaymentService paymentService)
    {
        _paymentService = paymentService;
    }

    [HttpPost("mpesa-callback")]
    public async Task<IActionResult> HandleMpesaCallback([FromBody] dynamic payload)
    {
        try
        {
            // Extract callback data
            var CheckoutRequestID = payload["Body"]["stkCallback"]["CheckoutRequestID"];
            var ResultCode = payload["Body"]["stkCallback"]["ResultCode"];
            var ResultDesc = payload["Body"]["stkCallback"]["ResultDesc"];
            var MpesaMessage = payload["Body"]["stkCallback"]["CallbackMetadata"]["Item"][1]["Value"];

            // Update transaction status
            await _paymentService.UpdateTransactionStatus(
                checkoutRequestId: CheckoutRequestID.ToString(),
                status: ResultCode == 0 ? "completed" : "failed",
                message: ResultDesc
            );

            // Return success response
            return Ok(new { status = "success" });
        }
        catch (Exception ex)
        {
            // Log error
            Console.WriteLine($"Webhook error: {ex.Message}");
            return BadRequest(new { error = ex.Message });
        }
    }
}

// Startup Configuration
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers().AddNewtonsoftJson();
        services.AddScoped<IPaymentService, PaymentService>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseEndpoints(endpoints => endpoints.MapControllers());
    }
}

5. Check Transaction Status

Query the status of a transaction at any time:

public async Task<dynamic> GetTransactionStatus(
    string clientId,
    string transactionId,
    string apiKey)
{
    var request = new HttpRequestMessage(
        HttpMethod.Get,
        $"{ApiBaseUrl}/api/v1/transactions/{transactionId}"
    );
    
    request.Headers.Add("X-API-Key", apiKey);

    var response = await _httpClient.SendAsync(request);
    var responseBody = await response.Content.ReadAsStringAsync();
    
    return JsonConvert.DeserializeObject(responseBody);
}

// Usage
var transaction = await client.GetTransactionStatus(
    clientId: "client_uuid",
    transactionId: "txn_uuid",
    apiKey: "api_key_here"
);

var status = transaction["data"]["status"];
var amount = transaction["data"]["amount"];
var createdAt = transaction["data"]["created_at"];

Error Handling

Implement comprehensive error handling:

public class ApiException : Exception
{
    public string ErrorCode { get; set; }
    public dynamic ResponseData { get; set; }

    public ApiException(string code, string message, dynamic data = null) 
        : base(message)
    {
        ErrorCode = code;
        ResponseData = data;
    }
}

public async Task<T> SafeApiCall<T>(
    Func<Task<string>> apiCall)
{
    try
    {
        var response = await apiCall();
        var data = JsonConvert.DeserializeObject<dynamic>(response);

        if (data["success"] == false)
        {
            throw new ApiException(
                data["error"]["code"].ToString(),
                data["error"]["message"].ToString(),
                data
            );
        }

        return JsonConvert.DeserializeObject<T>(data["data"].ToString());
    }
    catch (HttpRequestException ex)
    {
        throw new ApiException("NETWORK_ERROR", $"Network error: {ex.Message}");
    }
    catch (JsonException ex)
    {
        throw new ApiException("PARSE_ERROR", $"JSON parse error: {ex.Message}");
    }
}

Complete Example: Payment Flow

public class PaymentService
{
    private readonly GatewayClient _gateway;

    public PaymentService(GatewayClient gateway)
    {
        _gateway = gateway;
    }

    public async Task<dynamic> ProcessPayment(
        string clientId,
        string apiKey,
        string phoneNumber,
        decimal amount,
        string orderId)
    {
        try
        {
            // Step 1: Initiate STK Push
            var stkResponse = await _gateway.InitiateSTKPush(
                clientId: clientId,
                apiKey: apiKey,
                phoneNumber: phoneNumber,
                amount: amount,
                accountReference: $"ORDER_{orderId}",
                transactionDescription: $"Payment for Order {orderId}"
            );

            if (!stkResponse["success"])
            {
                throw new Exception(stkResponse["error"]["message"]);
            }

            var checkoutRequestId = stkResponse["data"]["checkout_request_id"];

            // Step 2: Wait for webhook callback (handle in your webhook endpoint)
            // The webhook will update the transaction status

            // Step 3: Optionally poll for status (in production, use webhooks)
            var statusTask = PollTransactionStatus(clientId, checkoutRequestId, apiKey);
            await Task.Delay(5000); // Wait 5 seconds before first poll

            return await statusTask;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Payment failed: {ex.Message}");
            throw;
        }
    }

    private async Task<dynamic> PollTransactionStatus(
        string clientId,
        string checkoutRequestId,
        string apiKey,
        int maxAttempts = 10)
    {
        for (int i = 0; i < maxAttempts; i++)
        {
            var transactions = await _gateway.GetTransactions(clientId, apiKey);
            var transaction = transactions["data"]
                .FirstOrDefault(t => t["checkout_request_id"] == checkoutRequestId);

            if (transaction != null && transaction["status"] != "pending")
            {
                return transaction;
            }

            await Task.Delay(2000); // Wait 2 seconds before retry
        }

        throw new Exception("Transaction status could not be determined");
    }
}

// Usage in ASP.NET Core Controller
[ApiController]
[Route("api/[controller]")]
public class PaymentsController : ControllerBase
{
    private readonly PaymentService _paymentService;

    [HttpPost("initiate")]
    public async Task<IActionResult> InitiatePayment(
        [FromBody] PaymentRequest request)
    {
        try
        {
            var result = await _paymentService.ProcessPayment(
                clientId: "your_client_id",
                apiKey: "your_api_key",
                phoneNumber: request.PhoneNumber,
                amount: request.Amount,
                orderId: request.OrderId
            );

            return Ok(result);
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
}

API Reference

Base URL

https://payments.navipos.co.ke

Authentication

Include your API key in the X-API-Key header for all protected endpoints.

Key Endpoints

  • POST - Initiate STK Push
  • GET - List Transactions
  • GET - Get Transaction Details
  • POST - Webhook Callback (no auth)

Ready to Integrate?

Start building secure payment solutions with SYSNAV M-Pesa Gateway

Back to Platforms