DEV Community

Sospeter Mong'are
Sospeter Mong'are

Posted on

How to Do Polling Requests to an API in JavaScript (with MPESA STK Push Example)

When you integrate payments like MPESA Daraja STK Push, the customer confirms the payment on their phone. The result (success or failure) is not immediate - Safaricom will notify your server via a callback URL.

But what about the user who is waiting on your web app? 🤔
This is where polling comes in.


🔑 Recap: MPESA STK Push Flow

  1. User clicks Pay Now.
  2. Your backend calls the Daraja STK Push API → Safaricom sends an STK prompt to the user’s phone.
  3. Backend saves the transaction in DB with status = PENDING.
  4. Safaricom calls your /callback once the user accepts/declines. Backend updates status → SUCCESS or FAILED.
  5. Frontend polls the backend every few seconds until the status changes from PENDING.

🖥️ Example Backend (Django / Node.js / Laravel – concept is same)

Pseudo-code:

# /stk-push endpoint (called when user clicks Pay Now)
def stk_push(request):
    # Call MPESA STK Push API here...
    tx = Transaction.objects.create(status="PENDING")
    return JsonResponse({"transaction_id": tx.id})

# /callback (Safaricom will call this)
def callback(request):
    data = json.loads(request.body)
    tx_id = data["MerchantRequestID"]  # example unique ID
    result_code = data["ResultCode"]

    status = "SUCCESS" if result_code == 0 else "FAILED"
    Transaction.objects.filter(id=tx_id).update(status=status)
    return JsonResponse({"Result": "OK"})

# /check-status (frontend polls this)
def check_status(request):
    tx_id = request.GET.get("tx_id")
    tx = Transaction.objects.get(id=tx_id)
    return JsonResponse({"status": tx.status})
Enter fullscreen mode Exit fullscreen mode

🌐 Frontend (JavaScript Polling)

<!DOCTYPE html>
<html>
<head>
  <title>MPESA STK Push Example</title>
</head>
<body>
  <button onclick="initiatePayment()">Pay with MPESA</button>
  <p id="statusMsg"></p>

  <script>
    function initiatePayment() {
      // Step 1: Call backend to trigger STK Push
      fetch("/stk-push", { method: "POST" })
        .then(res => res.json())
        .then(data => {
          const txId = data.transaction_id;
          document.getElementById("statusMsg").innerText = "STK Push sent. Enter your MPESA PIN...";
          pollStatus(txId); // start polling
        })
        .catch(err => console.error("Error:", err));
    }

    function pollStatus(txId) {
      // Step 2: Keep checking every 3s
      const interval = setInterval(() => {
        fetch(`/check-status?tx_id=${txId}`)
          .then(res => res.json())
          .then(data => {
            console.log("Payment Status:", data.status);

            if (data.status !== "PENDING") {
              clearInterval(interval);
              document.getElementById("statusMsg").innerText = "Payment " + data.status;
            }
          })
          .catch(err => console.error("Error:", err));
      }, 3000); // every 3 seconds
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

🔄 What Happens in Practice

  1. User clicks Pay with MPESA → STK push request sent.
  2. User enters MPESA PIN on phone.
  3. Frontend keeps showing “Waiting for confirmation…” and polls backend.
  4. Safaricom calls your /callback with result.
  5. Backend updates DB → SUCCESS or FAILED.
  6. Next poll request sees the new status → frontend updates instantly.

⚠️ Notes for Beginners

  • Always use a database (MySQL, Postgres, MongoDB) to store transaction status.
  • Don’t keep the frontend waiting forever → you can set a maximum wait time (e.g. 1 minute).
  • Polling is simple but not the most efficient → later you can upgrade to WebSockets for real-time push notifications.

Summary

  • MPESA STK Push is asynchronous → you don’t get instant results.
  • Polling lets your frontend “wait” while the user enters MPESA PIN.
  • Use /stk-push to initiate, /callback to receive result, and /check-status to poll for updates.

Top comments (0)