
August 16, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Implementing a reliable payment gateway is often the most critical bottleneck for Nepali eCommerce projects. This eSewa Integration Guide for PHP Apps provides the exact technical steps to integrate Nepal’s largest digital wallet into Laravel or custom PHP applications without relying on outdated documentation. Whether you are building a legal-tech portal or an online store, getting the handshake, signature generation, and verification loop correct is non-negotiable for securing real-money transactions.
Many developers I work with in Kathmandu still rely on deprecated MD5 implementations or test-mode snippets found in old forums. In 2026, eSewa mandates HMAC-SHA256 for all production integrations, and failing to implement proper server-side verification exposes your business to chargebacks and fraud. For those building broader payment ecosystems, this guide complements general Laravel payment integration strategies by focusing specifically on eSewa's unique cryptographic requirements and callback behaviors.
How does the eSewa payment flow work in PHP?
Understanding the sequence of redirects and API calls prevents the most common integration failures. The eSewa protocol is not a simple REST API where you send money and get a JSON response immediately. It is a redirect-based flow with an asynchronous verification step that many developers skip or implement incorrectly.
The critical takeaway from this flow is step 6. When eSewa redirects the user back to your success_url, that GET request contains transaction parameters but no proof of payment. A malicious user can manually visit your success URL with forged parameters. Your PHP backend must ignore these parameters for order confirmation and instead use them only as lookup keys to query eSewa’s verification endpoint directly from your server.
How do you generate HMAC-SHA256 signatures for eSewa?
Signature generation is where most integrations fail silently. eSewa rejects requests with invalid signatures without detailed error messages, leading to hours of debugging. The signature must be generated using HMAC-SHA256 with your merchant secret key, and the signed string must follow an exact format.
Preparing the signature string
The data to sign is a single concatenated string containing specific transaction fields separated by commas. The order matters absolutely. For the current ePay v2 API, the typical format is:
<?php
// Total amount, transaction UUID, product code, secret key
// NOTE: Verify exact field order in latest eSewa docs - this is illustrative
$totalAmount = '1000.00';
$transactionUuid = 'TXN-' . time() . '-' . random_int(1000, 9999);
$productCode = 'EPAYTEST'; // Use your assigned production code
$secretKey = config('services.esewa.secret_key');
// Build the message string exactly as specified
$message = "total_amount={$totalAmount},transaction_uuid={$transactionUuid},product_code={$productCode}";
// Generate HMAC-SHA256 signature
$signature = base64_encode(hash_hmac('sha256', $message, $secretKey, true));
A common mistake is including spaces after commas or using different decimal formatting than what you send in the form. If your form sends 1000 but your signature uses 1000.00, verification fails. Always normalize amounts to two decimal places consistently across both the signature generation and the HTML form fields.
Laravel implementation pattern
In Laravel applications, encapsulate this logic in a dedicated service class rather than scattering it across controllers. This makes testing easier and ensures consistent formatting. Store credentials in .env and access via config:
// app/Services/EsewaService.php
class EsewaService
{
public function generateSignature(float $amount, string $uuid): string
{
$formattedAmount = number_format($amount, 2, '.', '');
$message = sprintf(
'total_amount=%s,transaction_uuid=%s,product_code=%s',
$formattedAmount,
$uuid,
config('services.esewa.product_code')
);
return base64_encode(
hash_hmac('sha256', $message, config('services.esewa.secret_key'), true)
);
}
} For developers working on eCommerce platforms in Nepal, wrapping this in a reusable package saves significant time across multiple client projects. Remember that the secret key differs between test and production environments—never hardcode it.
What is the correct server-side verification process?
This is the most security-critical section of this eSewa Integration Guide for PHP Apps. After the customer completes payment, eSewa redirects them to your success URL with query parameters like oid, amt, refId, and txnId. These parameters are not trustworthy. You must verify them server-to-server.
Making the verification request
Use Laravel’s HTTP client or Guzzle to POST to eSewa’s verification endpoint. Include the transaction reference ID and your merchant credentials. The verification endpoint returns JSON indicating whether the transaction completed successfully and for what amount.
// In your PaymentController@success method
$response = Http::asForm()->post(config('services.esewa.verify_url'), [
'amt' => $request->input('amt'),
'rid' => $request->input('refId'),
'pid' => config('services.esewa.product_code'),
'scd' => config('services.esewa.merchant_code'),
]);
$data = $response->json();
if (($data['status'] ?? '') !== 'COMPLETE') {
Log::warning('eSewa verification failed', ['refId' => $request->input('refId'), 'response' => $data]);
return redirect()->route('checkout.failed')->with('error', 'Payment could not be verified.');
}
// CRITICAL: Verify amount matches YOUR stored order amount
$order = Order::where('esewa_txn_id', $request->input('txnId'))->firstOrFail();
if (abs((float)$data['amt'] - (float)$order->total_amount) > 0.01) {
Log::error('eSewa amount mismatch', ['expected' => $order->total_amount, 'received' => $data['amt']]);
abort(403, 'Transaction amount mismatch');
}
// Only NOW mark as paid
$order->update(['status' => 'paid', 'paid_at' => now()]); The amount check is not optional. Attackers have exploited gateways by paying Re 1 for orders worth Rs 10,000. Without verifying the returned amount against your database record, you will ship goods for fractions of their price. This pattern applies equally whether you’re running a multi-gateway Laravel setup or a standalone integration.
How do you handle eSewa test vs production environments safely?
Mixing test and production credentials causes lost payments and debugging nightmares. eSewa maintains separate endpoints, merchant codes, and secret keys for each environment. Your application must switch between them based on configuration, never runtime detection.
| Configuration Aspect | Test Environment | Production Environment |
|---|---|---|
| Gateway URL | https://uat.esewa.com.np/epay/main | https://epay.esewa.com.np/epay/main |
| Verification URL | https://uat.esewa.com.np/epay/transrec | https://epay.esewa.com.np/epay/transrec |
| Product Code | EPAYTEST | Your assigned merchant code |
| Secret Key | Public test key from docs | Private key from merchant dashboard |
| SSL Requirement | Optional (but recommended) | Mandatory HTTPS everywhere |
In Laravel, use environment-specific config files or conditional logic in config/services.php:
// config/services.php
'esewa' => [
'gateway_url' => env('ESEWA_GATEWAY_URL', 'https://uat.esewa.com.np/epay/main'),
'verify_url' => env('ESEWA_VERIFY_URL', 'https://uat.esewa.com.np/epay/transrec'),
'product_code' => env('ESEWA_PRODUCT_CODE', 'EPAYTEST'),
'merchant_code' => env('ESEWA_MERCHANT_CODE'),
'secret_key' => env('ESEWA_SECRET_KEY'),
], Never commit production secrets to version control. On shared hosting or VPS deployments common in Nepal, ensure your .env file permissions are restricted to 600 and owned by the web server user. I’ve seen too many sites where .env was world-readable, exposing payment credentials.
What are common eSewa integration pitfalls and how do you avoid them?
After integrating eSewa across multiple production systems—from legal service portals to florist eCommerce stores—I’ve catalogued recurring failure modes. Avoiding these saves days of troubleshooting.
- Decimal formatting inconsistencies: eSewa expects amounts formatted as
1000.00, not1,000.00or1000. Usenumber_format($amount, 2, '.', '')consistently everywhere. - Missing timeout handling: eSewa’s verification API can occasionally take 5-10 seconds under load. Set explicit timeouts (15s) and implement retry logic with exponential backoff. Don’t let users hang indefinitely.
- Ignoring failure URLs: Users cancel payments or encounter errors. Your
failure_urlhandler should gracefully restore the cart/session state so customers can retry without re-entering everything. - Stale session data: If your success handler relies on session-stored order IDs, sessions may expire during the payment redirect. Always pass the transaction UUID in the eSewa form and look up orders by that UUID, not session.
- Timezone mismatches: eSewa timestamps are in NPT (UTC+5:45). If your server runs UTC, convert explicitly when comparing transaction times or generating daily reconciliation reports.
One subtle issue specific to Nepal: some corporate firewalls and ISP proxies intermittently block outbound HTTPS to eSewa’s verification endpoint. If your verification calls fail sporadically despite correct credentials, test connectivity from your production server using curl -v https://epay.esewa.com.np/epay/transrec. Consider implementing a queue-based verification fallback that retries failed verifications every 30 seconds for up to 10 minutes before alerting support.
Secure eSewa Integration Checklist for Production
Before going live with any eSewa Integration Guide for PHP Apps implementation, run through this verification checklist. Each item addresses a real vulnerability or operational failure observed in production systems:
- HMAC-SHA256 only: Confirm no MD5 or SHA1 signature code remains anywhere in your codebase.
- Server-side verification mandatory: Audit every success route to ensure it calls eSewa’s API before updating any order status.
- Amount validation enforced: Verify your comparison logic handles floating-point precision correctly (use integer paisa or epsilon comparison).
- Credentials externalized: Check that no secret keys appear in git history or deployed artifacts.
- Timeout and retry configured: Ensure HTTP clients have explicit timeouts and verification failures trigger background retries.
- Logging comprehensive: Log every verification attempt with refId, expected amount, received amount, and API response (redact sensitive fields).
- Failure path tested: Manually test cancelled payments, expired sessions, and network timeouts to confirm graceful degradation.
- SSL enforced end-to-end: Verify your site serves only over HTTPS and eSewa callbacks arrive via HTTPS.
Integrating eSewa correctly requires discipline more than complexity. The cryptographic primitives are standard; the challenge is maintaining rigor around verification and environment separation when deadlines pressure teams to cut corners. For teams building financial or legal platforms where transaction integrity is non-negotiable, investing in proper admin panel tooling to monitor and reconcile eSewa transactions pays dividends immediately.
If you’re implementing eSewa for a production system and need architecture review or troubleshooting, reach out directly. I’ve debugged enough eSewa integrations to spot signature issues and verification gaps quickly, whether you’re running Laravel, Symfony, or vanilla PHP.

