payment.succeeded webhook to fulfill the order.
Live mode needs a Pro plan or higher; sandbox works on every plan. Sandbox Fonepay QRs can move real money, so the NPR 1,000 per-payment and NPR 5,000 monthly caps apply. An unpaid test QR can count towards the monthly cap for a short time, even if replaced or never scanned.
When to use this
Use Direct-QR for a custom app, point-of-sale terminal, or product page that must show the QR in place. For most websites, hosted checkout is simpler and supports eSewa, Khalti, and Fonepay. Direct-QR is Fonepay-only.How it works
SSE updates the customer page; the signed webhook confirms payment to your server.
POST /v1/qr/fonepay and sends the returned QR image and events_url to the page. The page opens an EventSource; PayBridgeNP sends qr.paid there (and qr.scanned when Fonepay reports the scan) while independently delivering the signed webhook to your server.
Endpoint
POST /v1/qr/fonepay
Authenticated with a secret API key.
Request
Send
customer.phone when you have it. In live mode, it is the only way
the buyer can receive an SMS payment confirmation. In sandbox, the rendered
SMS is recorded in your logs but never sent to a real phone.201)
GET /v1/qr/:id/events
A Server-Sent Events stream. No API key required - the session ID in the URL is the auth token, so this is safe to call directly from a customer’s browser.
Replay on reconnect: if the connection drops and the client reconnects after a terminal event already fired, the stream replays it once with
replay: true set. So you can safely use the browser’s auto-reconnect without missing the payment.
POST /v1/qr/{id}/refresh
The Fonepay QR’s display window is only ~3 minutes, and some wallets (eSewa) reject a stale QR. Call this to mint a fresh QR for the same session - same id, same events_url, same webhook - instead of spawning a brand-new session per order. It requires the payments:write scope and the same mode-aware plan access as QR creation.
It takes no body (the amount and customer already live on the session). The response is the same shape as create, with a new qr_message, qr_image, and expires_at:
EventSource open across refreshes: the session id is unchanged, so qr.scanned / qr.paid keep flowing on the same stream. A refresh returns 400 if the session is already paid, expired, or otherwise not refreshable.
One current QR per session. Refresh makes a new QR only when the current one is gone or in its last 20 seconds; otherwise you get the current one back with its expires_at. A replaced QR can still be paid, and that payment is still recorded. Call refresh on qr.expired or near expires_at, not on a timer. A 503 with code: "fonepay_busy" (QR creation busy) or "fonepay_unavailable" (Fonepay did not answer) charged nothing; retry shortly.
Full example
Webhooks vs SSE
Use SSE for immediate browser feedback. Use the signed, retriedpayment.succeeded webhook to update your database and fulfill the order. If the customer closes the page, the webhook still arrives.
Expiry and retries
The QR has a ~3-minute display window (set by us, not Fonepay). When it lapses,qr.expired fires as a UI hint so you can show a fresh QR - but it is not the end of the payment:
- A late payment still completes. Some wallets (Khalti) accept a QR after its window; Fonepay then confirms it, minutes late if need be, and
qr.paidandpayment.succeededstill fire. Never mark an order failed onqr.expiredalone: wait forqr.paid, the webhook, or the end of the session. - Refresh the same session. When
qr.expiredfires orexpires_atis close, callPOST /v1/qr/{id}/refresh. Keep the sameid,events_url, webhook, and openEventSource. - Settle on the webhook. Treat
payment.succeededas the source of truth for fulfilling the order - it fires whenever Fonepay confirms, fast or late. Use SSE only for live UI.
Pay a subscription invoice with Direct-QR
If you use subscription billing, you can collect a subscription’s invoice with a Direct-QR instead of sending the customer to the hosted bill page - perfect for in-person or embedded recurring collection (a counter membership, monthly tuition, etc.).POST /v1/billing/invoices/{id}/qr (no body) makes a Fonepay Direct-QR for the invoice’s amount and customer. Once paid, the invoice is marked paid and the subscription activates (incomplete→active), as if paid on the bill page. Creating it needs the billing:write scope, Growth or higher in live mode, and Fonepay set up on the project. The returned events_url gives payment updates. Refreshing it with POST /v1/qr/{id}/refresh also needs payments:write and, in live mode, Pro or higher.
Limitations
- Fonepay only. Other providers don’t have an equivalent QR-with-realtime-confirmation flow exposed today.
- NPR only.
- No partial captures or holds - the QR is a one-shot full-amount transfer.
- Refund successful Direct-QR payments from the PayBridgeNP dashboard. Fonepay refunds are not available through the create-refund API.
Related
- Hosted checkout - the standard multi-provider flow with no Pro gate.
- Webhook verification - verify
payment.succeededdeliveries on your server. - Sandbox testing - Fonepay has no test environment; sandbox Fonepay uses your real merchant credentials with small platform caps.