<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nduhukire Medard</title>
    <description>The latest articles on DEV Community by Nduhukire Medard (@nduhukire_medard).</description>
    <link>https://dev.to/nduhukire_medard</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4126156%2Fc53d0900-c6e4-410e-993e-d4b385b3fe59.jpg</url>
      <title>DEV Community: Nduhukire Medard</title>
      <link>https://dev.to/nduhukire_medard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nduhukire_medard"/>
    <language>en</language>
    <item>
      <title>M-Pesa ResultCode 1037 doesn't mean the payment failed</title>
      <dc:creator>Nduhukire Medard</dc:creator>
      <pubDate>Tue, 03 Nov 2026 11:15:00 +0000</pubDate>
      <link>https://dev.to/nduhukire_medard/m-pesa-resultcode-1037-doesnt-mean-the-payment-failed-4h2d</link>
      <guid>https://dev.to/nduhukire_medard/m-pesa-resultcode-1037-doesnt-mean-the-payment-failed-4h2d</guid>
      <description>&lt;p&gt;If you've integrated M-Pesa STK push (Daraja's "Lipa na M-Pesa Online"), you've probably written something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResultCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;markFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works in the sandbox. In production it quietly causes three problems:&lt;br&gt;
Callbacks go missing, so paid orders sit at "pending" forever.&lt;br&gt;
Callbacks can be faked, because Daraja doesn't sign them.&lt;br&gt;
ResultCode 1037 gets treated as a failure when it often isn't, and customers who already paid are told to pay again.&lt;br&gt;
What 1037 actually means&lt;br&gt;
&lt;code&gt;1037&lt;/code&gt; is "DS timeout: user cannot be reached". Safaricom sent the PIN prompt, but didn't hear back from the phone in time: it was off, had no signal, or the customer was slow.&lt;br&gt;
That is not the same as "the customer didn't pay". Developers report payments that completed minutes after a 1037 callback. If your code marks the order failed and shows a "Try again" button, a customer who did pay can end up paying twice.&lt;br&gt;
Sort ResultCodes into three groups, not two&lt;br&gt;
Group   ResultCodes What to do&lt;br&gt;
Paid    &lt;code&gt;0&lt;/code&gt;    Confirm with STK Push Query, check the amount, record the receipt, fulfil once&lt;br&gt;
Failed, final   &lt;code&gt;1&lt;/code&gt; (low balance), &lt;code&gt;1032&lt;/code&gt; (cancelled), &lt;code&gt;2001&lt;/code&gt; (wrong PIN), &lt;code&gt;1019&lt;/code&gt; (expired) Mark failed and offer "Try again"&lt;br&gt;
Not known yet   &lt;code&gt;1037&lt;/code&gt;, &lt;code&gt;1001&lt;/code&gt; (another transaction in progress), &lt;code&gt;1025&lt;/code&gt;, &lt;code&gt;17&lt;/code&gt;, &lt;code&gt;26&lt;/code&gt;, and anything you don't recognise Keep pending, query again later, and don't send a second push yet&lt;br&gt;
The third group is the whole point. Waiting a few minutes costs you nothing. Wrongly failing a paid order costs you a customer.&lt;br&gt;
In code, that's one small function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FINAL_FAILED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1032&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1019&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="cm"&gt;/** paid | failed | unknown. Anything not clearly final is "unknown": ask again. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FINAL_FAILED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1037, 1001, 1025, 17, 26, no answer yet, and anything new&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a 1037, keep the payment pending, tell the customer you're checking, and query it with STK Push Query after about 30 seconds, then with longer gaps (60 s, 2 min, 5 min, 10 min). If it's still unknown after that, flag it for a person to check against your M-Pesa statement. And don't allow a second push for the same order until the first one has a final answer.&lt;br&gt;
Why callbacks go missing&lt;br&gt;
When the callback never arrives, it's almost always one of these:&lt;br&gt;
The URL isn't public HTTPS (Safaricom can't reach &lt;code&gt;localhost&lt;/code&gt;).&lt;br&gt;
The URL contains "mpesa" or "safaricom". Developers report these are silently dropped; use a neutral path like &lt;code&gt;/webhooks/stk&lt;/code&gt;.&lt;br&gt;
A redirect (http → https, or a trailing slash). Safaricom doesn't follow them.&lt;br&gt;
Your server is slow or returns an error. Acknowledge immediately, do the work afterwards.&lt;br&gt;
The fix is the same in every case: don't rely on the callback alone. Query every push that is still pending after a minute or two.&lt;br&gt;
Callbacks can be faked&lt;br&gt;
Daraja callbacks aren't signed. Anyone who finds your callback URL can post &lt;code&gt;"ResultCode": 0&lt;/code&gt;. So treat the callback as a hint: find the payment, then confirm with STK Push Query before marking anything paid.&lt;br&gt;
One catch: the query response doesn't include the receipt number. Take &lt;code&gt;MpesaReceiptNumber&lt;/code&gt; from the callback, but only after the query confirms the payment. A forged callback then can't mark an order paid.&lt;br&gt;
Settle each payment exactly once&lt;br&gt;
Callbacks arrive twice, and your retry job can run at the same moment as a late callback. Let the database decide who wins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;mpesa_payments&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receipt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;checkout_request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- 1 row: you settled it, fulfil the order. 0 rows: someone else did. Do nothing.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full version&lt;br&gt;
I wrote this up in full, with a tested Node.js module (classify, safe callback parsing, a query schedule, and a test suite), on PayDev Africa: M-Pesa STK Push Callbacks and ResultCode 1037. Every ResultCode is explained in the M-Pesa error code reference, and there are versions of the STK push integration for Node.js, Laravel and Django.&lt;br&gt;
How do you handle 1037 in your integration? I'd like to hear what's worked for you.&lt;/p&gt;

</description>
      <category>mpesa</category>
      <category>payments</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>host awebsite</title>
      <dc:creator>Nduhukire Medard</dc:creator>
      <pubDate>Tue, 15 Sep 2026 10:59:54 +0000</pubDate>
      <link>https://dev.to/nduhukire_medard/host-awebsite-4d6h</link>
      <guid>https://dev.to/nduhukire_medard/host-awebsite-4d6h</guid>
      <description></description>
    </item>
  </channel>
</rss>
