NAV Navbar
shell php elixir java python csharp html

Introduction

Welcome to the eCitizen API guide. You can use our API to access SSO and Pesaflow API endpoints, which can allow you to perform different kinds transactions within our eco-system.

We have language bindings in Shell, JAVA , PHP and Elixir for necessary endpoints. You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.

Single Sign On (SSO)

This document provides a guide to using the eCitizen Authorization API, which is built on the OAuth 2.0 and OpenID Connect specifications.

The APIs are intended for use by Government MCDAs that need to allow users to log in to their applications via eCitizen. Submit Application Registration here

Sample flow is as below

...

Concepts - OAuth2.0 and OpenID Connect (OIDC)

OAuth2.0 is an open standard for authorization that provides a secure way for websites and applications to access protected user data without requiring the user to provide their credentials.

OpenID Connect (OIDC) is an authentication protocol built on top of OAuth2.0 that allows users to authenticate with a single sign-on (SSO) solution.

The Authorization Code Flow is a process that is used by a client to obtain an access token from an authorization server. The flow consists of the following steps:

  1. The client requests an authorization code from the authorization server.
  2. The user authorizes the client to access the protected resources.
  3. The authorization server issues an authorization code to the client.
  4. The client exchanges the authorization code for an access token from the authorization server.
  5. The client uses the access token to access the protected resources.

Proof Key for Code Exchange (PKCE)

Proof Key for Code Exchange (PKCE) is an extension to OAuth 2.0 that improves security for public clients by protecting against man-in-the-middle attacks. It does this by requiring the client to generate a unique code verifier and code challenge, which are then used to obtain an authorization code from the authorization server. The code verifier is used to verify the code challenge, which is used to protect the authorization code from guessing or theft.

To implement the Authorization Code Flow with PKCE, the following steps should be taken:

  1. Generate a code verifier and a code challenge. The code verifier is a cryptographically random string used to associate the authorization request with the token request. The code challenge is a hashed version of the code verifier (Code Verifier)
    • Must be at least 43 characters long.
    • Must be at most 128 characters long.
    • See the link above for more information on how to generate this Code Challenge php code_challenge = base64urlencode(sha256(code_verifier))
  2. Send the code challenge along with the authorization request.
  3. Upon approval, the authorization server will return an authorization code.
  4. Exchange the authorization code for a token using the code verifier.By using the code verifier and code challenge, the client can prove that it is the same one that initiated the authorization request.

By using the code verifier and code challenge, the client can prove that it is the same one that initiated the authorization request.

Introspection - Token introspection is the process of verifying the authenticity and integrity of a token to ensure its validity and prevent tampering.

Getting Started

To get started with this API you will need to have your application registered, providing the information below:

  1. Formal application name: This is the name of the MCDA. It will be displayed to users who need to authorize your application to access their eCitizen data (Boma Yangu in the image below).

  2. At least one redirect_uri - this is the URI to which the user logging in will be redirected after they authorize your application. You can provide more than one URL, e.g. one for production and one for testing.

Once registered, you will be provided with a client_id and a client_secret. The client_secret must not be exposed publicly and should only be used in server-to-server communication.

Your customers or system users will be taken to the redirect_uri specified. An example of this is below

...

Requesting Authorization

While the API supports most of the OAuth 2.0 flows, we require that clients use the Authorization Code Flow. We recommend using the Proof Key for Code Exchange (PKCE).

Upon clicking on the login button on your website, users will be redirected to eCitizen at the link provided below, with the parameters outlined.

Expected Parameters

Parameter Mandatory Description
redirect_uri Yes The URL to which the user is sent after the authorization is complete
client_id Yes The client ID you received when you first created the application.
response_type Yes Set this to 'code'
scope Yes A space-separated list of scopes that identify the resources that your application could access on the user's behalf. Pass 'openid'
state Yes (recommended) Data that will be returned to your application as was sent to the authorization server. Imagine a return_url after login.
code_challenge Yes (required for PKCE) The generated code challenge.
code_challenge_method Yes (required for PKCE) The method used to generate the challenge. Set this to S256.

The user can choose to authorize the request (by clicking on Proceed) or deny it (by clicking on Back).

Upon clicking on "Proceed," the user will be redirected to the redirect_uri specified in the authorization call, with the query parameters code and state. The code is the authorization code that will be used in the next request to obtain an access token. The state is the same as what was passed to the authorization request

Fetching the Access Token

You will need to have an access token for all calls to eCitizen-protected resources. After the user returns to your site, the application can exchange the authorization code for an access token at eCitizen’s oauth/access-token endpoint as shown

  curl -X POST https://accounts.ecitizen.go.ke/oauth/access-token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET' \
  -d 'grant_type=authorization_code' \
  -d 'code=YOUR_AUTHORIZATION_CODE' \
  -d 'redirect_uri=YOUR_REDIRECT_URI' \
  -d 'code_verifier=YOUR_CODE_VERIFIER'

Expected Parameters

