Skip to main content
A customer landing on your success page is not proof of payment. Confirm every payment on your server before you release goods, credit, or access.

Return URL

Navigation only. Treat every query parameter as untrusted.

Signed webhook

Your real-time signal. Verify the raw body before reading the event.

Payment API

Your fallback check. Match status, amount, and currency to your order.

Webhooks are the source of truth

PayBridgeNP sends a signed webhook the instant a payment reaches a terminal state. This is the reliable, real-time signal to act on. Do not build your fulfillment around the browser redirect.
1

Register an endpoint

Add an HTTPS URL in the Dashboard under Settings → Webhooks, or with the API. Save the signing secret shown once at creation.
2

Verify the signature

Every delivery carries an X-PayBridgeNP-Signature: t=<timestamp>,v1=<hmac> header. Parse out t and v1, compute HMAC-SHA256(signing_secret, "<t>.<raw_request_body>"), and compare the hex digest to v1 in constant time before you trust the payload. Or let the SDK’s constructEvent() do it. Full walkthrough in Verifying webhooks.
3

Act on the event

Fulfill on payment.succeeded, and release any hold on payment.failed. Deliveries are idempotent and can be retried, so key your fulfillment on the payment id and ignore a repeat you have already handled.
Never fulfill an order from return-URL parameters or client-side JavaScript alone. A customer can change them. Fulfill from a signature-verified webhook on your server.

Payment states

Payment states from pending and processing to success, failed, cancelled, or refunded

Release value only after the provider-verified payment reaches success.

When a payment fails or the buyer cancels

Hosted checkout does not leave the buyer on a dead end. If a payment fails, or the buyer cancels partway through, returning to the same checkout link while the session is still valid brings back the same session at the method picker, with a notice naming the provider that failed and inviting the buyer to try again or pick a different method. The provider that just failed is de-prioritized in the picker rather than pre-selected, so the retry nudges the buyer toward a different route. Because the same session is reused rather than a new one being created, a recovered checkout cannot double-charge: only one attempt on that session can end in a successful payment. The retry does not buy extra time either. The session’s 30-minute expiry window starts at creation, and a failed or cancelled attempt does not extend it. This matters for your webhook handling. A session that simply times out unpaid fires no webhook at all. A buyer-initiated cancellation does fire payment.cancelled, and because a buyer can cancel more than one attempt on the same session before succeeding or giving up, you may receive payment.cancelled more than once for a single session. Treat repeats as informational rather than as a reason to change anything you have already recorded for that session.

Amount mismatch

If the provider reports a paid amount that differs from what the session billed, PayBridgeNP does not record a success. The payment is stored as failed with the decline reason Amount mismatch, the order is never marked paid, no receipt goes out, and payment.failed is sent with reason set to Amount mismatch: expected <paisa>, received <paisa>. In live mode, the amount the buyer actually paid has still settled into your provider account, because the provider moves the money before PayBridgeNP sees the result. Reconcile it against your bank or wallet statement, and if you want to return it to the buyer, refund it from the provider’s merchant panel. The payment’s page in the dashboard shows the billed and reported amounts side by side. In test mode, the eSewa and Khalti sandboxes move no real money, so a test-mode mismatch has nothing to refund. Fonepay has no sandbox: a test-mode Fonepay payment runs on your real merchant account and is real money (see Sandbox testing).

Verify before you release value

When a webhook is delayed, or you want a second confirmation before handing over something valuable, read the payment back and check its status yourself. The payment object reports status: "success" once it is paid.
Before you release anything, confirm three things: the status is success, and the amount and currency match what you charged.

Do not poll in a tight loop

Reading the payment endpoint on a fast timer is unreliable and will run into rate limits. Let webhooks do the waiting. Use GET /v1/payments/{id} for a one-off check or to reconcile a specific payment after the fact, not as a polling loop.

Reconciliation

To reconcile a batch against your own records, page through your payments with limit and offset and match each one:
Every payment carries a stable id, status, amount, provider, and provider_ref you can match against your ledger. Page with offset until you have covered the window you care about.