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

MethodBest ForLatency
PollingSimple integrations, on-demand status checksDepends on poll interval
WebhooksServer-to-server notifications, background processingNear real-time (seconds)
WebSocketReal-time UI updates, customer-facing status pagesInstant (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 seconds
4
5 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 );
14
15 const result = await response.json();
16 const status = result.data?.status;
17
18 if (status === 'COMPLETED') {
19 return { success: true, data: result.data };
20 }
21
22 if (status === 'FAILED') {
23 return {
24 success: false,
25 error: result.data?.failureReason || 'Payment failed',
26 };
27 }
28
29 // Still pending - wait and retry
30 await new Promise(resolve => setTimeout(resolve, delay));
31 attempts++;
32 delay = Math.min(delay * 1.5, 10000); // Max 10 second delay
33 }
34
35 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

EventWhen Triggered
deposit.initiatedSTK Push sent to customer's phone
deposit.completedCustomer completed payment, funds credited
deposit.failedPayment 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

Endpointwss://api.wasaapay.com/partner/socket
Sandboxwss://sandbox.wasaapay.com/partner/socket
ProtocolSocket.io (WebSocket with fallback to polling)
AuthenticationAPI key in connection auth or headers

Connecting with Socket.io Client

websocket-client.js
1import { io } from 'socket.io-client';
2
3// Connect to WasaaPay WebSocket
4const 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});
11
12// Connection events
13socket.on('connect', () => {
14 console.log('Connected to WasaaPay real-time service');
15});
16
17socket.on('authenticated', (data) => {
18 console.log('Authenticated:', data.partnerName);
19 console.log('Environment:', data.environment);
20});
21
22socket.on('connect_error', (error) => {
23 console.error('Connection error:', error.message);
24});
25
26// Subscribe to a specific deposit
27socket.emit('subscribe', {
28 type: 'deposit',
29 id: 'dep_xyz789',
30});
31
32socket.on('subscribed', (data) => {
33 console.log('Subscribed to', data.type, data.id);
34});
35
36// Listen for deposit status events
37socket.on('deposit:completed', (payload) => {
38 console.log('Deposit completed!', payload.data);
39 // Update UI, redirect customer, etc.
40});
41
42socket.on('deposit:failed', (payload) => {
43 console.log('Deposit failed:', payload.data.failureReason);
44 // Show error message to customer
45});
46
47// Clean up on unmount
48socket.on('disconnect', (reason) => {
49 console.log('Disconnected:', reason);
50});

WebSocket Events

EventDescription
authenticatedSuccessfully connected and authenticated
subscribedSuccessfully subscribed to a resource
deposit:initiatedDeposit request created
deposit:completedDeposit completed successfully
deposit:failedDeposit failed
withdrawal:completedWithdrawal completed
withdrawal:failedWithdrawal 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';
3
4interface DepositStatus {
5 status: 'PENDING' | 'COMPLETED' | 'FAILED';
6 receiptNumber?: string;
7 failureReason?: string;
8}
9
10export function useDepositStatus(depositId: string, apiKey: string) {
11 const [status, setStatus] = useState<DepositStatus>({ status: 'PENDING' });
12 const [connected, setConnected] = useState(false);
13
14 useEffect(() => {
15 const socket: Socket = io('wss://sandbox.wasaapay.com', {
16 path: '/partner/socket',
17 auth: { apiKey },
18 });
19
20 socket.on('connect', () => setConnected(true));
21 socket.on('disconnect', () => setConnected(false));
22
23 socket.on('authenticated', () => {
24 socket.emit('subscribe', { type: 'deposit', id: depositId });
25 });
26
27 socket.on('deposit:completed', (payload) => {
28 setStatus({
29 status: 'COMPLETED',
30 receiptNumber: payload.data.receiptNumber,
31 });
32 });
33
34 socket.on('deposit:failed', (payload) => {
35 setStatus({
36 status: 'FAILED',
37 failureReason: payload.data.failureReason,
38 });
39 });
40
41 return () => {
42 socket.disconnect();
43 };
44 }, [depositId, apiKey]);
45
46 return { ...status, connected };
47}
48
49// Usage in component
50function PaymentStatusPage({ depositId }) {
51 const { status, connected, receiptNumber, failureReason } = useDepositStatus(
52 depositId,
53 process.env.NEXT_PUBLIC_WASAAPAY_API_KEY
54 );
55
56 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:

  1. Initiate deposit and get deposit ID
  2. Connect WebSocket for real-time UI updates
  3. Configure webhooks for reliable backend processing
  4. Use polling as fallback if WebSocket disconnects
combined-tracking.js
1async function initiatePaymentWithTracking(walletId, amount, phone) {
2 // 1. Create the deposit
3 const deposit = await createMpesaDeposit(walletId, amount, phone);
4
5 // 2. Connect WebSocket for real-time updates
6 const socket = connectToWebSocket();
7 socket.emit('subscribe', { type: 'deposit', id: deposit.id });
8
9 // 3. Set up polling fallback
10 const pollInterval = setInterval(async () => {
11 if (socket.connected) return; // Skip if WebSocket is working
12
13 const status = await checkDepositStatus(deposit.id);
14 if (status === 'COMPLETED' || status === 'FAILED') {
15 clearInterval(pollInterval);
16 handleStatusUpdate(status);
17 }
18 }, 5000);
19
20 // 4. Listen for WebSocket events
21 socket.on('deposit:completed', (data) => {
22 clearInterval(pollInterval);
23 handleStatusUpdate('COMPLETED', data);
24 });
25
26 socket.on('deposit:failed', (data) => {
27 clearInterval(pollInterval);
28 handleStatusUpdate('FAILED', data);
29 });
30
31 // 5. Set overall timeout (STK Push expires in 2 min)
32 setTimeout(() => {
33 clearInterval(pollInterval);
34 socket.disconnect();
35 }, 130000); // 2 min 10 sec
36
37 return deposit;
38}