Parameter Mandatory Description
client_id Yes The client ID you received when you registered the application.
redirect_uri Yes Must be identical to the redirect URI provided in the original '/oauth/authorize' request.
grant_type Yes Set this to 'authorization_code'
code Yes The code you received in the query string
code_verifier Yes The code verifier that was originally created.

This API responds with an access token, the token type, and the expiration date. The payload also includes an ID Token, which is a JWT that contains claims about the authenticated user and can be decoded to extract user information.

  {
    "access_token": "",
    "token_type": "",
    "expiration_date": "",
  }

Access Token Introspection

Use this API to verify that an access token is still valid.

curl -X POST \
 https://accounts.ecitizen.go.ke/api/oauth/token/introspect \
 -H 'Content-Type: application/x-www-form-urlencoded' \
 -d 'token=48wJCHeJpUpeGUrDsLXc1AC8gzhgn7HX'

If the token is valid, you will receive an HTTP status code 200 with a JSON payload in the format

  {
    "active": true,
    "scope": null,
    "token_type": "authorization_code",
    "client_id": "278f1ef8168611eebdbf0050560101d6"
  }

If the token is invalid - maybe expired - you will receive an HTTP status 200 with a JSON payload in the format

  {
    "active": false
  }

Fetching User Information

To get the user information, call the '/api/user-info' endpoint as shown.

curl -X GET "https://accounts.ecitizen.go.ke/api/userinfo?access_token=<ACCESS_TOKEN>"

If the request fails e.g. maybe the access token is expired, you will receive an HTTP status code 400 with a JSON payload below

  {
    "error": "invalid_request",
    "error_description": "The request is missing a required parameter..."
  }

If the request is successful, you will receive an HTTP status code 200 with a JSON payload

  {
    "id": 1,
    "id_number": "12345678",
    "email": "user@ecitizen.com",
    "active": true,
    "first_name": "John",
    "last_name": "Doe",
    "surname": "Blah",
    "account_type": "citizen",
    "mobile_number": "254711000000",
    "mobile_verified": true,
    "Gender": "m",
    "Kra_pin_number": "A1000121",
    "dob": "1951-05-28"
  }

Invoicing

Creating invoices is easy. Pesaflow allows you to do this using the available checkout iframe endpoint.

To do this you need an api_client_id, secret and key for generating a secure hash.

These (api_client_id, secret and key) are provided by Pesaflow Team. If you have issues with your credentials contact Our Support Team for assistance.

Secure Hash

<?php
$api_client_id= "my-client-id provided by Pesaflow";
$service_id = "my-client-id provided by Pesaflow";
$id_number = "Customer ID number";
$currency = "KES";
$client_invoice_ref = "Ab-CD-EF-001";
$desc = "Description of invoice";
$name = "John Doe";
$secret = "my-secret";
$key = "my-key";

data_string = "$api_client_id"."$amount_expected"."$service_id"."$id_number"."$currency"."$client_invoice_ref"."$desc"."$name"."$secret";
secure_hash = base64_encode(hash_hmac('sha256', $data_string, $key));
echo "SecureHash : $secure_hash\n";
?>

generate_secure_hash("my-key",["1","100","124","12345678","KES","REF-01-2024-XVBC","Some invoice","John Doe","my-secret"])

def generate_secure_hash(secret, hash_list) when is_list(hash_list) do
    :crypto.hmac(:sha256, secret, hash_list |> Enum.map(&to_string(&1)))
    |> Base.encode16()
    |> String.downcase()
    |> Base.encode64()
end

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.lang.*;
import java.io.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
public class Main {
private final String USER_AGENT = "Mozilla/5.0";
  public static void main(String[] args) throws Exception {
      Main m = new Main();
      String hash = m.genSig();

      System.out.println("\nHash - "+ hash);
  }
  // HTTP POST request
  private String genSig() {

      /**
       * Validate Payment
       * This endpoint validates a invoices or topups exist before payment is accepted.
       * To generate your secure hash, use format below,
       * 
       */


      String provided_hmac_secret = "your-key"; 
      String api_client_id= "your-api-client-id";
      String data_string = "[YOUR-DATA-STRING]";

      return  generate_signature(data_string, provided_hmac_secret);
  }

