Instant QR Payments
PerfectPay's QR payment system lets merchants present a QR code at a terminal, invoice, or screen. The customer scans, selects an instant payment rail, and settles in seconds. The merchant receives real-time confirmation over WebSocket.
How It Works
- Merchant creates a payment session with an amount and supported rails
- PerfectPay generates an HMAC-signed QR URL and a W3C Verifiable Presentation proving the payment terms
- Merchant displays the QR code on a terminal, receipt, or screen
- Customer scans the QR code and opens the hosted payment page
- Customer selects a funding source and confirms payment over an instant rail (FedNow, RTP, Push-to-Card, or stablecoin)
- Merchant receives a real-time WebSocket event when the payment completes
QR Payment Flow
Supported Instant Rails
| Rail | Settlement Speed | Notes |
|---|---|---|
| FedNow | Seconds | Federal Reserve instant payment network |
| RTP | Seconds | The Clearing House real-time payments |
| Push-to-Card | Minutes | Visa Direct / Mastercard Send |
| Stablecoin | Seconds | On-chain settlement |
| Same Day ACH | Same business day | Lower cost, slower |
The merchant specifies which rails they support when creating a session. PerfectPay presents the available options to the customer based on what both sides support.
Security Model
HMAC-Signed QR URLs
Every QR URL is signed with HMAC-SHA256 to prevent tampering:
https://pay.perfectpay.ai/s/{session_id}?h={hmac}
The HMAC is verified server-side before the session is trusted. Constant-time comparison prevents timing attacks.
W3C Verifiable Presentations
Each payment session includes a cryptographically signed Verifiable Presentation (W3C VC Data Model 2.0) that proves:
- The merchant identity (ID, name, MCC)
- The exact amount and currency
- Which instant rails are accepted
- A session expiry timestamp
- A unique JTI nonce for replay protection
The VP is signed with ES256 (ECDSA P-256) via AWS KMS. Customers or their agents can independently verify the payment terms before authorizing funds.
Rate Limiting
Each session allows a maximum of 5 fetch attempts. After that, the session is locked to prevent abuse. Sessions also have a configurable TTL (default 15 minutes) and are automatically expired by a background scheduler.
Session Lifecycle
| Status | Meaning |
|---|---|
created | Session exists, QR code is ready to display |
scanned | Customer has scanned the QR code |
confirming | Payment is being processed on the selected rail |
succeeded | Payment completed successfully |
failed | Payment attempt failed |
expired | Session TTL exceeded (default 15 minutes) |
cancelled | Merchant cancelled the session |
State transitions:
created → scanned → confirming → succeeded
→ failed
created → expired
created → cancelled
scanned → expired
scanned → cancelled
Real-Time WebSocket Updates
Merchants connect to a WebSocket endpoint to receive live session updates without polling.
Connect from a Terminal
wss://sandbox.perfectpay.ai/ws/terminal/{terminal_id}
Auth: Pass your API key in the api-key header during the WebSocket handshake.
Connect from the Web SDK
wss://sandbox.perfectpay.ai/ws/session/{session_id}
Auth: Use the ws_token returned from the session fetch API.
Event Types
| Event | When |
|---|---|
session_created | QR code is ready |
session_scanned | Customer scanned the QR |
payment_confirmed | Payment confirmed on rail |
payment_failed | Payment attempt failed |
session_expired | TTL exceeded |
session_cancelled | Merchant cancelled |
pong | Heartbeat response |
Terminal Commands
Terminals can also send commands over the WebSocket:
create_session— Create a new session directly from the terminal (HMAC-signed)cancel_session— Cancel an active session (HMAC-signed)
Connection Limits
- Max 10 connections per IP
- Max 100 connections per merchant
- 60-second idle timeout (use ping/pong heartbeats)
- Horizontal scaling via Redis pub/sub
Funding Sources
Customers link funding sources (bank accounts, stablecoin wallets, or debit cards) to pay via instant rails. See the Payment Sessions API for the funding source endpoints.
| Type | Integration |
|---|---|
| Bank Account | Plaid integration for account linking |
| Stablecoin Wallet | Direct blockchain wallet connection |
| Debit Card | Push-to-Card via card networks |
Merchant Integration
Minimal Flow
- Create a payment session via API or WebSocket terminal command
- Render the
qr_urlfrom the response as a QR code image - Listen on WebSocket for
payment_confirmedorsession_expired - Fulfill the order when the payment succeeds
POS Integration
For point-of-sale terminals:
- Connect to the WebSocket terminal endpoint on startup
- Send
create_sessioncommands as customers check out - Display the QR code on the customer-facing screen
- React to WebSocket events for real-time status updates
Online Invoicing
For invoices and remote payments:
- Create a session via the API
- Embed the QR code in an invoice PDF or email
- Poll the session status endpoint or listen on WebSocket
- Mark the invoice as paid when
succeeded
Webhooks
Payment session events are also delivered as webhooks:
payment_session.createdpayment_session.scannedpayment_session.completedpayment_session.expired
See Events & Webhooks for webhook configuration.
Related
- Payment Sessions API for endpoint details and curl examples
- Instant Settlement for rail comparison
- Payouts for sending funds to recipients