PayBridgeNP
A payment gateway businesses in Nepal use to accept eSewa, Khalti and Fonepay payments.
Used for online store checkouts on WooCommerce and Shopify, and for collecting payments through payment links.
- Founder & developer · 2026
- Founded and built by Bhabishya Bhatt
- Built with
- TypeScript, Bun, Hono, Next.js, Drizzle, PostgreSQL

The idea
I started PayBridgeNP because I wanted a better checkout and developer experience for payments in Nepal. I built the API, hosted checkout, SDKs, store integrations, documentation, and a sandbox to test against before going live.
Businesses connect their own merchant accounts and offer multiple payment methods through one integration. PayBridgeNP coordinates checkout and payment status. Payments settle directly to the merchant through their payment provider; PayBridgeNP does not hold the funds.
Bugs and decisions
Duplicate webhooks from overlapping workers
A store could still be processing a payment notification when the next worker run picked up the same pending delivery. That led to repeated notifications and duplicate order notes in WooCommerce. The bug only showed up when the store’s response was slow.
The first fix claimed a batch of deliveries in the database, but a slow batch could outlast those claims before every notification was sent. I changed it to claim each delivery immediately before sending it. The claim expires if a worker stops, allowing another attempt without leaving the notification stuck.
Claiming and expiry now use the database’s clock, avoiding disagreement between server clocks. Tests cover overlapping workers and abandoned claims becoming available again. Receiving applications still need to handle repeated webhook events safely.
A safety check that could not see its own data
A guard I added briefly blocked new Direct-QR payment requests. The guard checks whether a request is using the sandbox. To do that it read the checkout session the same request had just written, but through a separate database connection, before the transaction had committed. It could not see the row, so it rejected the request.
I changed the guard to accept that context from its caller instead of querying for rows that had just been created. It still rejects requests when required context is missing. My first fix missed other callers, so some sandbox requests failed again the next day. I followed up across the remaining entry points and added regression checks for missing context.
Providers use different amount units
The PayBridgeNP API takes amounts in paisa. Khalti’s API also wants paisa. eSewa’s wants rupees. Send the wrong unit and the requested amount is a hundred times too large or too small.
Amount conversion stays inside the provider adapters. The API and stored payment amounts use integer paisa, so the rest of the payment flow does not need to know which unit a provider expects.
A dashboard cache that mixed sandbox and live views
While setting up a new merchant, I saved a provider’s settings in sandbox mode and saw the same settings when I switched to live. Toggling a provider in one view appeared to flip it in the other. The API itself scoped reads and writes to the mode sent with each request.
The dashboard’s client-side cache keyed provider data by project only, so sandbox and live shared one entry. An optimistic update changed both views. It only reproduced intermittently, so it took a while to pin down. The fix included mode in the cache keys and passed it explicitly with provider requests, instead of relying on a header updated after navigation.
SDKs & tools
TypeScript SDK, PHP SDK, Python SDK, React Native SDK, PayBridgeNP CLI, MCP server, WooCommerce plugin, WHMCS module.
