This implementation provides a complete, production-ready M-Pesa STK Push integration with enterprise-grade security features, comprehensive callback validation, print-friendly receipts, and Cloudflare tunnel support. Built with Django and optimized for high-traffic scenarios using Gunicorn.
- 🔒 Enterprise Security: Multi-layer callback validation with IP whitelisting and HMAC verification
- 🖨️ Print-Friendly Receipts: Clean, professional PDF-ready transaction receipts
- ☁️ Cloudflare Ready: Optimized for Cloudflare tunnel deployments
- 🚀 Production Ready: Gunicorn WSGI server with optimized configuration
- 📊 Comprehensive Logging: Structured security logging with data sanitization
- 🛡️ Input Validation: XSS and injection attack prevention
- 📱 Responsive Design: Mobile-first UI with Bootstrap 5
- 🔍 Transaction Tracking: Complete audit trail for all payments
- Installation
- Configuration
- Security Features
- API Documentation
- Usage Examples
- Deployment
- Testing
- Contributing
- License
- Authors
- Disclaimer
- Python 3.8+
- Django 5.2+
- PostgreSQL/MySQL (recommended) or SQLite (development)
- Redis (for production caching)
- SSL certificate (for production)
-
Clone the repository
git clone https://github.com/ngemuantony/mpesa.git cd mpesa -
Create virtual environment
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
-
Install dependencies
pip install -r requirements.txt
-
Environment configuration
cp .env.example .env # Edit .env with your M-Pesa credentials -
Database setup
python manage.py migrate python manage.py collectstatic
-
Run development server
./start_dev.sh
For production deployment with Gunicorn:
# Production setup
./start_prod.sh
# Or with systemd service
sudo cp mpesa-gunicorn.service /etc/systemd/system/
sudo systemctl enable mpesa-gunicorn
sudo systemctl start mpesa-gunicornCreate a .env file in the project root:
# Django Configuration
DEBUG=False
SECRET_KEY=your-super-secret-key-here
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
# Database (Production)
DATABASE_URL=postgresql://user:password@localhost/dbname
# M-Pesa Configuration
MPESA_CONSUMER_KEY=your_consumer_key
MPESA_CONSUMER_SECRET=your_consumer_secret
MPESA_SHORTCODE=your_shortcode
MPESA_PASSKEY=your_passkey
MPESA_CALLBACK_URL=https://yourdomain.com/payments/callback/
# Cloudflare Tunnel Support
CLOUDFLARE_TUNNEL=True
# Security
MPESA_HMAC_SECRET=your-callback-hmac-secret
ENABLE_CALLBACK_SECURITY=TrueThe system implements comprehensive security for M-Pesa callbacks:
- IP Whitelisting: Validates requests against official Safaricom IP addresses
- HMAC Signature Validation: Cryptographic validation of callback authenticity
- Structure Validation: Ensures callback payloads have proper format and required fields
- Rate Limiting: Prevents callback flooding and abuse
- Comprehensive Logging: Detailed security event logging and monitoring
To configure enhanced callback security in your settings:
# M-Pesa Security Configuration
MPESA_SECURITY = {
'ENABLE_HMAC_VALIDATION': True, # Enable HMAC signature validation
'ENABLE_STRUCTURE_VALIDATION': True, # Enable payload structure validation
'HMAC_SECRET_KEY': 'your-secret-key', # HMAC secret (defaults to Django SECRET_KEY)
'CALLBACK_TIMEOUT': 300, # Callback timeout in seconds (5 minutes)
'MAX_CALLBACK_RATE': 100, # Max callbacks per minute per IP
}from mpesa.callback_security import EnhancedCallbackSecurity
# Initialize security system
security = EnhancedCallbackSecurity(
enable_hmac=True,
enable_structure_validation=True
)
# Validate incoming callback
result = security.validate_callback(request, view)
if result['overall_status'] == 'approved':
# Process callback
process_mpesa_callback(request.data)
else:
# Reject callback
return Response({'error': 'Security validation failed'}, status=403)Endpoint: POST /payments/checkout/
Request Body:
{
"phone_number": "0718643064",
"amount": 100,
"reference": "INV-2024-001",
"description": "Payment for services"
}Response:
{
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResponseCode": "0",
"ResponseDescription": "Success. Request accepted for processing",
"CustomerMessage": "Success. Request accepted for processing"
}Endpoint: POST /payments/query/
Request Body:
{
"checkout_request_id": "ws_CO_191220191020363925"
}Response:
{
"ResponseCode": "0",
"ResponseDescription": "The service request has been accepted successfully",
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResultCode": "0",
"ResultDesc": "The service request is processed successfully.",
"local_transaction": {
"transaction_no": "432930cf-222d-421e-9151-bc4b7b296f9c",
"phone_number": "+254718643064",
"amount": "100",
"status": "0",
"receipt_no": "NLJ7RT61SV",
"created": "2025-08-04T02:05:13.577718Z"
}
}Endpoint: POST /payments/callback/
This endpoint receives callbacks from Safaricom and is IP-whitelisted for security.
- Initiate Payment: Client calls
/payments/checkout/ - STK Push Sent: User receives STK push on their phone
- User Responds: User enters PIN or cancels
- Callback Received: Safaricom sends callback to
/payments/callback/ - Transaction Updated: Local database is updated with final status
- Query Status: Client can query status using
/payments/query/
0: Complete/Successful1: Pending/Failed
0718643064→ Converted to254718643064+254718643064→ Converted to254718643064254718643064→ Remains as is
- IP whitelisting for callback endpoint (Safaricom IPs only)
- CSRF protection
- Input validation and sanitization
- Error logging and monitoring
# Initiate payment
curl -X POST http://127.0.0.1:8000/payments/checkout/ \
-H "Content-Type: application/json" \
-d '{
"phone_number": "0718643064",
"amount": 100,
"reference": "INV-2024-001",
"description": "Payment for services"
}'
# Query payment status
curl -X POST http://127.0.0.1:8000/payments/query/ \
-H "Content-Type: application/json" \
-d '{
"checkout_request_id": "ws_CO_191220191020363925"
}'// Initiate payment
const initiatePayment = async (phoneNumber, amount, reference, description) => {
const response = await fetch('/payments/checkout/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone_number: phoneNumber,
amount: amount,
reference: reference,
description: description
})
});
return await response.json();
};
// Query payment status
const queryPayment = async (checkoutRequestId) => {
const response = await fetch('/payments/query/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
checkout_request_id: checkoutRequestId
})
});
return await response.json();
};consumer_key=your_consumer_key
consumer_secret=your_consumer_secret
shortcode=your_shortcode
pass_key=your_passkey
access_token_url=https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials
checkout_url=https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest
mpesa_query_check_url=https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query
c2b_callback=https://yourdomain.com/payments/callback/The system handles various error scenarios:
- Invalid phone numbers
- Network timeouts
- Invalid access tokens
- Callback validation failures
- Database errors
All errors are logged for debugging and monitoring purposes.
# Using Gunicorn with systemd
sudo cp mpesa-gunicorn.service /etc/systemd/system/
sudo systemctl enable mpesa-gunicorn
sudo systemctl start mpesa-gunicorn
sudo systemctl status mpesa-gunicornFROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--config", "gunicorn.conf.py", "config.wsgi:application"]# Install cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb
# Create tunnel
cloudflared tunnel create mpesa-tunnel
cloudflared tunnel route dns mpesa-tunnel yourdomain.com
# Run tunnel
cloudflared tunnel --config config.yml run- Gunicorn Workers: Configured based on CPU cores (2 * cores + 1)
- Database Connection Pooling: Optimized for high-traffic scenarios
- Static File Serving: Nginx recommended for production
- Caching: Redis integration for session and query caching
# Unit tests
python manage.py test mpesa
# Security tests
python test_security.py
# Integration tests
python test_integration.py
# Print functionality tests
python test_print.py# Install coverage
pip install coverage
# Run with coverage
coverage run --source='.' manage.py test mpesa
coverage report -m
coverage htmlUse the provided curl commands or test scripts:
# Test payment initiation
./test_payment.sh
# Test callback security
./test_callback_security.sh
# Test print functionality
./test_print_receipts.shWe welcome contributions! Please follow these guidelines:
-
Fork the repository
git clone https://github.com/yourusername/mpesa.git cd mpesa -
Create feature branch
git checkout -b feature/your-feature-name
-
Install development dependencies
pip install -r requirements-dev.txt
-
Pre-commit hooks
pre-commit install
- Python: Follow PEP 8 style guidelines
- Documentation: Update README.md for new features
- Testing: Write tests for all new functionality
- Security: Follow OWASP security practices
- Logging: Use structured logging for all operations
- Update documentation
- Add/update tests
- Ensure all tests pass
- Update CHANGELOG.md
- Submit pull request with detailed description
- Code follows project style guidelines
- Tests cover new functionality
- Documentation is updated
- Security implications considered
- Performance impact assessed
- Backward compatibility maintained
This project is licensed under the MIT License - see below for details:
MIT License
Copyright (c) 2025 Anthony Ngemu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Anthony Ngemu
- GitHub: @ngemuantony
- Email: ngemuantony@gmail.com
- LinkedIn: Anthony Ngemu
- Special thanks to all contributors who have helped improve this project
- Django and Python community for excellent frameworks and libraries
- Safaricom for the M-Pesa API documentation and support
- Django Framework: Robust web framework for rapid development
- Gunicorn: Reliable Python WSGI HTTP Server
- Bootstrap: Responsive front-end framework
- Cloudflare: CDN and security services
- Safaricom: M-Pesa API and payment gateway
This software is provided for educational and development purposes. Please note:
- This implementation is designed for learning and development
- Thoroughly test in sandbox environment before production deployment
- Implement additional security measures as required by your use case
- Regular security audits and updates are recommended
- Users are responsible for compliance with financial regulations
- Implement proper transaction logging and audit trails
- Ensure PCI DSS compliance for card data handling
- Follow local banking and payment processing regulations
- Comply with Safaricom's M-Pesa API terms of service
- Respect rate limits and usage guidelines
- Implement proper error handling and retry mechanisms
- Monitor API usage and costs
- Regularly update dependencies for security patches
- Implement proper access controls and authentication
- Use HTTPS in production environments
- Sanitize and validate all user inputs
- Follow OWASP security guidelines
For support and questions:
- Create an issue on GitHub
- Check existing documentation and FAQ
- Review test files for usage examples
- Join community discussions
See CHANGELOG.md for version history and updates.
Built with ❤️ by Anthony Ngemu
Making M-Pesa integration simple, secure, and scalable.