Payment Status Tracking
WasaaPay provides three ways to track payment status: polling endpoints, webhooks, and real-time WebSocket connections. Choose the method that best fits your use case.
Overview
| Method | Best For | Latency |
|---|---|---|
| Polling | Simple integrations, on-demand status checks | Depends on poll interval |
| Webhooks | Server-to-server notifications, background processing | Near real-time (seconds) |
| WebSocket | Real-time UI updates, customer-facing status pages | Instant (milliseconds) |
Method 1: Polling
Use the GET endpoints to check payment status on-demand. This is the simplest approach but adds latency since you must periodically check.
Get Deposit Status
Polling Best Practices
- Use exponential backoff - Start with 2-second intervals, increase to 5s, 10s, etc.
- Set a timeout - STK Push expires in 2 minutes; stop polling after that
- Check status transitions - PENDING → COMPLETED or PENDING → FAILED
polling-example.js
1async function pollDepositStatus(depositId, maxAttempts = 30) {2 let attempts = 0;3 let delay = 2000; // Start with 2 seconds45 while (attempts < maxAttempts) {6 const response = await fetch(7 `https://api.wasaapay.com/api/v1/partner/deposits/${depositId}`,8 {9 headers: {10 'X-API-Key': process.env.WASAAPAY_API_KEY,11 },12 }13 );1415 const result = await response.json();16 const status = result.data?.status;1718 if (status === 'COMPLETED') {19 return { success: true, data: result.data };20 }2122 if (status === 'FAILED') {23 return {24 success: false,25 error: result.data?.failureReason || 'Payment failed',26 };27 }2829 // Still pending - wait and retry30 await new Promise(resolve => setTimeout(resolve, delay));31 attempts++;32 delay = Math.min(delay * 1.5, 10000); // Max 10 second delay33 }3435 return { success: false, error: 'Timeout waiting for payment' };36}
Method 2: Webhooks
Webhooks push status updates to your server as they happen. This is more efficient than polling and ideal for server-side processing.
See the Webhooks documentation for setup details.
Webhook Events for Deposits
| Event | When Triggered |
|---|---|
deposit.initiated | STK Push sent to customer's phone |
deposit.completed | Customer completed payment, funds credited |
deposit.failed | Payment failed, cancelled, or timed out |
Webhook Payload Example
webhook-payload.json
{"event": "deposit.completed","timestamp": "2024-01-15T10:30:45Z","partnerId": "partner_abc123","environment": "SANDBOX","signature": "sha256=a1b2c3d4e5f6...","data": {"depositId": "dep_xyz789","reference": "DEP-20240115-ABC123","externalReference": "ORDER-001","amount": "1000.00","status": "COMPLETED","provider": "MPESA","receiptNumber": "QKJ7Y8H2X9"}}
Method 3: WebSocket (Real-Time)
For instant status updates in customer-facing applications, connect to our WebSocket service. This provides sub-second latency for status changes.
Connection Details
| Endpoint | wss://api.wasaapay.com/partner/socket |
| Sandbox | wss://sandbox.wasaapay.com/partner/socket |
| Protocol | Socket.io (WebSocket with fallback to polling) |
| Authentication | API key in connection auth or headers |
Connecting with Socket.io Client
websocket-client.js
1import { io } from 'socket.io-client';23// Connect to WasaaPay WebSocket4const socket = io('wss://sandbox.wasaapay.com', {5 path: '/partner/socket',6 transports: ['websocket', 'polling'],7 auth: {8 apiKey: 'ppk_sandbox_your_api_key_here',9 },10});1112// Connection events13socket.on('connect', () => {14 console.log('Connected to WasaaPay real-time service');15});1617socket.on('authenticated', (data) => {18 console.log('Authenticated:', data.partnerName);19 console.log('Environment:', data.environment);20});2122socket.on('connect_error', (error) => {23 console.error('Connection error:', error.message);24});2526// Subscribe to a specific deposit27socket.emit('subscribe', {28 type: 'deposit',29 id: 'dep_xyz789',30});3132socket.on('subscribed', (data) => {33 console.log('Subscribed to', data.type, data.id);34});3536// Listen for deposit status events37socket.on('deposit:completed', (payload) => {38 console.log('Deposit completed!', payload.data);39 // Update UI, redirect customer, etc.40});4142socket.on('deposit:failed', (payload) => {43 console.log('Deposit failed:', payload.data.failureReason);44 // Show error message to customer45});4647// Clean up on unmount48socket.on('disconnect', (reason) => {49 console.log('Disconnected:', reason);50});
WebSocket Events
| Event | Description |
|---|---|
authenticated | Successfully connected and authenticated |
subscribed | Successfully subscribed to a resource |
deposit:initiated | Deposit request created |
deposit:completed | Deposit completed successfully |
deposit:failed | Deposit failed |
withdrawal:completed | Withdrawal completed |
withdrawal:failed | Withdrawal failed |
Event Payload Structure
websocket-event-payload.json
{"event": "deposit:completed","timestamp": "2024-01-15T10:30:45.123Z","data": {"id": "dep_xyz789","reference": "DEP-20240115-ABC123","externalReference": "ORDER-001","walletId": "wallet_abc123","amount": "1000.00","fee": "0.00","currency": "KES","status": "COMPLETED","provider": "MPESA","method": "MOBILE_MONEY","phoneNumber": "+254712345678","providerReference": "ws_CO_15012024103000123456","receiptNumber": "QKJ7Y8H2X9","initiatedAt": "2024-01-15T10:30:00.000Z","completedAt": "2024-01-15T10:30:45.123Z"}}
React Hook Example
use-deposit-status.tsx
1import { useEffect, useState } from 'react';2import { io, Socket } from 'socket.io-client';34interface DepositStatus {5 status: 'PENDING' | 'COMPLETED' | 'FAILED';6 receiptNumber?: string;7 failureReason?: string;8}910export function useDepositStatus(depositId: string, apiKey: string) {11 const [status, setStatus] = useState<DepositStatus>({ status: 'PENDING' });12 const [connected, setConnected] = useState(false);1314 useEffect(() => {15 const socket: Socket = io('wss://sandbox.wasaapay.com', {16 path: '/partner/socket',17 auth: { apiKey },18 });1920 socket.on('connect', () => setConnected(true));21 socket.on('disconnect', () => setConnected(false));2223 socket.on('authenticated', () => {24 socket.emit('subscribe', { type: 'deposit', id: depositId });25 });2627 socket.on('deposit:completed', (payload) => {28 setStatus({29 status: 'COMPLETED',30 receiptNumber: payload.data.receiptNumber,31 });32 });3334 socket.on('deposit:failed', (payload) => {35 setStatus({36 status: 'FAILED',37 failureReason: payload.data.failureReason,38 });39 });4041 return () => {42 socket.disconnect();43 };44 }, [depositId, apiKey]);4546 return { ...status, connected };47}4849// Usage in component50function PaymentStatusPage({ depositId }) {51 const { status, connected, receiptNumber, failureReason } = useDepositStatus(52 depositId,53 process.env.NEXT_PUBLIC_WASAAPAY_API_KEY54 );5556 return (57 <div>58 <p>Connection: {connected ? 'Connected' : 'Connecting...'}</p>59 <p>Status: {status}</p>60 {status === 'COMPLETED' && <p>Receipt: {receiptNumber}</p>}61 {status === 'FAILED' && <p>Error: {failureReason}</p>}62 </div>63 );64}
Choosing the Right Method
Use Polling When:
- Building a simple integration without server infrastructure
- Checking status on-demand (e.g., user clicks "Check Status")
- WebSocket connections aren't practical (firewalls, etc.)
Use Webhooks When:
- Processing payments on your backend
- Updating databases, sending receipts, fulfilling orders
- Need reliable delivery with automatic retries
Use WebSocket When:
- Building customer-facing payment status pages
- Need instant UI updates when payment completes
- Building real-time dashboards or monitoring tools
Combining Methods
For the best user experience, we recommend combining methods:
- Initiate deposit and get deposit ID
- Connect WebSocket for real-time UI updates
- Configure webhooks for reliable backend processing
- Use polling as fallback if WebSocket disconnects
combined-tracking.js
1async function initiatePaymentWithTracking(walletId, amount, phone) {2 // 1. Create the deposit3 const deposit = await createMpesaDeposit(walletId, amount, phone);45 // 2. Connect WebSocket for real-time updates6 const socket = connectToWebSocket();7 socket.emit('subscribe', { type: 'deposit', id: deposit.id });89 // 3. Set up polling fallback10 const pollInterval = setInterval(async () => {11 if (socket.connected) return; // Skip if WebSocket is working1213 const status = await checkDepositStatus(deposit.id);14 if (status === 'COMPLETED' || status === 'FAILED') {15 clearInterval(pollInterval);16 handleStatusUpdate(status);17 }18 }, 5000);1920 // 4. Listen for WebSocket events21 socket.on('deposit:completed', (data) => {22 clearInterval(pollInterval);23 handleStatusUpdate('COMPLETED', data);24 });2526 socket.on('deposit:failed', (data) => {27 clearInterval(pollInterval);28 handleStatusUpdate('FAILED', data);29 });3031 // 5. Set overall timeout (STK Push expires in 2 min)32 setTimeout(() => {33 clearInterval(pollInterval);34 socket.disconnect();35 }, 130000); // 2 min 10 sec3637 return deposit;38}