Web Dev

Integrating eSewa and Khalti: a developer’s field notes

Pratik Thapa
Mobile & Apps Lead · Apr 21, 2026 · 11 min read

Two gateways, two completely different integration models. These are the sandbox credentials, callback patterns and gotchas that matter when you wire eSewa and Khalti into a real store.

After a few dozen integrations the pattern is clear. Khalti behaves like a modern payment API, and eSewa behaves like the wallet it is, with quirks you learn by shipping. This walkthrough covers the flows, the environments, the callbacks and the mistakes that bite first time integrators.

Get your credentials and environments straight

  • eSewa. You need a numeric merchant code issued after KYC. There is no public sandbox, so you test with your live merchant code and a tiny amount, or with the demo credentials your eSewa representative shares. The docs live on the eSewa developer portal and change without much notice, so keep a copy of the agreement you signed.
  • Khalti. The merchant dashboard gives you a test secret key, and a separate live key after approval. The test key works against their sandbox and can simulate success, user cancel and failure.

Rule one: the secret key stays on the server. Khalti's initiate and lookup calls are server only by design, and a leaked live key lets someone forge callback confirmations. Keep keys in environment variables, never in the repository.

The two flows

eSewa uses a redirect model. You POST the amount (tAmt), your merchant code (scd) and a payment ID (pid) to the eSewa payment URL. The customer pays on the eSewa page and eSewa sends them back to your return URL. Nothing in that return proves the payment succeeded. You must call the eSewa verification endpoint from your server with the same pid and amount, and only then mark the order paid.

Khalti uses a server initiated flow. Your backend calls /api/v2/epayment/initiate/ with the amount in paisa, a purchase order ID and a return URL. Khalti responds with a pidx and a payment URL you redirect the customer to. When they finish, Khalti bounces them to your return URL with the pidx, and you call the lookup API to confirm the state before you update the order.

Always verify server side. The redirect back to your site is a navigation event, not a receipt. Both gateways require a confirmation API call before you can trust that the money exists.

Callback and return URL gotchas

  • Two events, one order. Users close tabs after paying. The return URL never fires but the money still moves. Register a webhook, or run a lookup sweep every few minutes, so those orders do not sit in limbo.
  • Duplicate events. The return URL and the webhook can both fire for one payment. Make your payment table idempotent: update only when the order is not already paid, and key the lookup on your own order ID.
  • Amounts are integers. Both gateways work in paisa. Compare integers, never floats, or you will chase phantom mismatches.
  • Local testing. A localhost return URL works for testing redirects, but verification calls must reach the gateway from a server with a public IP. For webhooks, use a tunnel such as ngrok or Cloudflare Tunnel and point the dashboard at the public URL.
  • HTTPS from the start. Some client stacks drop query parameters when a plain 302 bounces HTTP to HTTPS. Keep the return URL HTTPS end to end.

The eSewa quirks worth knowing

  • The pid you send must be unique per order, and you store it with the order. eSewa matches verification on that ID.
  • The verification response is XML. Parse it defensively. The success flag lives in the response body, not the HTTP status code.
  • Merchants report flaky timeouts on the eSewa page during peak festival traffic (Dashain, Tihar). Retry the verification, never the charge, and do not auto retry a payment without telling the user.
  • The merchant code is public information. Do not confuse it with a secret.

The Khalti quirks worth knowing

  • The initiate and lookup calls are both server side with your secret key. There is no client side token dance in the v2 API.
  • Store the pidx with your order. It is your handle for lookup calls and for reconciling against the Khalti dashboard.
  • Test keys accept any amount, live keys enforce limits. Fail a payment in the sandbox on purpose before you go live.
  • The Khalti docs ship example payloads in several languages, and their developer channel answers fast. Use both.

A go live checklist

  1. Keys in environment variables, never in the repository.
  2. Amounts as integers in paisa, end to end.
  3. Server side verification on every return, plus a reconciliation sweep for missed callbacks.
  4. Idempotent order updates so double events do not double credit.
  5. Sandbox runs for success, cancel, insufficient balance and timeout, on both gateways.
  6. One live test order for a small amount followed by a refund, to prove the full loop.

Wrap both gateways behind a shared payment interface and keep gateway specifics out of your order code. When you add Fonepay next quarter, it becomes a weekend task instead of a rewrite.

Payment code is the only code where a bug costs you money in both directions: lost sales on one side, angry refunds on the other.
Pratik Thapa
Mobile & Apps Lead at Web Development Nepal. Writes about building and growing products on the Nepali web.

Need help putting this into practice?

Get a free, honest review of your site or project.

Get a free quote
Quick answers

>Questions readers ask us

Usually a few days to a couple of weeks, depending on the gateway, your business documents and the review queue. Start onboarding early, before you need it.
Yes. Each gateway has its own merchant account, keys and settlement schedule, so plan for parallel onboarding of every gateway you intend to offer.
Trusting the redirect and skipping server side verification. Always verify the transaction with the gateway API and reconcile against your own order records.
Yes. Callbacks can be delayed or dropped. A gentle polling fallback for stuck orders, with idempotent handling, prevents both double charges and lost sales.