If you're building a product for the Nepali market and need to accept payments, this guide covers everything you need as a developer. We'll cover the full integration lifecycle with working code examples.
Overview: What You're Building
A complete payment integration in Nepal typically involves:
- Creating a checkout session - your server creates a session with the amount and customer details
- Redirecting to checkout - the customer pays on a hosted page
- Receiving a webhook - your server is notified of the payment outcome
- Verifying and fulfilling - you verify the webhook signature and fulfill the order
This pattern is identical whether you're building with Node.js, Python, PHP, or any other backend.
Authentication
PayBridgeNP uses bearer token authentication. All API requests must include your secret key:
Authorization: Bearer sk_live_your_secret_key
Keep your secret key server-side only. Never expose it in client-side JavaScript, mobile apps, or public repositories.
Key types:
sk_live_...- live secret key (server-side only, real payments)sk_test_...- sandbox secret key (server-side only, no real money moves)
Keep secret keys server-side only. PayBridgeNP has no client-safe publishable keys.
Installing the SDK
TypeScript / Node.js
npm install @paybridge-np/sdk
# or
bun add @paybridge-np/sdk
Python
pip install paybridge-np
PHP
composer require paybridge-np/sdk
Creating a Checkout Session
This is the core operation. Your server creates a session; the customer completes payment on PayBridgeNP's hosted checkout.
import { PayBridge } from "@paybridge-np/sdk";
const paybridge = new PayBridge({
apiKey: process.env.PAYBRIDGE_API_KEY!,
});
// In your order handler:
const session = await paybridge.checkout.create({
amount: 250000, // Amount in paisa (NPR 2,500.00)
currency: "NPR",
customer: {
name: "Binod Karki",
email: "binod@example.com",
phone: "9851234567",
},
successUrl: "https://yoursite.com/orders/{CHECKOUT_SESSION_ID}/success",
cancelUrl: "https://yoursite.com/checkout",
metadata: {
orderId: "ORD-12345",
userId: "usr_abc",
},
});
// Redirect the customer
return redirect(session.checkoutUrl);
Amount is in paisa (1 NPR = 100 paisa). This is the standard for payment APIs - it avoids floating-point issues with decimal amounts.
Handling the Webhook
When the customer completes (or fails) payment, PayBridgeNP sends a POST request to your registered webhook URL.
import { createHmac } from "crypto";
import type { Request, Response } from "express";
export async function handleWebhook(req: Request, res: Response) {
const signature = req.headers["paybridge-signature"] as string;
const body = req.rawBody; // needs raw body, not parsed JSON
// Verify the signature
const expected = createHmac("sha256", process.env.PAYBRIDGE_WEBHOOK_SECRET!)
.update(body)
.digest("hex");
if (signature !== `sha256=${expected}`) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(body);
switch (event.type) {
case "payment.succeeded": {
const { metadata, amount, provider } = event.data;
await fulfillOrder(metadata.orderId);
console.log(`Order ${metadata.orderId} paid via ${provider}`);
break;
}
case "payment.failed": {
await markOrderFailed(event.data.metadata.orderId);
break;
}
case "payment.cancelled": {
await releaseInventory(event.data.metadata.orderId);
break;
}
}
return res.json({ received: true });
}
Always verify webhook signatures. Unverified webhooks are a security vulnerability - an attacker could POST fake "payment.succeeded" events to your endpoint.
Webhook Events Reference
| Event | When it fires |
|---|---|
payment.succeeded | Payment successful |
payment.failed | Payment declined or error |
payment.cancelled | Customer didn't pay within timeout |
payment.refunded | Refund fully processed |
payment.refunded | Refund failed |
Idempotency
Always design your fulfillment logic to be idempotent. Webhooks can be delivered more than once. Check if you've already fulfilled the order before acting:
case "payment.succeeded": {
const order = await db.orders.findOne({ id: event.data.metadata.orderId });
if (order.status === "fulfilled") break; // already handled
await fulfillOrder(order.id);
break;
}
Retrieving a Payment
You can fetch payment details at any time:
const payment = await paybridge.payments.retrieve("pay_abc123");
console.log(payment.status); // "completed" | "failed" | "pending"
console.log(payment.provider); // "khalti" | "esewa" | "connectips"
console.log(payment.amount); // 250000 (paisa)
Processing Refunds
const refund = await paybridge.refunds.create({
paymentId: "pay_abc123",
amount: 250000, // Full refund. Partial: specify less than original
reason: "Customer requested cancellation",
});
console.log(refund.status); // "pending" | "completed"
Refunds are processed back to the original payment method. Processing time:
- Khalti: instant to 1 business day
- eSewa: 1-3 business days
- ConnectIPS: 2-5 business days
Sandbox Testing
Use sandbox keys (sk_test_...) for development. The sandbox behaves identically to production but no real money moves.
Sandbox test credentials are in your PayBridgeNP dashboard under Developers > Sandbox.
Error Handling
try {
const session = await paybridge.checkout.create({ ... });
} catch (error) {
if (error instanceof PayBridge.APIError) {
console.error(error.status); // HTTP status code
console.error(error.code); // "invalid_amount" | "provider_unavailable" | etc.
console.error(error.message); // Human-readable message
}
}
Next Steps
- Read the full API reference
- See SDK guides for TypeScript, Python, and PHP
- For recurring billing use cases, see the subscription billing guide
- Get your API keys - sandbox is free, no approval required