BrifNet M-Pesa Gateway
WordPress M-Pesa payment gateway with STK Push, C2B and webhook delivery
by Denis Irungu | CEO BrifNet Technologies LTD · github.com/denno88/brifnet-mpesa-gateway · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/denno88/brifnet-mpesa-gateway/archive/refs/heads/main.zipReadme
BrifNet M-Pesa Gateway
A WordPress plugin that provides an application-facing API for initiating M-Pesa STK Push payments and receiving asynchronous payment completion notifications through webhooks.
The gateway separates payment initiation from payment confirmation and provides signed, idempotent webhook delivery for client applications.
What It Does
The gateway allows your application to:
- Initiate an M-Pesa STK Push.
- Register webhook endpoints for payment events.
- Activate and deactivate registered webhook endpoints.
- Receive notifications when payments are completed.
- Verify webhook authenticity using HMAC-SHA256 signatures.
- Handle webhook delivery using an at-least-once delivery model.
- Safely process duplicate webhook deliveries using an event ID.
Requirements
- WordPress
- PHP 8.4+
- Composer
- M-Pesa/Daraja credentials
- A publicly accessible HTTPS URL for webhook delivery
The plugin uses Composer dependencies and should be installed in a WordPress environment where PHP and Composer are available during development or deployment.
Installation
1. Clone or Download the Plugin
Place the plugin inside the WordPress plugins directory:
wp-content/plugins/brifnet-mpesa-gateway
For example:
wp-content/
└── plugins/
└── brifnet-mpesa-gateway/
2. Install PHP Dependencies
From the plugin directory, run:
composer install
This installs the dependencies defined in composer.lock and creates the vendor/ directory.
The vendor/ directory is generated by Composer and should not be committed to the repository.
3. Activate the Plugin
From the WordPress administration dashboard:
Plugins → Installed Plugins → BrifNet M-Pesa Gateway → Activate
The plugin registers its REST API routes when WordPress loads the plugin.
Configuration
The plugin loads M-Pesa credentials from environment variables.
For security, the environment file should be stored outside the WordPress web root.
The current local-development configuration expects the environment file at:
C:/xampp/brifnet-secrets/.env
This location is configured in:
src/WordPress/DarajaConfigLoader.php
The path can be changed to match the deployment environment.
Local Development
Create the secrets directory:
C:\xampp\brifnet-secrets
Then create:
C:\xampp\brifnet-secrets\.env
Add the required configuration:
# M-Pesa / Daraja credentials
DARAJA_CONSUMER_KEY=
DARAJA_CONSUMER_SECRET=
DARAJA_BUSINESS_SHORTCODE=
DARAJA_PASSKEY=
# Daraja API endpoints
DARAJA_AUTH_URL=https://sandbox.safaricom.co.ke/oauth/v1/generate
DARAJA_STK_PUSH_URL=https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest
DARAJA_STK_QUERY_URL=https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query
# Public URL that receives M-Pesa callbacks
DARAJA_CALLBACK_URL=
Replace the empty values with your Daraja credentials.
For production, use the appropriate production Daraja credentials and production API endpoints.
Required Environment Variables
| Variable | Description |
|---|---|
DARAJA_CONSUMER_KEY |
Daraja consumer key |
DARAJA_CONSUMER_SECRET |
Daraja consumer secret |
DARAJA_BUSINESS_SHORTCODE |
M-Pesa business shortcode |
DARAJA_PASSKEY |
M-Pesa passkey |
DARAJA_AUTH_URL |
Daraja OAuth endpoint |
DARAJA_STK_PUSH_URL |
Daraja STK Push endpoint |
DARAJA_STK_QUERY_URL |
Daraja STK Query endpoint |
DARAJA_CALLBACK_URL |
Public URL used for M-Pesa callbacks |
Daraja Callback URLs
The plugin provides the callback endpoints used to receive payment notifications from Safaricom Daraja.
These routes are created automatically by the plugin. Clients do not need to create separate WordPress routes.
For a WordPress site running at:
https://example.com
the callback URLs are:
STK Push Callback
https://example.com/wp-json/brifnet/v1/mpesa/callback
This URL receives the asynchronous result of an STK Push request from Safaricom Daraja.
Set this URL as the callback URL when configuring the STK Push integration.
The URL used by the plugin is also configured through:
DARAJA_CALLBACK_URL=https://example.com/wp-json/brifnet/v1/mpesa/callback
C2B Callback
https://example.com/wp-json/brifnet/v1/mpesa/c2b
This endpoint receives C2B payment notifications from Safaricom Daraja.
Use this URL when registering the C2B confirmation and validation URLs required by the Daraja C2B configuration.
Current Registration Process
At this stage, the plugin does not register the callback URLs with Safaricom automatically.
The URLs are available from the plugin's REST routes and must currently be entered manually when setting up the corresponding Daraja integration.
For example:
STK Callback: https://example.com/wp-json/brifnet/v1/mpesa/callback
C2B: https://example.com/wp-json/brifnet/v1/mpesa/c2b
The planned implementation will provide an API for registering the required URLs with Daraja directly from the plugin.
Until that API is implemented, Daraja URL registration remains a manual setup step.
Callback Requirements
The WordPress site receiving Daraja callbacks must be publicly accessible.
For production use:
- Use HTTPS.
- Do not require normal WordPress user authentication on these callback routes.
- Make sure the URLs are reachable from Safaricom's infrastructure.
- Do not use
localhostfor Daraja callbacks.
The plugin will throw a configuration error if a required environment variable is missing or empty.
Webhook Secret
Webhook deliveries are signed using HMAC-SHA256.
The client application and the gateway must therefore share a secret used to generate and verify the webhook signature.
The secret should be stored securely outside the web root and must never be committed to Git.
Security
Never commit real credentials or webhook secrets to Git.
Do not place production credentials in:
README.md- Source code
- JavaScript files
- Public WordPress directories
- GitHub
- Screenshots
- Example configuration files
Use .env.example as a template only. It must contain placeholders and must not contain real credentials.
Production secrets should be supplied through environment-specific configuration or a secure secrets-management mechanism.
API Base URL
The public API is exposed through the WordPress REST API:
https://your-domain.com/wp-json/brifnet/v1
Replace your-domain.com with your WordPress site's domain.
1. Initiate an STK Push
Initiates an M-Pesa STK Push request.
POST /wp-json/brifnet/v1/mpesa/stk
Content-Type: application/json
Request
{
"reference": "PAY-CB-003",
"phone": "0729633304",
"amount": 1
}
Fields
| Field | Type | Description |
|---|---|---|
reference |
string | Unique payment reference supplied by the client application |
phone |
string | Customer's Kenyan phone number |
amount |
integer | Amount to request from the customer |
The phone number must use the Kenyan local format:
07XXXXXXXX
The amount must be a positive integer.
The payment reference represents the business payment identity supplied by the client application.
Successful Response
If the STK Push is accepted for processing:
200 OK
{
"message": "STK Push initiated.",
"merchant_request_id": "3b8d-40c5-817d-9beb9d179fd433836",
"checkout_request_id": "ws_CO_050920260057144729633304"
}
The customer should then receive the M-Pesa payment prompt on their phone.
The checkout_request_id and merchant_request_id identify the STK request.
Important: A successful STK Push response only confirms that the request was accepted for processing. It does not mean that the customer has successfully paid.
Payment completion is communicated asynchronously through the payment.completed webhook.
Possible Errors
Invalid Request
400 Bad Request
{
"message": "Invalid request."
}
M-Pesa Rejected the Request
502 Bad Gateway
{
"message": "M-Pesa rejected the STK Push.",
"error": "..."
}
Internal Error
500 Internal Server Error
{
"message": "Unable to initiate payment."
}
2. Webhooks
Webhooks allow client applications to receive asynchronous notifications when payment events occur.
A webhook endpoint can subscribe to one or more payment events.
Webhook Events
Currently Supported Events
| Event | Description |
|---|---|
payment.completed |
Fired when a payment has been successfully completed and recorded by the payment domain. |
At present, payment.completed is the only supported webhook event.
Planned Payment Events
The webhook architecture is designed to support additional payment lifecycle events as the payment domain grows.
Potential future events include:
| Event | Description |
|---|---|
payment.failed |
Payment processing definitively failed. |
payment.cancelled |
Payment was cancelled before completion. |
payment.reversed |
A previously completed payment was reversed. |
These events are not currently supported and should not be registered until their corresponding domain events and delivery workflows are implemented.
3. Register a Webhook
Register a webhook endpoint for one or more supported payment events.
POST /wp-json/brifnet/v1/webhooks
Content-Type: application/json
Request
{
"url": "https://example.com/webhook",
"events": [
"payment.completed"
]
}
The events field is an array, allowing an endpoint to subscribe to multiple supported events.
Successful Response
200 OK
{
"url": "https://example.com/webhook",
"active": true,
"events": [
"payment.completed"
]
}
Newly registered webhook endpoints are active immediately.
4. Deactivate a Webhook
A webhook endpoint can be temporarily disabled without deleting its registration.
POST /wp-json/brifnet/v1/webhooks/deactivate
Content-Type: application/json
Request
{
"url": "https://example.com/webhook"
}
Successful Response
{
"url": "https://example.com/webhook",
"active": false,
"events": [
"payment.completed"
]
}
A deactivated endpoint remains registered but will not receive webhook deliveries.
5. Activate a Webhook
A previously deactivated endpoint can be activated again.
POST /wp-json/brifnet/v1/webhooks/activate
Content-Type: application/json
Request
{
"url": "https://example.com/webhook"
}
Successful Response
{
"url": "https://example.com/webhook",
"active": true,
"events": [
"payment.completed"
]
}
6. Webhook Registration Errors
Invalid JSON
400 Bad Request
{
"message": "Invalid JSON payload."
}
Events Must Be an Array
{
"message": "Webhook events must be an array."
}
Event Names Must Be Strings
{
"message": "Webhook event names must be strings."
}
Invalid Webhook URL
{
"message": "Webhook URL is invalid."
}
Empty Webhook URL
{
"message": "Webhook URL cannot be empty."
}
Duplicate Webhook
{
"message": "Webhook endpoint already exists."
}
7. Payment Completed Webhook
After a payment is successfully completed, BrifNet sends an HTTP POST request to every active registered endpoint subscribed to payment.completed.
Example request:
POST https://example.com/webhook
Content-Type: application/json
X-Webhook-Signature: <signature>
Webhook Payload
{
"event_id": "evt-123",
"event": "payment.completed",
"occurred_at": "2026-09-04T12:30:00+03:00",
"data": {
"reference": "PAY-123",
"phone": "0712345678",
"amount": 500,
"channel": "STK"
}
}
Payload Fields
| Field | Description |
|---|---|
event_id |
Unique identifier for the payment event |
event |
Event name |
occurred_at |
Time the event occurred |
data.reference |
Payment reference supplied when initiating the payment |
data.phone |
Customer phone number |
data.amount |
Payment amount |
data.channel |
Payment channel |
For an STK payment, the channel is:
STK