  static String generate_signature(String data_string, String secret){
     try{

           SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(),"HmacSHA256");

           Mac mac = Mac.getInstance("HmacSHA256");
           mac.init(keySpec);
           byte[] result = mac.doFinal(data_string.getBytes());

          String encode_result = Base16Encoder.encode(result);


          return Base64.getEncoder().encodeToString(encode_result.toLowerCase().getBytes());

       } catch (Exception e) {

            return "Cannot Process Signature";
      }
  }
}
class Base16Encoder {
    private final static char[] HEX = new char[]{
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    /**
     * Convert bytes to a base16 string.
     */
    public static String encode(byte[] byteArray) {
        StringBuffer hexBuffer = new StringBuffer(byteArray.length * 2);
        for (int i = 0; i < byteArray.length; i++)
            for (int j = 1; j >= 0; j--)
                hexBuffer.append(HEX[(byteArray[i] >> (j * 4)) & 0xF]);
        return hexBuffer.toString();
    }

    /**
     * Convert a base16 string into a byte array.
     */
    public static byte[] decode(String s) {
        int len = s.length();
        byte[] r = new byte[len / 2];
        for (int i = 0; i < r.length; i++) {
            int digit1 = s.charAt(i * 2), digit2 = s.charAt(i * 2 + 1);
            if (digit1 >= '0' && digit1 <= '9')
                digit1 -= '0';
            else if (digit1 >= 'A' && digit1 <= 'F')
                digit1 -= 'A' - 10;
            if (digit2 >= '0' && digit2 <= '9')
                digit2 -= '0';
            else if (digit2 >= 'A' && digit2 <= 'F')
                digit2 -= 'A' - 10;

            r[i] = (byte) ((digit1 << 4) + digit2);
        }
        return r;
    }
}
using System;
using System.Security.Cryptography;
using System.Text;
public class HelloWorld {
    public static void Main(string[] args) {
        var key =  "[YOUR-KEY]";
        var data_string = "[YOUR-DATA-STRING]";
        byte[] res = hmacSHA256(data_string, key);
        var enc = BitConverter.ToString(res).Replace("-", "").ToLower();
        Console.WriteLine(Base64Encode(enc));
    }
    static byte[] hmacSHA256(String data, String key) {
        using (HMACSHA256 hmac = new HMACSHA256(Encoding.ASCII.GetBytes(key)))
        {
            return hmac.ComputeHash(Encoding.ASCII.GetBytes(data));
        }
    }
    public static string Base64Encode(string plainText) {
    var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
    return System.Convert.ToBase64String(plainTextBytes);
    }
}

This segment explains or demonstrates how to generate a secure hash. To generate your secure hash for checkout purpose, the below fields are required:-

Expected Parameters

Parameter Mandatory Description
apiClientID Yes Provided by Pesaflow
amountExpected Yes Amount to be paid
serviceID Yes Service ID Provided by Pesaflow
clientIDNumber Yes Customers ID Number
currency Yes Bill currency
billRefNumber Yes Unique client reference
billDesc Yes Description of bill
clientName Yes Customers Name
secret Yes Unique client secret provided by Pesaflow

The above command returns a base64 encoded hash

Checkout

curl -H "Authorization: Bearer <TOKEN>" \
  -d '<see sample payload in `php` or `elixir` tab >'
 -XPOST "https://{UAT_SERVER_URL}/api/PaymentAPI/checkout"

<form method="post" action="https://{UAT_SERVER_URL}/api/PaymentAPI/checkout" target="some_iframe" id="some_form">
    <input type="hidden" name="apiClientID" id="apiClientID" value="[apiClientID provided by Pesaflow]">
    <input type="hidden" name="serviceID" id="serviceID" value="[serviceID provided by Pesaflow]">
    <input type="hidden" name="billDesc" id="billDesc" value="Invoice for service X">
    <input type="hidden" name="currency" id="currency" value="KES">
    <input type="hidden" name="billRefNumber" id="billRefNumber" value="Bill-bb9a0495-f586-40b6-b4a7-37f78f7e51a0">
    <input type="hidden" name="clientMSISDN" id="clientMSISDN" value="2547XXXYYYZZ">
    <input type="hidden" name="clientName" id="clientName" value="John Doe"/>
    <input type="hidden" name="clientIDNumber" id="clientIDNumber" value="00"/>
    <input type="hidden" name="clientEmail" id="clientEmail" value="some@gmail.com"/>
    <input type="hidden" name="callBackURLOnSuccess" id="callBackURLOnSuccess" value="[Your webpage callback URL]"/>
    <input type="hidden" name="amountExpected" id="amountExpected" value="1"/>
    <input type="hidden" name="notificationURL" id="notificationURL" value="[Your notification URL]"/>
    <input type="hidden" name="pictureURL" id="pictureURL" value=""/>
    <input type="hidden" name="secureHash" id="secureHash" value="MmEwMWNlNDNkZWFmYTg4MmFkMTE0MWFkMmRjNjliZDU2Mzk4NTViNmRiMjFiMWY2MjRjNzFkMjVjYzdmYmI0MA=="/>
    <input type="hidden" name="format" value="html"/>
    <input type="hidden" name="sendSTK" value="false"/>
    <input type="hidden"  value="Submit"/>
</form>
<form method="post" action="https://{UAT_SERVER_URL}/api/PaymentAPI/checkout" target="some_iframe" id="some_form">
    <input type="hidden" name="apiClientID" id="apiClientID" value="[apiClientID provided by Pesaflow]">
    <input type="hidden" name="serviceID" id="serviceID" value="[serviceID provided by Pesaflow]">
    <input type="hidden" name="billDesc" id="billDesc" value="Invoice for service X">
    <input type="hidden" name="currency" id="currency" value="KES">
    <input type="hidden" name="billRefNumber" id="billRefNumber" value="Bill-bb9a0495-f586-40b6-b4a7-37f78f7e51a0">
    <input type="hidden" name="clientMSISDN" id="clientMSISDN" value="2547XXXYYYZZ">
    <input type="hidden" name="clientName" id="clientName" value="John Doe"/>
    <input type="hidden" name="clientIDNumber" id="clientIDNumber" value="00"/>
    <input type="hidden" name="clientEmail" id="clientEmail" value="some@gmail.com"/>
    <input type="hidden" name="callBackURLOnSuccess" id="callBackURLOnSuccess" value="[Your webpage callback URL]"/>
    <input type="hidden" name="amountExpected" id="amountExpected" value="1"/>
    <input type="hidden" name="notificationURL" id="notificationURL" value="[Your notification URL]"/>
    <input type="hidden" name="pictureURL" id="pictureURL" value=""/>
    <input type="hidden" name="secureHash" id="secureHash" value="MmEwMWNlNDNkZWFmYTg4MmFkMTE0MWFkMmRjNjliZDU2Mzk4NTViNmRiMjFiMWY2MjRjNzFkMjVjYzdmYmI0MA=="/>
    <input type="hidden" name="format" value="html"/>
    <input type="hidden" name="sendSTK" value="false"/>
    <input type="hidden"  value="Submit"/>
</form>

The above will redirect or return a html page that allows your customer to make payment.

This endpoint creates a new invoice for a customer and redirects to the Pesaflow checkout page.

HTTP Request

Post data as form data

POST https://{UAT_SERVER_URL}/api/PaymentAPI/checkout

Expected Parameters

Parameter Mandatory Description
apiClientID Yes Provided by Pesaflow
serviceID Yes Provided by Pesaflow
billRefNumber Yes Unique client transaction number
billDesc Yes Description of the invoice
clientMSISDN Yes Mobile number of whoever is receiving the invoice or bill
clientIDNumber Yes Customers ID Number
clientName Yes Customers Name
clientEmail Yes Customers Email
notificationURL Yes Clients endpoint URL to receive invoice updates on payment
pictureURL No URL of customers image to be displayed on checkout
callBackURLOnSuccess Yes Callback url to be invoked for redirection to merchant site on successful payment
currency Yes Currency for the invoice. This is based on the serviceID.
amountExpected Yes Invoice amount
format No Default format is 'html'. If you want to store the invoice number set this to 'json'
sendSTK No If you want the customer to receive M-PESA STK set this to 'true'
secureHash Yes Hash generated to ensure validity of invoice

Payments

This segment applies to Payment Aggregators and Banks and describes the API available on Pesaflow, to receive and process payment instructions for invoices paid at the aggregator or bank.

Validate Payment

curl -H "Content-Type: application/json" \
 -XPOST -d '<see sample payload in `php` or `elixir` tab >' \
 "https://{UAT_SERVER_URL}/api/payment/validate"

<?php

