
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Integrating webhooks for Nepal payment gateways Khalti eSewa is the only reliable way to confirm digital transactions without blocking your user interface or risking race conditions. While polling APIs works for low-volume testing, production systems serving Nepali customers require asynchronous event notifications to handle network latency and mobile banking timeouts gracefully. This guide covers the exact verification logic, security controls, and infrastructure patterns needed to build a resilient payment confirmation system that actually works in local production environments.
How do you verify webhook signatures for Khalti and eSewa securely?
Security is non-negotiable when handling financial events. A common mistake I see in Nepali fintech projects is trusting the payload body without cryptographic verification. Attackers can easily spoof POST requests to your endpoint if you skip this step. Both Khalti and eSewa use HMAC-SHA256 signatures, but the implementation details differ slightly. Always store your merchant secrets in environment variables or a secrets manager like HashiCorp Vault; never hardcode them in your repository. For teams managing sensitive credentials across multiple services, reviewing Kubernetes secrets management done right provides essential patterns for keeping these keys safe in production clusters.
Khalti Signature Verification
Khalti sends the signature in the X-Khalti-Signature header. The signature is computed over the raw JSON request body. Do not parse and re-stringify the JSON before verifying, as key ordering differences will invalidate the hash. Use the raw byte stream from the request.
<?php
// Laravel/PHP Example for Khalti Webhook Verification
$secret = config('services.khalti.webhook_secret');
$payload = file_get_contents('php://input');
$signature = $request->header('X-Khalti-Signature');
$computedHash = base64_encode(
hash_hmac('sha256', $payload, $secret, true)
);
if (!hash_equals($computedHash, $signature)) {
Log::warning('Khalti webhook signature mismatch', [
'ip' => $request->ip(),
'expected' => $computedHash,
'received' => $signature
]);
abort(403, 'Invalid signature');
}
// Safe to process payload after this point
$data = json_decode($payload, true);
eSewa Signature Verification
eSewa typically includes the signature within the POST body itself or as a specific header depending on the API version (v1 vs v2). In the modern v2 integration, look for the signature field in the JSON body. The message format often concatenates specific fields rather than the entire body. Always consult the current eSewa merchant documentation, as their signing algorithm has evolved. If the signature fails, log the raw payload immediately for debugging—network proxies or WAFs sometimes modify request bodies in transit.
How do you handle webhook idempotency and duplicate deliveries?
Network instability between Kathmandu data centers and cloud providers means duplicates are guaranteed, not exceptional. Your webhook handler must be idempotent. Processing the same payment notification twice should never result in double-crediting a user account or shipping two orders. The primary key for idempotency is the gateway's unique transaction ID (transaction_id for Khalti, transaction_code for eSewa), not your internal order ID.
- Extract the gateway transaction ID from the verified payload immediately.
- Check your database for an existing record with that gateway ID. Use a dedicated
gateway_transactionstable with a UNIQUE constraint on the gateway reference column. - If the record exists, return HTTP 200 immediately without modifying any business state. Log it as a duplicate for observability.
- If the record does not exist, insert it within a database transaction alongside your order status update. This atomic operation prevents partial updates.
- Return HTTP 200 only after the transaction commits successfully.
This pattern ensures that even if the gateway retries the same event five times due to a timeout, your system processes it exactly once. For deeper context on maintaining data consistency during high-load events, the principles in PostgreSQL replication and high availability apply directly to ensuring your transaction ledger remains consistent during webhook storms.
What are the key differences between Khalti and eSewa webhook payloads?
While both gateways serve the same market, their API contracts differ significantly. Understanding these distinctions prevents integration bugs when supporting multiple payment methods. I maintain a comparison reference for teams building unified payment abstractions.
| Feature | Khalti | eSewa |
|---|---|---|
| Signature Header | X-Khalti-Signature | Body field signature (v2) |
| Transaction ID Field | transaction_id | transaction_code |
| Amount Format | Paisa (integer, e.g., 10000 = NPR 100) | NPR (decimal string, e.g., "100.00") |
| Status Values | Completed, Pending, Failed | COMPLETE, PENDING, FAILED |
| Retry Policy | Exponential backoff up to 24h | Fixed interval retries (typically 3 attempts) |
| Payload Content-Type | application/json | application/json (v2) / Form-encoded (legacy) |
A critical operational note: Khalti amounts are in paisa. Forgetting to divide by 100 is the most frequent bug I encounter during code reviews for Nepali e-commerce platforms. Always normalize amounts to a single canonical format (I recommend storing paisa as integers) at the ingestion boundary before passing data to business logic. For broader context on integrating these gateways into a complete checkout flow, see accept online payments in Nepal eSewa and Khalti integration.
How do you monitor webhook reliability and handle failures?
Webhooks are invisible until they fail. You need explicit observability to detect silent failures where the gateway stops delivering events or your endpoint silently rejects them. Treat webhook endpoints like any other critical API: define SLIs and alert on error rates. The monitoring strategies outlined in the four golden signals of monitoring apply directly here—track latency, traffic, errors, and saturation specifically for your payment notification routes.
Essential Monitoring Checklist
- Response Code Distribution: Alert if non-200 responses exceed 1% over 5 minutes. Track 4xx (your validation failure) vs 5xx (your server crash) separately.
- Processing Latency: P95 should stay under 3 seconds. Slow handlers cause gateway timeouts and unnecessary retries.
- Signature Failure Rate: Any spike indicates either an attack or a rotated secret you missed. Alert immediately.
- Gap Detection: Monitor the time since last successful webhook. If you expect steady volume and receive nothing for 30 minutes, something is broken.
- Queue Depth: If you offload processing to a background job (recommended), monitor queue lag. Backpressure here means users aren't getting confirmations.
For local development and staging environments where your server isn't publicly accessible, use tunneling tools like ngrok or Cloudflare Tunnel. Never expose a development server directly to the internet. Configure your gateway's test mode webhook URL to point to the tunnel endpoint, and always verify that your local handler behaves identically to production—including signature checks against test secrets.
Production Readiness for Nepal Payment Webhooks
Implementing webhooks for Nepal payment gateways Khalti eSewa correctly requires treating them as first-class distributed system components, not afterthoughts. Verify every signature, enforce idempotency at the database level, normalize payload formats early, and instrument everything. Test your failure modes explicitly: simulate signature mismatches, duplicate deliveries, and slow processing in staging before going live. If your team needs help auditing your payment integration architecture or setting up compliant infrastructure for Nepali fintech, reach out to discuss your specific requirements.