Shop It Docs
Developer ResourcesPayment

eSewa ePay v2

Complete reference for the eSewa ePay v2 gateway integration — flow, signature generation, verification, test credentials, and implementation details.

eSewa ePay v2

Audience: Backend developers, QA engineers Scope: eSewa ePay v2 integration details, HMAC signature, verification flow, test environment


Overview

eSewa is Nepal's most popular digital wallet. The ePay v2 API uses a form POST flow where the backend signs the payment fields and the frontend submits a hidden form to eSewa's portal.

PropertyValue
Gateway nameesewa
Initiation typeform_post
Signature algorithmHMAC-SHA256 (base64)
Amount formatRupees (string)
Official docsdeveloper.esewa.com.np/pages/Epay

Payment Flow


Signature Generation

The backend signs three fields using HMAC-SHA256 with the merchant secret key:

const message = `total_amount=${totalAmount},transaction_uuid=${transactionUuid},product_code=${productCode}`;
const signature = createHmac("sha256", secretKey)
  .update(message)
  .digest("base64");

The signed_field_names field tells both eSewa and the verification logic which fields were signed and in what order.


Form Fields (gatewayPayload)

These are the hidden form inputs the frontend submits to eSewa:

FieldDescriptionExample
amountPayment amount (same as total_amount for simple payments)"500"
tax_amountTax component"0"
total_amountTotal amount including tax"500"
transaction_uuidUnique transaction ID generated by backend"42-1708000000000"
product_codeMerchant/product code from eSewa"EPAYTEST"
product_service_chargeService charge"0"
product_delivery_chargeDelivery charge"0"
success_urlWhere eSewa redirects on success"https://myapp.com/payment/success"
failure_urlWhere eSewa redirects on failure"https://myapp.com/payment/failure"
signed_field_namesComma-separated list of signed fields"total_amount,transaction_uuid,product_code"
signatureHMAC-SHA256 signature (base64)"kI4GnQx9..."

Frontend Form Submission

const form = document.createElement("form");
form.method = "POST";
form.action = redirectUrl; // "https://rc-epay.esewa.com.np/api/epay/main/v2/form"

for (const [name, value] of Object.entries(gatewayPayload)) {
  const input = document.createElement("input");
  input.type = "hidden";
  input.name = name;
  input.value = value;
  form.appendChild(input);
}

document.body.appendChild(form);
form.submit();

Callback Response

After the user completes payment, eSewa redirects to success_url with a data query parameter containing a base64-encoded JSON string:

https://myapp.com/payment/success?data=eyJ0cmFuc2FjdGlvbl9jb2RlIjoi...

Decoded JSON:

{
  "transaction_code": "0005ABC",
  "status": "COMPLETE",
  "total_amount": "500",
  "transaction_uuid": "42-1708000000000",
  "product_code": "EPAYTEST",
  "signed_field_names": "transaction_code,status,total_amount,transaction_uuid,product_code,signed_field_names",
  "signature": "xyz789base64..."
}

The backend decodes this payload on the API-owned success callback before redirecting the browser to the shared payment result page.

If PAYMENT_RESULT_PAGE_URL is configured, that result page can live in the frontend.

If it is not configured, the API temporarily falls back to:

${API_PUBLIC_BASE_URL}/api/payments/result

Verification (Two Steps)

Step 1: Signature Verification

The backend recomputes the HMAC using the signed_field_names from the response:

// Build message from signed_field_names in the response
const fields = signedFieldNames.split(",");
const message = fields.map((field) => `${field}=${response[field]}`).join(",");

const computed = createHmac("sha256", secretKey)
  .update(message)
  .digest("base64");

const isValid = computed === response.signature;

This prevents tampering — if any signed field was modified, the signature won't match.

Step 2: Server-Side Status Check

Even if the signature is valid, the backend makes a server-to-server call to eSewa's status API for independent confirmation:

GET https://rc.esewa.com.np/api/epay/transaction/status/
  ?product_code=EPAYTEST
  &total_amount=500
  &transaction_uuid=42-1708000000000

Expected response:

{
  "status": "COMPLETE",
  "ref_id": "0005ABC",
  "total_amount": "500.0"
}

The backend only marks the payment as completed if status === "COMPLETE".


Configuration

Environment Variables

VariableDescriptionTest Default
ESEWA_EPAY_URLeSewa form POST URLhttps://rc-epay.esewa.com.np/api/epay/main/v2/form
ESEWA_EPAY_STATUS_URLeSewa status check APIhttps://rc.esewa.com.np/api/epay/transaction/status/
ESEWA_PRODUCT_CODEMerchant/product codeEPAYTEST
ESEWA_SECRET_KEYHMAC secret key8gBm/:&EnhH.1/q

These are defined in apps/api/src/config/env.validation.ts with Zod validation and test defaults.

Production

For production, replace the test values with real credentials from the eSewa merchant portal:

VariableProduction Value
ESEWA_EPAY_URLhttps://epay.esewa.com.np/api/epay/main/v2/form
ESEWA_EPAY_STATUS_URLhttps://esewa.com.np/api/epay/transaction/status/
ESEWA_PRODUCT_CODEYour registered merchant code
ESEWA_SECRET_KEYYour merchant secret key

No code changes required — only env var values change.


Test Credentials

For the test/sandbox environment:

ItemValue
eSewa Test IDs9806800001 through 9806800005
PasswordNepal@123
OTP Token123456
MPIN (app only)1122
Product CodeEPAYTEST
Secret Key8gBm/:&EnhH.1/q

Implementation Reference

Gateway file: apps/api/src/modules/payment/gateways/esewa.gateway.ts

Key methods:

  • initiate() — generates transaction_uuid, computes HMAC-SHA256 signature, returns form_post payload
  • verify() — validates response signature + calls eSewa status API
  • generateSignature() — HMAC-SHA256 helper
  • verifyResponseSignature() — recomputes and compares HMAC from callback
  • checkTransactionStatus() — server-to-server GET to eSewa status API

Security Checklist

  • HMAC signature computed server-side — secret key never sent to frontend
  • Callback signature verified before trusting response data
  • Server-side status API call for independent confirmation
  • signed_field_names from the response used for verification (not hardcoded)
  • Secret key stored in environment variable, not hardcoded
  • Amount from eSewa's response returned for cross-checking by the consumer