BartaPay
Payment verification for merchants who only have a personal bKash or Nagad number.
Creator and sole developer. I designed the payment lifecycle, API, matching logic, admin tooling, and the Android companion app.
2025 – Present
Context
Many small Bangladeshi businesses accept bKash, Nagad, Rocket, or Upay payments into a personal wallet. There is no merchant API to call, so confirmation usually means a customer sending a screenshot and someone checking the phone by hand.
BartaPay turns that manual loop into an API. A merchant site creates a payment request, an Android app on the wallet owner's phone forwards incoming SMS, and BartaPay matches the message to the request and notifies the merchant through a webhook.
The problem
The hard part is not receiving an SMS. It is deciding, safely and exactly once, that a specific message pays a specific request — when messages arrive late, when two code paths race, and when amounts or senders do not match.
Merchant callbacks also need to be delivered reliably without letting an attacker point a webhook at an internal service.
Technical decisions
Two separate authentication models
Merchant sites authenticate with an API key. The Android forwarder uses a device token and heartbeat, with tighter rate limits on login and SMS endpoints. The two trust boundaries are deliberately not mixed.
Atomic matching with row locks
SMS matching can be triggered by the queue and by a customer-entered transaction ID at the same time. The matcher re-reads both rows inside a transaction with SELECT … FOR UPDATE, so a payment can only be settled once.
Amount equality and a time window
A message matches only when the parsed amount equals the requested amount to the paisa, the request is still pending, and it is inside its 30-minute checkout window. A separate grace window handles messages that arrive late because a phone was offline.
Queued webhooks with SSRF protection
Delivery runs as a queued job with retries and backoff. Before sending, the callback URL is resolved and rejected if it points at loopback, private, link-local, or cloud metadata addresses.
Operational visibility
A Filament admin area exposes payments, SMS records, webhook logs, device status, and a reconciliation screen, so a failed match can be diagnosed and resolved without touching the database.
Challenges and fixes
Double-verification races
An automatic SMS match and a manual verification can reach the same payment at the same time. Moving the state change into a transaction with row locks removes the race.
Unreliable message delivery
Phones lose connectivity and SMS arrives late. Instead of rejecting every late payment, the matcher allows a configurable grace window while still refusing to re-complete an already settled request.
Webhook security
A merchant-supplied URL is an outbound request controlled by a third party. The delivery path validates the resolved host against private ranges and logs every attempt for audit.
Results
- A working gateway deployed at payment.gamitisa.com.
- Automatic matching plus a manual reconciliation path for exceptions.
- No duplicate settlement in the locked matching flow.
Note: Source code is private. The live gateway is at payment.gamitisa.com.
Want to talk about this work?
I'm open to full-time onsite roles in Dhaka.