Documentation

Instant QR Payments

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

  1. Merchant creates a payment session with an amount and supported rails
  2. PerfectPay generates an HMAC-signed QR URL and a W3C Verifiable Presentation proving the payment terms
  3. Merchant displays the QR code on a terminal, receipt, or screen
  4. Customer scans the QR code and opens the hosted payment page
  5. Customer selects a funding source and confirms payment over an instant rail (FedNow, RTP, Push-to-Card, or stablecoin)
  6. Merchant receives a real-time WebSocket event when the payment completes

QR Payment Flow

Loading diagram...

Supported Instant Rails

RailSettlement SpeedNotes
FedNowSecondsFederal Reserve instant payment network
RTPSecondsThe Clearing House real-time payments
Push-to-CardMinutesVisa Direct / Mastercard Send
StablecoinSecondsOn-chain settlement
Same Day ACHSame business dayLower 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:

Text
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

StatusMeaning
createdSession exists, QR code is ready to display
scannedCustomer has scanned the QR code
confirmingPayment is being processed on the selected rail
succeededPayment completed successfully
failedPayment attempt failed
expiredSession TTL exceeded (default 15 minutes)
cancelledMerchant cancelled the session

State transitions:

Text
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

Text
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

Text
wss://sandbox.perfectpay.ai/ws/session/{session_id}

Auth: Use the ws_token returned from the session fetch API.

Event Types

EventWhen
session_createdQR code is ready
session_scannedCustomer scanned the QR
payment_confirmedPayment confirmed on rail
payment_failedPayment attempt failed
session_expiredTTL exceeded
session_cancelledMerchant cancelled
pongHeartbeat 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.

TypeIntegration
Bank AccountPlaid integration for account linking
Stablecoin WalletDirect blockchain wallet connection
Debit CardPush-to-Card via card networks

Merchant Integration

Minimal Flow

  1. Create a payment session via API or WebSocket terminal command
  2. Render the qr_url from the response as a QR code image
  3. Listen on WebSocket for payment_confirmed or session_expired
  4. Fulfill the order when the payment succeeds

POS Integration

For point-of-sale terminals:

  1. Connect to the WebSocket terminal endpoint on startup
  2. Send create_session commands as customers check out
  3. Display the QR code on the customer-facing screen
  4. React to WebSocket events for real-time status updates

Online Invoicing

For invoices and remote payments:

  1. Create a session via the API
  2. Embed the QR code in an invoice PDF or email
  3. Poll the session status endpoint or listen on WebSocket
  4. Mark the invoice as paid when succeeded

Webhooks

Payment session events are also delivered as webhooks:

  • payment_session.created
  • payment_session.scanned
  • payment_session.completed
  • payment_session.expired

See Events & Webhooks for webhook configuration.