  //Sample hash-generation
  $secret="some-secret";
  $key="some-key";
  $api_client_id="your-id";
  $invoice_number="invoice-number";
  $amount="some-amount";

  $data_string = $api_client_id.$invoice_number.$amount.$secret;

  echo "\nDataString: $data_string\n";

  $hash = base64_encode(hash_hmac('sha256', $data_string, $key));

  echo "\n $hash \n"
?>

 <?php
  $url = 'https://{UAT_SERVER_URL}/api/payment/validate';
  $api_client_id = "";
  $invoice_no = "";
  $amount = "";
  $currency="";
  $secret="";
  $key="";

  $data = array(
    "api_client_id"=> $api_client_id,
    "ref_no"=> $invoice_no,
    "currency"=> $currency,
    "amount"=> $amount,    
    "secure_hash"=> base64_encode(hash_hmac('sha256', $api_client_id.$invoice_number.$amount.$secret, $key));,
  );
  $payload = json_encode($data);
  $headers = ["Content-Type","application/json"];

  $curl = curl_init();
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  $curl_response = curl_exec($curl);
  echo $curl_response;
?>
url = 'https://{UAT_SERVER_URL}/api/payment/validate'
payload = %{
  "api_client_id": "",
  "ref_no": "",
  "currency": "",
  "amount": "",  
  "secure_hash": "",
}
{:ok, %HTTPoison.Response{body: body}} = HTTPoison.post(url, payload, %{
                    "Content-type" => "application/json;charset=UTF-8"})
{:ok, body}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.lang.*;
import java.io.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
public class Main {
private final String USER_AGENT = "Mozilla/5.0";
  public static void main(String[] args) throws Exception {
      Main m = new Main();
      String hash = m.genSig();

      System.out.println("\nHash - "+ hash);
  }
  // HTTP POST request
  private String genSig() {

      /**
       * Validate Payment
       * This endpoint validates a invoices or topups exist before payment is accepted.
       * To generate your secure hash, use format below,
       * data_string = "$api_client_id"."$ref_no"."$amount"."$secret"
       * 
       */
      String provided_hmac_secret = "your-key"; //
      String api_client_id= "your-api-client-id";
      String invoice_no = "";
      String amount = "";
      String secret = "your-secret";
      String data_string = api_client_id + invoice_no + amount + secret;

      return  generate_signature(data_string, provided_hmac_secret);
  }

