The Direct-QR API skips the PayBridgeNP hosted checkout entirely. You collect the customer’s details on your own page, call one endpoint, and get back a Fonepay QR string + image plus a real-time event stream you subscribe to from the browser. When the customer pays, you receiveDocumentation Index
Fetch the complete documentation index at: https://docs.paybridgenp.com/llms.txt
Use this file to discover all available pages before exploring further.
qr.paid over Server-Sent Events at the same moment the payment.succeeded webhook fires on your server.
Direct-QR is a Premium feature. On the free plan,
POST /v1/qr/fonepay returns 403 with entitlement: "fonepay.directQr".When to use this
Use Direct-QR when you want full control over the checkout UI - for example, a custom mobile app, a self-built point-of-sale terminal, or a marketplace where you want the QR embedded directly inside your product page rather than after a redirect. For most websites, the standard hosted checkout is simpler and gives you all three providers (eSewa, Khalti, Fonepay) in one flow. Direct-QR is Fonepay-only.How it works
Your server collects customer details
Name, email, phone, and (optionally) address - you own the form.
Your server calls POST /v1/qr/fonepay
PayBridgeNP creates a checkout session, talks to Fonepay, and returns a QR string + a base64 PNG + a per-session event stream URL.
Your page shows the QR and subscribes to events
Use any QR renderer (or the bundled PNG) and open an
EventSource on events_url.Endpoint
POST /v1/qr/fonepay
Authenticated with a secret API key. Idempotency is supported via the standard Idempotency-Key header.
Request
| Field | Required | Notes |
|---|---|---|
amount | Yes | Integer paisa. Min 1,000 (Rs. 10), max 100,000,000 (Rs. 10,00,000). |
currency | No | Defaults to "NPR". Fonepay only supports NPR. |
customer.name | Yes | Up to 100 chars. |
customer.email | Yes | Valid email. Used for the receipt. |
customer.phone | No | Up to 30 chars. Used for the success SMS on Premium. |
customer.address | No | If present, line1 and city are required. |
metadata | No | Arbitrary JSON object echoed back on webhooks. |
201)
| Field | Description |
|---|---|
id | Session ID. Doubles as the events_url token - anyone with this ID can listen to events for it, like Stripe’s client_secret. |
qr_message | The raw EMV QR payload. Render it yourself with any QR library if you want full control over size, colors, or to add a logo in the middle. |
qr_image | A 320×320 PNG embedded as a data URL. Drop this straight into an <img src> if you don’t want to render your own. |
events_url | Open an EventSource on this to receive qr.scanned, qr.paid, qr.expired, and ping events. |
expires_at | Hard 3-minute deadline imposed by Fonepay. After this, the QR is dead and qr.expired fires. |
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.
| Event | When it fires | Payload |
|---|---|---|
connected | Right after the stream opens. | { id, status } |
qr.scanned | The customer opened the QR in their bank app. | { sessionId, scannedAt } |
qr.paid | Payment confirmed by Fonepay. Terminal - the stream closes after this. | { paymentId, sessionId, amount, currency, provider, traceId } |
qr.expired | The 3-minute QR window lapsed without payment. Terminal. | { sessionId, expiredAt } |
ping | Keepalive every 7s. Ignore it. | {} |
replay: true set. So you can safely use the browser’s auto-reconnect without missing the payment.
Full example
Webhooks vs SSE - use both
The SSE stream is for the customer’s browser: instant UI feedback. Thepayment.succeeded webhook is for your server: durable, signed, retried. They fire from the same event, so use SSE to update the page and the webhook to commit your order to the database.
If the customer closes the browser between scan and confirmation, the SSE stream goes away - but the webhook still fires.
Expiry and retries
Fonepay QRs expire after 3 minutes. There is no auto-rotation - whenqr.expired fires, the session is permanently dead. To retry, just call POST /v1/qr/fonepay again with the same customer details and you get a fresh QR.
This matches how PaymentIntent works in other payment APIs: short-lived intent, simple recreate-on-expiry.
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.
- No refunds via this endpoint - refund a successful Direct-QR payment the same way as any other Fonepay payment (create-refund).
Related
- Hosted checkout - the standard multi-provider flow with no Premium gate.
- Webhook verification - verify
payment.succeededdeliveries on your server. - Sandbox testing - Fonepay sandbox uses the merchant’s UAT credentials, not a shared sandbox.