Daraja SDK Logomark
Daraja SDK
Core Concepts

Error Handling

Every error the SDK throws goes through one class DarajaError.

Every failure a validation error before a request is even sent, a rejected response from Daraja, a network timeout is thrown as a DarajaError. You only need one catch shape across the entire SDK.

export class DarajaError extends Error {
  readonly statusCode: number; // HTTP status, or 0 for client-side validation errors
  readonly errorCode: string; // e.g. "INVALID_STK_REQUEST", "OAUTH_AUTHENTICATION_FAILED"
  readonly endpoint?: string; // which Daraja path was being called, if any
  readonly suggestion?: string; // a human-readable fix, when the SDK knows one
  readonly docUrl?: string;
  readonly rawResponse?: unknown; // Daraja's raw JSON body, for anything not surfaced above
}

Catching errors

import { Daraja, DarajaError } from "@lumierelabs/daraja";

try {
  await daraja.stkPush({
    businessShortCode: "174379",
    passkey: process.env.MPESA_PASSKEY!,
    transactionType: "CustomerPayBillOnline",
    amount: 1,
    partyA: "254708374149",
    partyB: "174379",
    phoneNumber: "254708374149",
    callBackURL: "https://example.com/callbacks/stk",
    accountReference: "INV-1042",
  });
} catch (error) {
  if (error instanceof DarajaError) {
    console.error(error.errorCode, error.message);
    if (error.suggestion) console.error("Suggestion:", error.suggestion);
  } else {
    throw error; // something genuinely unexpected  don't swallow it
  }
}

Two error categories

Client-side validation vs. Daraja rejection

DarajaError is thrown for two different reasons, distinguishable by statusCode:

  • statusCode === 0 the SDK caught a bad request shape before it ever left your machine (e.g. a malformed MSISDN, a callback URL over HTTP in production). No network call happened.
  • statusCode > 0 Daraja itself responded with an HTTP error. The SDK read errorMessage / ResultDesc / message off the response body (Daraja is inconsistent about which field it uses) and normalized it onto error.message.

This distinction matters operationally: a statusCode === 0 error is a bug in your calling code, worth fixing before deploy. A statusCode > 0 error is Daraja telling you something about the transaction itself (insufficient funds, invalid shortcode, etc.) and often needs to be shown to the end user or logged for support.

Terminal-friendly formatting

DarajaError overrides toString() and the Node.js util.inspect hook, so console.log(error) in a terminal prints a readable, color-coded box (colors respect NO_COLOR) instead of a raw stack dump:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠  Safaricom Daraja Error

Code        INVALID_STK_REQUEST
Status      N/A (Client Error)
Message     amount must be between 1 and 250000 KES (Safaricom's
            per-transaction limit), but received 500000.

Suggestion
Check the field named in the message against the M-Pesa Express
(STK Push) request schema.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

If you're logging structured JSON instead (Pino, Winston, a log aggregator), reach for the individual fields errorCode, statusCode, endpoint, rawResponse rather than the formatted string.

Common errorCode values

errorCodeMeaning
INVALID_CREDENTIALSconsumerKey/consumerSecret failed validation see Configuration
INVALID_ENVIRONMENTenvironment wasn't "sandbox" or "production"
INVALID_TIMEOUTtimeout wasn't a positive finite number
INVALID_CALLBACK_URLA callback URL failed validation see Callback URLs
INVALID_STK_REQUESTM-Pesa Express push/query request failed validation
INVALID_C2B_REQUESTC2B registerUrl/simulate request failed validation
INVALID_B2C_TOPUP_REQUESTB2C Top Up request failed validation
INVALID_B2B_HAKIKISHA_REQUESTB2B Hakikisha request failed validation
INVALID_QR_REQUESTDynamic QR request failed validation
INVALID_DYNAMIC_OFFERS_REQUESTDynamic Offers request failed validation
INVALID_SWAP_REQUEST / INVALID_IMSI_REQUESTSwap/IMSI request failed validation (bad MSISDN, usually)
INVALID_MOBILE_NUMBER_VALIDATION_REQUESTKYC validation request failed validation
SIMULATE_NOT_AVAILABLE_IN_PRODUCTIONYou called c2b.simulate() with environment: 'production'
OAUTH_AUTHENTICATION_FAILEDDaraja rejected your credentials at the token endpoint
MALFORMED_OAUTH_RESPONSEDaraja returned 200 with an unexpected body shape
REQUEST_TIMEOUTThe request didn't complete within your configured timeout
NETWORK_ERRORA fetch failure below the HTTP layer (DNS, connection reset, etc.)
API_REQUEST_FAILEDFallback code for a Daraja error response Safaricom didn't tag with its own errorCode

Endpoint-specific errorCodes and status-code details are covered on each endpoint's own page under Endpoints.

On this page