  static String generate_signature(String data_string, String secret){
     try{

           SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(),"HmacSHA256");

           Mac mac = Mac.getInstance("HmacSHA256");
           mac.init(keySpec);
           byte[] result = mac.doFinal(data_string.getBytes());

          String encode_result = Base16Encoder.encode(result);


          return Base64.getEncoder().encodeToString(encode_result.toLowerCase().getBytes());

       } catch (Exception e) {

            return "Cannot Process Signature";
      }
  }
}
class Base16Encoder {
    private final static char[] HEX = new char[]{
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    /**
     * Convert bytes to a base16 string.
     */
    public static String encode(byte[] byteArray) {
        StringBuffer hexBuffer = new StringBuffer(byteArray.length * 2);
        for (int i = 0; i < byteArray.length; i++)
            for (int j = 1; j >= 0; j--)
                hexBuffer.append(HEX[(byteArray[i] >> (j * 4)) & 0xF]);
        return hexBuffer.toString();
    }

    /**
     * Convert a base16 string into a byte array.
     */
    public static byte[] decode(String s) {
        int len = s.length();
        byte[] r = new byte[len / 2];
        for (int i = 0; i < r.length; i++) {
            int digit1 = s.charAt(i * 2), digit2 = s.charAt(i * 2 + 1);
            if (digit1 >= '0' && digit1 <= '9')
                digit1 -= '0';
            else if (digit1 >= 'A' && digit1 <= 'F')
                digit1 -= 'A' - 10;
            if (digit2 >= '0' && digit2 <= '9')
                digit2 -= '0';
            else if (digit2 >= 'A' && digit2 <= 'F')
                digit2 -= 'A' - 10;

            r[i] = (byte) ((digit1 << 4) + digit2);
        }
        return r;
    }
}

The above command returns JSON structured like this:

  {
    "status": 200,
    "description": "Bill Found",
    "data": {
      "amount": "xxx",
      "name": "xxx",
      "currency": "xxx"
    }
  }

  {
    "status": 404,
    "description": "Bill not found",
  }

  {
    "status": 500,
    "description": "Internal Error or any other failure error",
  }

This endpoint checks the validity of an invoice before payment is accepted.

To generate your secure hash, use fields below:-

Expected Parameters

Parameter Mandatory Description
api_client_id Yes Provided by Pesaflow
ref_no Yes Invoice Reference
amount Yes Amount paid
secret Yes Unique client secret provided by Pesaflow

HTTP Request

POST https://{UAT_SERVER_URL}/api/payment/validate

Expected Parameters

Parameter Mandatory Description
api_client_id Yes The provided client ID.
ref_no Yes Unique invoice/topup reference code from Pesaflow.
currency Yes Currency associated
amount Yes Amount of the bill.
secure_hash Yes A sha256 encoded hash.

Confirm Payment

curl -H "Content-Type: application/json"\
-XPOST -d '<see sample payload in `php` or `elixir` tab >'\
"https://{UAT_SERVER_URL}/api/payment/confirm"
 <?php
  $url = 'https://{UAT_SERVER_URL}/api/payment/confirm';
  $key = "";
  $secret = "";
  $api_client_id = "";
  $invoice_no = "";
  $amount = "";
  $currency="";
  $gateway_transaction_id = "";
  $gateway_transaction_date = date("Y-m-d H:i:s");
  $customer_name = "John Doe";
  $customer_account_number = "";

  $data = array(
    "api_client_id"=> $api_client_id,
    "ref_no"=> $invoice_no,
    "amount"=> $amount, 
    "currency"=> $currency,
    "gateway_transaction_id"=> $gateway_transaction_id,
    "gateway_transaction_date"=> $gateway_transaction_date,
    "customer_name"=> $customer_name,
    "customer_account_number"=> $customer_account_number,
    "secure_hash"=> base64_encode(hash_hmac('sha256',$api_client_id . $invoice_no . $amount . $currency . $gateway_transaction_id . $gateway_transaction_date . $customer_name . $customer_account_number . $secret, $key))
  );

  $headers = ["Content-Type","application/json"];

  $payload = json_encode($data);
  $curl = curl_init();
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  $curl_response = curl_exec($curl);
  echo $curl_response;
?>
url = 'https://{UAT_SERVER_URL}/api/payment/confirm'
payload = %{
  "api_client_id": "",
  "ref_no": "",
  "amount": "",  
  "currency": "",
  "gateway_transaction_id": "",
  "gateway_transaction_date": "",
  "customer_name": "",
  "customer_account_number": "",
  "secure_hash": "",
}
{:ok, %HTTPoison.Response{body: body}} = HTTPoison.post(url, payload, %{
                    "Content-type" => "application/json;charset=UTF-8"})
{:ok, body}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.lang.*;
import java.io.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
public class Main {
private final String USER_AGENT = "Mozilla/5.0";
  public static void main(String[] args) throws Exception {
      Main m = new Main();
      String hash = m.genSig();

      System.out.println("\nHash - "+ hash);
  }
  // HTTP POST request
  private String genSig() {

      String provided_hmac_secret = "key"; //
      String api_client_id= "1";
      String invoice_no = "NWQTYU";
      String currency = "SSP";
      String amount = "10";
      String gateway_transaction_id="1234434";
      String gateway_transaction_date="2021-01-26 15:30:01";
      String customer_name = "JOHN DOE";
      String customer_account_number = "438485959";
      String secret = "secret";
      String data_string = api_client_id + invoice_no + amount + currency + gateway_transaction_id + gateway_transaction_date + customer_name + customer_account_number + secret;

      return  generate_signature(data_string, provided_hmac_secret);
  }

