Payment Sessions API
Payment sessions power PerfectPay's QR code payment flow. A session represents a single payment opportunity with an HMAC-signed QR URL, a W3C Verifiable Presentation, and real-time status updates.
Create a Payment Session
POST /v2/payment-sessions
curl https://sandbox.perfectpay.ai/v2/payment-sessions \
-X POST \
-H "api-key: YOUR_SECRET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 575,
"currency": "USD",
"supported_rails": ["fednow", "rtp", "push_to_card"],
"terminal_id": "term_store_42",
"metadata": {
"order_id": "ORD-1234",
"register": "3"
}
}'
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | yes | Amount in minor units (575 = $5.75) |
currency | string | yes | ISO 4217 currency code |
supported_rails | string[] | yes | Instant rails to offer: fednow, rtp, push_to_card, stablecoin, same_day_ach |
terminal_id | string | no | POS terminal identifier |
idempotency_key | string | no | Prevents duplicate session creation |
metadata | object | no | Arbitrary key-value pairs |
Response:
{
"session_id": "2mGFCDJt5RmJlk9WcPqN0123456",
"status": "created",
"amount": 575,
"currency": "USD",
"qr_url": "https://pay.perfectpay.ai/s/2mGFCDJt5RmJlk9WcPqN0123456?h=abc123...",
"verifiable_presentation": {
"@context": ["https://www.w3.org/ns/credentials/v2", "https://perfectpay.ai/ns/payments/v1"],
"type": ["VerifiablePresentation"],
"holder": "did:web:perfectpay.ai",
"verifiableCredential": [{
"type": ["VerifiableCredential", "PaymentCredential"],
"credentialSubject": {
"session_id": "2mGFCDJt5RmJlk9WcPqN0123456",
"jti": "550e8400-e29b-41d4-a716-446655440000",
"merchant": {"id": "merchant_abc", "name": "Corner Store", "mcc": "5411"},
"amount": {"value": "5.75", "currency": "USD"},
"supported_rails": ["fednow", "rtp", "push_to_card"],
"expiry": "2026-03-23T15:15:00Z"
}
}],
"proof": {
"type": "JsonWebSignature2020",
"jws": "eyJhbGciOiJFUzI1NiJ9...",
"created": "2026-03-23T15:00:00Z"
}
},
"ws_token": "wst_abc123...",
"expires_at": "2026-03-23T15:15:00Z",
"created_at": "2026-03-23T15:00:00Z"
}
The session_id is a 27-character KSUID. The qr_url should be rendered as a QR code for the customer to scan. The ws_token is used for customer-side WebSocket authentication.
Fetch a Session (After QR Scan)
GET /v2/payment-sessions/{session_id}?h={hmac}
This is a public endpoint authenticated by the HMAC parameter. It is called when a customer scans the QR code.
curl "https://sandbox.perfectpay.ai/v2/payment-sessions/2mGFCDJt5RmJlk9WcPqN0123456?h=abc123..."
The response includes the session details, merchant info, amount, supported rails, and the Verifiable Presentation. Each session allows a maximum of 5 fetch attempts before it locks.
Check Session Status
GET /v2/payment-sessions/{session_id}/status
curl https://sandbox.perfectpay.ai/v2/payment-sessions/2mGFCDJt5RmJlk9WcPqN0123456/status \
-H "api-key: YOUR_SECRET_API_KEY"
Returns a lightweight status response without the full VP payload. Use this for polling when WebSocket is not available.
Cancel a Session
DELETE /v2/payment-sessions/{session_id}
curl https://sandbox.perfectpay.ai/v2/payment-sessions/2mGFCDJt5RmJlk9WcPqN0123456 \
-X DELETE \
-H "api-key: YOUR_SECRET_API_KEY"
Only sessions in created or scanned status can be cancelled.
Confirm a Session Payment
POST /v2/payments/confirm-session
This endpoint is called by the customer's client (Web SDK or mobile app) after scanning and selecting a funding source. It uses customer JWT authentication.
curl https://sandbox.perfectpay.ai/v2/payments/confirm-session \
-X POST \
-H "Authorization: Bearer CUSTOMER_JWT" \
-H "Content-Type: application/json" \
-d '{
"session_id": "2mGFCDJt5RmJlk9WcPqN0123456",
"funding_source_id": "fs_abc123",
"rail": "fednow"
}'
Capture a Payment
POST /v2/payments/{session_id}/capture
For sessions created with manual capture, use this to capture after authorization.
curl https://sandbox.perfectpay.ai/v2/payments/2mGFCDJt5RmJlk9WcPqN0123456/capture \
-X POST \
-H "api-key: YOUR_SECRET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 575
}'
Void a Payment
POST /v2/payments/{session_id}/void
Cancel a pre-authorized payment before capture.
curl https://sandbox.perfectpay.ai/v2/payments/2mGFCDJt5RmJlk9WcPqN0123456/void \
-X POST \
-H "api-key: YOUR_SECRET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Customer changed mind"
}'
Refund a Session Payment
POST /v2/payments/{session_id}/refund
curl https://sandbox.perfectpay.ai/v2/payments/2mGFCDJt5RmJlk9WcPqN0123456/refund \
-X POST \
-H "api-key: YOUR_SECRET_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 575,
"reason": "Item returned"
}'
Funding Sources
Customers link funding sources to pay via instant rails. These endpoints use customer JWT authentication. The {customer_id} in the path must match the JWT sub claim.
Link a Funding Source
POST /v2/customers/{customer_id}/funding-sources
curl https://sandbox.perfectpay.ai/v2/customers/cus_abc123/funding-sources \
-X POST \
-H "Authorization: Bearer CUSTOMER_JWT" \
-H "Content-Type: application/json" \
-d '{
"type": "bank_account",
"plaid_public_token": "public-sandbox-abc123...",
"plaid_account_id": "acct_abc123"
}'
Funding source types:
| Type | Fields | Integration |
|---|---|---|
bank_account | plaid_public_token, plaid_account_id | Plaid for account verification |
stablecoin_wallet | wallet_address, chain, stablecoin_type | Direct blockchain wallet |
debit_card | card_number, card_exp_month, card_exp_year | Push-to-Card rails |
List Funding Sources
GET /v2/customers/{customer_id}/funding-sources
curl https://sandbox.perfectpay.ai/v2/customers/cus_abc123/funding-sources \
-H "Authorization: Bearer CUSTOMER_JWT"
Delete a Funding Source
DELETE /v2/customers/{customer_id}/funding-sources/{funding_source_id}
curl https://sandbox.perfectpay.ai/v2/customers/cus_abc123/funding-sources/fs_abc123 \
-X DELETE \
-H "Authorization: Bearer CUSTOMER_JWT"
Session Expiry
Sessions expire after 15 minutes by default. A background scheduler checks for expired sessions every 30 seconds, transitions them to expired status, broadcasts a WebSocket event, and fires a webhook.
Related
- Instant QR Payments guide for the full integration walkthrough
- Instant Settlement for rail comparison
- Events & Webhooks for webhook delivery