Integrate SYSNAV M-Pesa Payment Gateway in your .NET applications
This guide walks you through integrating the SYSNAV M-Pesa Payment Gateway into your .NET applications using C#.
Install required NuGet packages:
dotnet add package Newtonsoft.Json
dotnet add package HttpClientFactory
dotnet add package System.Net.Http.Json
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"];
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"
);
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"];
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());
}
}
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"];
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}");
}
}
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 });
}
}
}
https://payments.navipos.co.ke
Include your API key in the X-API-Key header for all protected endpoints.
- Initiate STK Push - List Transactions - Get Transaction Details - Webhook Callback (no auth)Start building secure payment solutions with SYSNAV M-Pesa Gateway
Back to Platforms