Skip to main content
POST
/
{locale}
/
shop
/
auth
/
qr-login
/
generate
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"
  }
}

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

X-API-Key
string
required
API key from Partners Portal
X-API-Secret
string
required
API secret from Partners Portal

Request Body

device_name
string
Optional name for the device requesting loginExample: "Shop Plugin - Checkout"
shop_id
integer
Optional shop ID for multi-shop partners

Response

success
boolean
Indicates if session was created successfully
data
object
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

session_id
string
required
The session ID from the generate endpoint

Response

data.token
string
Ably JWT token for WebSocket connection
data.expires
integer
Unix timestamp when token expires
data.channel
string
Ably channel name (automatically determined based on session type)
data.session_type
string
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

qr-login:{session_id}
Example: qr-login:550e8400-e29b-41d4-a716-446655440000

Events to Subscribe

Event NameDescription
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

session_id
string
required
The QR login session ID

Response Statuses

StatusDescription
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"
{
  "success": true,
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "expires_at": "2024-12-08T15:35:00.000Z"
  }
}

Session Timeline

1

Generate Session

Partner creates QR session (valid for 5 minutes)
2

Display QR Code

Convert deep link to QR code image for user to scan
3

Subscribe to Events

Connect to Ably WebSocket or start polling
4

User Scans

Mobile app user scans QR code (status: scanned)
5

User Confirms

User confirms login in mobile app (status: confirmed)
6

Receive Tokens

Shop plugin receives JWT tokens via WebSocket/polling

Error Codes

CodeDescriptionHTTP Status
AUTH_FORBIDDENInvalid or missing API credentials403
RESOURCE_NOT_FOUNDShop not found or access denied404
QR_SESSION_NOT_FOUNDSession expired or invalid404
RESOURCE_EXPIREDQR code has expired410
INTERNAL_ERRORServer error500

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