  static String generate_signature(String data_string, String secret){
     try{

           SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(),"HmacSHA256");

           Mac mac = Mac.getInstance("HmacSHA256");
           mac.init(keySpec);
           byte[] result = mac.doFinal(data_string.getBytes());

          String encode_result = Base16Encoder.encode(result);


          return Base64.getEncoder().encodeToString(encode_result.toLowerCase().getBytes());

       } catch (Exception e) {

            return "Cannot Process Signature";
      }
  }
}
class Base16Encoder {
    private final static char[] HEX = new char[]{
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    /**
     * Convert bytes to a base16 string.
     */
    public static String encode(byte[] byteArray) {
        StringBuffer hexBuffer = new StringBuffer(byteArray.length * 2);
        for (int i = 0; i < byteArray.length; i++)
            for (int j = 1; j >= 0; j--)
                hexBuffer.append(HEX[(byteArray[i] >> (j * 4)) & 0xF]);
        return hexBuffer.toString();
    }

    /**
     * Convert a base16 string into a byte array.
     */
    public static byte[] decode(String s) {
        int len = s.length();
        byte[] r = new byte[len / 2];
        for (int i = 0; i < r.length; i++) {
            int digit1 = s.charAt(i * 2), digit2 = s.charAt(i * 2 + 1);
            if (digit1 >= '0' && digit1 <= '9')
                digit1 -= '0';
            else if (digit1 >= 'A' && digit1 <= 'F')
                digit1 -= 'A' - 10;
            if (digit2 >= '0' && digit2 <= '9')
                digit2 -= '0';
            else if (digit2 >= 'A' && digit2 <= 'F')
                digit2 -= 'A' - 10;

            r[i] = (byte) ((digit1 << 4) + digit2);
        }
        return r;
    }
}

The above command returns JSON structured like this:

  {
    "status": 200,
    "description": "Success",
  }

  {
    "status": 500,
    "description": "Failed",
  }

This endpoint received and stores the payment tied to a specific invoice.

To generate your secure hash, use fields below:-

Expected Parameters

Parameter Mandatory Description
api_client_id Yes Provided by Pesaflow
ref_no Yes Invoice Reference
amount Yes Amount paid
currency Yes Currency
gateway_transaction_id Yes Unique gateway transaction ID
gateway_transaction_date Yes Transaction date, format Y-m-d H:i:s
customer_name Yes Customers name
customer_account_number Yes Customers account number e.g phone number
secret Yes Unique client secret provided by Pesaflow

HTTP Request

POST https://{UAT_SERVER_URL}/api/payment/confirm

URL Parameters

Parameter Mandatory Description
api_client_id Yes The provided client ID.
ref_no Yes Unique bill/invoice reference code.
amount Yes Amount of the bill.
currency Yes Currency used for payment
gateway_transaction_id Yes Callers unique transaction reference
gateway_transaction_date Yes Date and time transaction takes place. Format Y-m-d h:i:s e.g 2019-08-01 08:30:08
customer_name Yes Name of customer.
customer_account_number Yes Associated account identifier for customer e,g id_number or msisdn can be used here
secure_hash Yes A sha256 encoded hash.

Instant Payment Notification

POST https://{thirdparty endpoint}

<?php
  $json = file_get_contents('php://input');
  $data = json_decode($json);
  //Sample hash-generation
  $secret="some-secret";
  $key="some-key";
  $client_invoice_ref= $data["client_invoice_ref"];
  $invoice_number=$data["invoice_number"];
  $amount=$data["amount_paid"];
  $payment_date = $data["payment_date"];

  $data_string = $client_invoice_ref.$invoice_number.$amount.$payment_date.$secret;

  echo "\nDataString: $data_string\n";

  $hash = base64_encode(hash_hmac('sha256', $data_string, $key));

