Skip to content

M-Pesa Reconciliation

M-Pesa callbacks can be missed due to network issues, server downtime, or Safaricom delays. The reconciliation system ensures no payment is lost.

Three Recovery Mechanisms

flowchart TB
    subgraph "1. Transaction Status Query"
        A["Merchant notices missing callback"] --> B["POST /v1/payments/mpesa/check-status/{code}"]
        B --> C["Daraja sends result to callback URL"]
        C --> D["DB updated + merchant webhook sent"]
    end

    subgraph "2. Manual Reconciliation"
        E["Merchant triggers"] --> F["POST /v1/payments/mpesa/reconcile"]
        F --> G["Pull last 2h of transactions from Daraja"]
        G --> H["Sync pending → completed"]
    end

    subgraph "3. Automatic Reconciliation"
        I["ARQ cron every 15 min"] --> J["Pull last 2h from Daraja"]
        J --> K["Auto-sync missed transactions"]
    end

1. Transaction Status Query

For a specific transaction where you have the M-Pesa receipt number:

POST /v1/payments/mpesa/check-status/NEF61H8J60
X-API-Key: sk_live_...

This is async — Daraja sends the result to your configured result_url. The response only confirms the query was accepted.

2. Manual Reconciliation

Pull all missed transactions at once:

One-time setup

POST /v1/payments/mpesa/register-pull
X-API-Key: sk_live_...

Register once

This registers your shortcode with Daraja's Pull API. Only needs to be done once.

Trigger reconciliation

POST /v1/payments/mpesa/reconcile
X-API-Key: sk_live_...

Response

{
  "success": true,
  "data": {
    "synced": 3,
    "skipped": 47,
    "not_found": 1
  }
}
Field Meaning
synced Transactions updated from pendingcompleted
skipped Already completed in our DB
not_found In Daraja but not in our DB (different shortcode usage)

3. Automatic Reconciliation

An ARQ cron job runs every 15 minutes automatically:

  • Pulls the last 2 hours of transactions from Daraja
  • Compares against our DB
  • Updates any pending transactions to completed
  • Enqueues payment.completed webhooks with "reconciled": true flag

No action needed from the merchant — this runs in the background.

Reconciled Webhook Payload

When a transaction is synced via reconciliation, the outbound webhook includes a reconciled flag:

{
  "type": "payment.completed",
  "data": {
    "payment_id": "txn-uuid-...",
    "amount": "500",
    "currency": "KES",
    "provider": "mpesa",
    "status": "completed",
    "previous_status": "pending",
    "receipt_no": "NEF61H8J60",
    "reconciled": true
  }
}

When to Use What

Scenario Mechanism
Single missing callback Transaction Status Query
Server was down for a while Manual Reconciliation
Ongoing safety net Automatic (runs by itself)