Skip to main content
BetterSoftZ
engineeringPublished September 5, 2026

Integrating bKash and Nagad properly

The charge call is the easy part. Everything that makes an integration trustworthy happens in the failure paths.

By BetterSoftZ Engineering, Dhaka

A production-ready bKash or Nagad integration requires more than the payment call: idempotency so retries cannot double-charge, deduplication of repeated callbacks, a polled status check because callbacks are not guaranteed, explicit refund and reversal handling, and automated daily reconciliation against the provider's settlement statement.

Record intent before you call the provider

The first record should be created in your own system before the provider is contacted, in a pending state with your own reference. If the request times out after the provider charged the customer, you still have a record to reconcile against. Systems that write only on success lose exactly the transactions that matter most.

Make every value-moving operation idempotent

Retries are normal — a mobile client on a weak connection will resend. Every operation that moves money carries an idempotency key, and a repeat with the same key returns the original result rather than performing the action again. This single decision prevents most double-charge incidents.

Treat callbacks as unreliable, unordered and repeatable

Provider callbacks can arrive twice, arrive late, arrive out of order, or not at all. A correct handler:

  1. Verifies the callback’s authenticity.
  2. Deduplicates on the provider’s transaction identifier.
  3. Applies the state change only if the transaction is still pending.
  4. Records the raw payload for later dispute investigation.

Alongside that, a scheduled job checks anything still pending against the provider’s status API, because a system that only reacts to callbacks is a system with permanently stuck transactions.

Refunds are not negative payments

Model refunds as their own transactions linked to the original, supporting partial amounts, with their own state machine. Reconciliation, dispute handling and accounting all become significantly simpler, and a partial refund on a partly delivered order stops being a manual correction.

Reconcile every day, automatically

At end of day, match your ledger against the provider’s settlement statement. Anything unmatched goes to an exception queue with the transaction context attached. The target is a short list each morning that a person clears in minutes — not a monthly investigation into a difference nobody can explain.

Test the failures before production does

In the provider sandbox, script the cases that actually cause incidents: timeouts mid-charge, duplicate callbacks, callbacks for unknown transactions, partial refunds, and settlement mismatches. A failure mode that has not been exercised before launch will be exercised by a customer instead.

Frequently asked questions

Why do payments sometimes get recorded twice?

Usually because the integration trusts the callback as a single event. Providers retry, and a network hiccup can deliver the same notification more than once. Without deduplication and idempotency keys, each delivery creates another credit.

What if the callback never arrives?

It happens, so the system should not depend on it. Transactions left in a pending state past a threshold are checked against the provider's status API on a schedule, and resolved from that rather than waiting.

How should refunds be handled?

As first-class transactions with their own records, linked to the original payment, including partial refunds. Treating a refund as a negative payment makes reconciliation and dispute handling much harder later.

Working through this decision yourself?

Describe your situation and we will tell you what we would do, including when the answer is not to hire us.