  echo "\n $hash \n"
?>
key = "my-key"
secret = "my-secret"
hash_list = ["#{client_invoice_ref}", "#{invoice_number}", "#{amount_paid}", "#{payment_date}", "#{secret}"]

def generate_secure_hash(key, hash_list) when is_list(hash_list) do
  :crypto.hmac(:sha256, key, hash_list)
  |> Base.encode16()
  |> String.downcase()
  |> Base.encode64()
end

Pesaflow will send a payment notification after SUCCESSFUL processing. You will expect a payload that is structured as below and within this payload, a secure_hash is provided so that third-party systems can be able to verify the legitimacy of the notification. A Sample is below,


{
  "payment_channel" : "Some Channel",
  "client_invoice_ref" : "SS6489db2e1fdfd",
  "payment_reference" : [ {
    "payment_reference" : "ABCDEF-925",
    "payment_date" : "2023-06-14T15:33:16",
    "inserted_at" : "2023-06-14T15:33:16",
    "currency" : "KES",
    "amount" : "102.00"
  } ],
  "currency" : "KES",
  "amount_paid" : "102.00",
  "invoice_amount" : "102.00",
  "status" : "settled",
  "invoice_number" : "ABCDEF",
  "payment_date" : "2023-06-14 15:33:16Z",
  "last_payment_amount" : "102.00",
  "secure_hash": "NTU4NzEzNzhiOTI1N2NlODY3YWYzYjVhYjQ4MzNiNDYzY2M3MzQwYmNlZDc4ZDJlZjg3ZDZkOTQ5ZjUyM2EzNQ==",
}

Please note: Use secure_hash, to check validity of the received notification.

In addition, to generate a matching hash, use/concatenate the following fields in the same order:-

Expected Parameters

Parameter Mandatory Description
client_invoice_ref Yes Clients reference
invoice_number Yes Invoice Reference
amount_paid Yes Amount paid
payment_date Yes Payment Date
secret Yes Unique client secret provided by Pesaflow

The above parameters are from the sample IPN payload on to your right side.

Query Payment Status

curl -H "Content-Type: application/json" \
-X GET -d '<see sample payload in `php` or `elixir` tab >' \
"https://{UAT_SERVER_URL}/api/invoice/payment/status?api_client_id=YOUR_CLIENT_ID&ref_no=YOUR_REF_NO&secure_hash=YOUR_HASH"
 <?php

  $key = "my_key";
  $data_string = "my_client_id"."my_ref_no";
  $secure_hash = base64_encode(hash_hmac('sha256', $data_string, $key));

  $url = 'https://{UAT_SERVER_URL}/invoice/payment/status';
  $data = array(
    "api_client_id"=>"",
    "ref_no"=>"",
    "secure_hash"=>"$secure_hash",
  );
  $payload = json_encode($data);
  $curl = curl_init();
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
  $curl_response = curl_exec($curl);
  echo $curl_response;
?>
key = "my-key"
api_client_id="my_api_client_id"
ref_no="ref-no"
hash_list = ["#{api_client_id}", "#{ref_no}"]

secure_hah = generate_secure_hash(key, hash_list) # Referenced from the Elixir hash sample

url = 'https://{UAT_SERVER_URL}/invoice/payment/status'
payload = %{
  "api_client_id": "",
  "ref_no": "",
  "secure_hash": ""
}
{:ok, %HTTPoison.Response{body: body}} = HTTPoison.get(url, payload, %{
                    "Content-type" => "application/json;charset=UTF-8"})
{:ok, body}

The above command returns JSON structured like this(example):

{
    "status": "pending",
    "ref_no": "WVZGEP",
    "name": "Test User",
    "currency": "KES",
    "client_invoice_ref": "EPIC-ORDER103",
    "payment_date": "2024-01-01 12:30:01",
    "amount_paid": "0.00",
    "amount_expected": "100.00"
}

Status can be - pending, partial, settled

In the event that push notification is not available, or Network Timeouts, clients can use this endpoint to query the status of bill payments.

This endpoint checks the status of an invoice. To generate your secure hash, use fields below:-

Expected Parameters

Parameter Mandatory Description
api_client_id Yes Provided by Pesaflow
ref_no Yes Invoice Reference
key Yes Unique client key provided by Pesaflow

HTTP Request

GET https://{UAT_SERVER_URL}/api/invoice/payment/status

URL Parameters

Parameter Mandatory Description
api_client_id Yes Provided by Pesaflow
ref_no Yes Unique invoice number from Pesaflow.
secure_hash Yes A sha256 encoded hash.

USSD

Pluging in your USSD service is made easy. Pesaflow allows you to do this using the available endpoints.

To do this you need an api_client_id, secret and key for generating a secure hash.

These (api_client_id, secret and key) are provided by Pesaflow Team. If you have issues with your credentials contact Our Support Team for assistance.

USSD Integration

This segment provides information on how to plugin an existing USSD channel so that your end consumers can be able to pay for services onboarded on eCitizen. If a validation URL is provided, the following payload will be sent to the endpoint. This will be a post request. Note that this is server to server to communication.

SecureHash Generation

If validation of the payload is required, the secure hash can be recreated using the below code examples in your preferred language. After this, the two hashes can be compared and if they are similar, the payload can be assumed to be valid. A few code snippets are available on the right hand section on how to recreate the hash using different common languages.

Expected Parameters

Parameter Mandatory Description
reference Yes Provided by user. This is information the user has provided and validation is required by calling an API endpoint. It may be an ID number, invoice, parcel no or any other valid information
service_code Yes Service code provided by user. This is the service code belonging to the service the customer wants to pay for
secure_hash Yes A hash to validate the payload. This is a hash of payload in combination with the merchant secrets i.e merchant key and merchant secret. This is provided so as it can be used to validate if the request is coming from a trusted source i.e eCitizen. The only way to validate it is to recreate the hash and compare with what is provided. If they match, it is safe to assume the request is from a trusted source.


{ "display_info": { "Name": "John Doe", "Account": "123456789" }, "valid":true }

...

