QR Login System
The QR Login system allows users to authenticate on desktop/web by scanning a QR code with their mobile app. This is commonly used in custom shop plugin integrations.
QR Login requires Shop API authentication (X-API-Key and X-API-Secret headers) and real-time WebSocket support via Ably.
Authentication Flow
Generate QR Login Session
Create a new QR login session that generates a scannable QR code.
Endpoint
POST /{locale}/shop/auth/qr-login/generate
Authentication
API key from Partners Portal
API secret from Partners Portal
Request Body
Optional name for the device requesting login Example: "Shop Plugin - Checkout"
Optional shop ID for multi-shop partners
Response
Indicates if session was created successfully
Unique session identifier (UUID)
Deep link URL for QR code: loyaltylt://qr-login?code={code}&session={session_id}
Name of the partner business
Partner’s unique identifier
Shop ID if provided (nullable)
ISO 8601 timestamp when session expires (5 minutes)
curl -X POST "https://staging-api.loyalty.lt/en/shop/auth/qr-login/generate" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key" \
-H "X-API-Secret: your_api_secret" \
-d '{
"device_name": "Shop Plugin - Checkout",
"shop_id": 123
}'
{
"success" : true ,
"data" : {
"session_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"qr_code" : "loyaltylt://qr-login?code=abcd1234efgh5678ijkl9012mnop3456&session=550e8400-e29b-41d4-a716-446655440000" ,
"partner_name" : "Coffee Paradise" ,
"partner_id" : 45 ,
"shop_id" : 123 ,
"expires_at" : "2024-12-08T15:35:00.000Z"
}
}
Generate Ably Token
Get a secure Ably JWT token to subscribe to real-time QR login events.
Endpoint
POST /{locale}/shop/ably/token
Request Body
The session ID from the generate endpoint
Response
Ably JWT token for WebSocket connection
Unix timestamp when token expires
Ably channel name (automatically determined based on session type)
Type of session: login or card_scan
curl -X POST "https://staging-api.loyalty.lt/en/shop/ably/token" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key" \
-H "X-API-Secret: your_api_secret" \
-d '{
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}'
{
"success" : true ,
"data" : {
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"expires" : 1702054500 ,
"channel" : "qr-login:550e8400-e29b-41d4-a716-446655440000" ,
"session_type" : "login"
}
}
Real-time Integration (Ably)
After generating a session and obtaining an Ably token, subscribe to the WebSocket channel to receive login status updates in real-time.
Channel Name
Example: qr-login:550e8400-e29b-41d4-a716-446655440000
Events to Subscribe
Event Name Description qr_login_statusLogin status changed (scanned, confirmed, expired)
Ably Connection Example
import Ably from 'ably' ;
// 1. Get token from API response
const { token , channel } = ablyTokenResponse . data ;
// 2. Connect to Ably
const ably = new Ably . Realtime ({ token: token });
// 3. Subscribe to channel
const ablyChannel = ably . channels . get ( channel );
// 4. Listen for status updates
ablyChannel . subscribe ( 'qr_login_status' , ( message ) => {
const { status , user , token , refresh_token } = message . data ;
if ( status === 'scanned' ) {
console . log ( 'User scanned QR, waiting for confirmation...' );
}
if ( status === 'confirmed' ) {
console . log ( 'User authenticated:' , user . name );
// Store tokens and redirect user
localStorage . setItem ( 'access_token' , token );
localStorage . setItem ( 'refresh_token' , refresh_token );
}
});
Event Payload: qr_login_status
Status: scanned
{
"session_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "scanned"
}
Status: confirmed
{
"session_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "confirmed" ,
"user" : {
"id" : 123 ,
"name" : "Jonas Jonaitis" ,
"phone" : "+37060000000" ,
"email" : "[email protected] "
},
"token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." ,
"refresh_token" : "a1b2c3d4e5f6..." ,
"expires_in" : 3600
}
Poll QR Login Status (Fallback)
Alternative to WebSockets: poll the session status periodically.
Endpoint
POST /{locale}/shop/auth/qr-login/poll/{session_id}
Path Parameters
Response Statuses
Status Description pendingWaiting for user to scan QR code scannedUser scanned QR, waiting for confirmation confirmedUser confirmed login, tokens available expiredSession expired (after 5 minutes)
curl -X POST "https://staging-api.loyalty.lt/en/shop/auth/qr-login/poll/550e8400-e29b-41d4-a716-446655440000" \
-H "X-API-Key: your_api_key" \
-H "X-API-Secret: your_api_secret"
Pending Status
Confirmed Status
Session Not Found (404)
{
"success" : true ,
"data" : {
"session_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"status" : "pending" ,
"expires_at" : "2024-12-08T15:35:00.000Z"
}
}
Session Timeline
Generate Session
Partner creates QR session (valid for 5 minutes)
Display QR Code
Convert deep link to QR code image for user to scan
Subscribe to Events
Connect to Ably WebSocket or start polling
User Scans
Mobile app user scans QR code (status: scanned)
User Confirms
User confirms login in mobile app (status: confirmed)
Receive Tokens
Shop plugin receives JWT tokens via WebSocket/polling
Error Codes
Code Description HTTP Status AUTH_FORBIDDENInvalid or missing API credentials 403 RESOURCE_NOT_FOUNDShop not found or access denied 404 QR_SESSION_NOT_FOUNDSession expired or invalid 404 RESOURCE_EXPIREDQR code has expired 410 INTERNAL_ERRORServer error 500
Best Practices
Use WebSockets Prefer Ably WebSockets over polling for real-time updates
Handle Expiration Sessions expire after 5 minutes - show countdown to users
Secure Storage Store received tokens securely after successful login
Fallback Polling Implement polling as fallback if WebSockets fail