  <?php
    $reference = "your_reference";
    $service_code = "your_service_code";
    $merchant_key = "your_merchant_key";
    $merchant_secret = "your_merchant_secret";
    // Concatenate the data
    $dataToHash = $reference . $service_code . $merchant_secret;
    // Calculate HMAC-SHA256
    $hash = hash_hmac("sha256", $dataToHash, $merchant_key, true);
    // Convert to hexadecimal
    $hexHash = bin2hex($hash);
    // Convert to lowercase
    $lowercaseHash = strtolower($hexHash);
    // Base64 encode
    $base64Encoded = base64_encode(hex2bin($lowercaseHash));
    echo $base64Encoded;
  ?>
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.util.Base64;
  public class HmacExample {
    public static String generateHmac(String reference, String serviceCode, String merchantKey, String merchantSecret) {
      try {
      // Create a SecretKeySpec object for the HMAC key
      SecretKeySpec keySpec = new
      SecretKeySpec(merchantKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
      // Create an instance of the Mac algorithm and
      initialize it with the key
      Mac mac = Mac.getInstance("HmacSHA256");
      mac.init(keySpec);
      // Concatenate the inputs and compute the HMAC
      String input = reference + serviceCode +
      merchantSecret;
      byte[] hmacBytes =
      mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
      // Encode the result in base16 (hex)
      String hexEncoded = bytesToHex(hmacBytes);
      // Convert the hex string to lowercase
      hexEncoded = hexEncoded.toLowerCase();
      // Encode the lowercase hex string in base64
      byte[] base64Encoded = Base64.getEncoder().encode(hexEncoded.getBytes(StandardCharsets.UTF_8));
      return new String(base64Encoded,
      StandardCharsets.UTF_8);
      } catch (Exception e) {
        e.printStackTrace();
        return null;
      }
    }

    private static String bytesToHex(byte[] bytes) {
      StringBuilder hexStringBuilder = new StringBuilder(2 * bytes.length);
      for (byte b : bytes) {
      hexStringBuilder.append(String.format("%02x", b &0xFF));
      }
      return hexStringBuilder.toString();
    }

    public static void main(String[] args) {
      String reference = "yourReference";
      String serviceCode = "yourServiceCode";
      String merchantKey = "yourMerchantKey";
      String merchantSecret = "yourMerchantSecret";
      String result = generateHmac(reference, serviceCode,
      merchantKey, merchantSecret);
      System.out.println("Generated HMAC: " + result);
    }
  import hashlib
  import base64
  def generate_mac(merchant_key, reference, service_code, merchant_secret):
    # Concatenate data
    data_to_sign =
    f"{reference}{service_code}{merchant_secret}".encode('utf-8')
    # Calculate HMAC-SHA256
    hmac_sha256 = hashlib.new('sha256',
    merchant_key.encode('utf-8'))
    hmac_sha256.update(data_to_sign)
    mac_result = hmac_sha256.digest()
    encoded_result = base64.b64encode(mac_result).decode('utf-8')
    return encoded_result.lower()
  # Example usage
  merchant_key = "your_merchant_key"
  reference = "your_reference"
  service_code = "your_service_code"
  merchant_secret = "your_merchant_secret"
  result = generate_mac(merchant_key, reference, service_code,
  merchant_secret)
  print(result)
  using System;
  using System.Security.Cryptography;
  using System.Text;
  class Program
  {
    static void Main() {
      string merchantKey = "your_merchant_key";
      string reference = "your_reference";
      string serviceCode = "your_service_code";
      string merchantSecret = "your_merchant_secret";
      string mac = GenerateHmacSha256(merchantKey, reference,
      serviceCode, merchantSecret);
      Console.WriteLine(mac);
    }
    static string GenerateHmacSha256(string key, params string[] values) {
      using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
      {
      var data = string.Join("", values);
      var hashBytes =
      hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
      var hexString =
      BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
      var base64String =
      Convert.ToBase64String(Encoding.UTF8.GetBytes(hexString));
      return base64String;
    }
  }

The response expected from this validation request looks like below:-

  {
    "display_info": {},
    "valid": true
  } 

FAQs

Still unable to integrate, here are the frequently asked questions about the process.

Issue Meaning
No IPN Response Check if your IPN endpoint has an error and you are able to connect to it via the internet.
Invalid Hash Error Please review the secure hash section and verify that your data string conforms to the documentation. Confirm that the credentials you are using are valid.
502 Error Possible maintainance on-going. Wait a few minutes to retry

Errors

The Pesaflow API uses the following HTTP error codes:

Error Code Meaning
200 Valid Request -- Your request is valid.
400 Invalid Request -- Your request is invalid.
401 Unauthorized/Invalid secure hash -- Your API key is wrong.
403 Forbidden Access
404 Item not found or does not exist -- The item (e.g) does not exist.
500 Internal Server Error -- We had a problem with our server. Try again later.
502 Bad Gateway - Something went wrong.
503 Service Unavailable -- We're temporarily offline for maintenance. Please try again later.