Skip to main content
POST
/
{locale}
/
shop
/
qr-card
/
generate
curl -X POST "https://staging-api.loyalty.lt/en/shop/qr-card/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -H "X-API-Secret: your_api_secret" \
  -d '{
    "device_name": "POS Terminal 1",
    "shop_id": 123
  }'
{
  "success": true,
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "qr_code": "loyaltylt://qr-card?code=abcd1234efgh5678&session=550e8400-e29b-41d4-a716-446655440000",
    "ably_channel": "qr-card:550e8400-e29b-41d4-a716-446655440000",
    "partner_id": 45,
    "partner_name": "Coffee Paradise",
    "shop_id": 123,
    "expires_at": "2024-12-10T16:05:00.000Z"
  }
}

QR Card Scan System

The QR Card Scan system allows POS terminals to instantly identify customers by displaying a QR code. When a customer scans this QR code with their Loyalty.lt mobile app, their loyalty card data is automatically sent back to the POS system in real-time.
Unlike QR Login which authenticates users for web sessions, QR Card Scan is designed specifically for POS integration - instantly identifying customers and retrieving their loyalty card data without any confirmation steps.

How It Works

Key Differences from QR Login

FeatureQR LoginQR Card Scan
PurposeAuthenticate user for web sessionIdentify customer at POS
User ActionScan + Confirm in appScan only (instant)
ReturnsJWT tokens for sessionLoyalty card data
Use CaseShop plugins, web integrationsPOS systems, kiosks
Ably Channelqr-login:{session_id}qr-card:{session_id}
Deep Linkloyaltylt://qr-login?...loyaltylt://qr-card?...

Generate QR Card Session

Create a new QR session for customer identification at POS.

Endpoint

POST /{locale}/shop/qr-card/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
Name for the POS terminal or deviceExample: "POS Terminal 1", "Checkout Counter 3"
shop_id
integer
Shop ID for multi-location partners (optional)

Response

success
boolean
Indicates if session was created successfully
data
object
curl -X POST "https://staging-api.loyalty.lt/en/shop/qr-card/generate" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -H "X-API-Secret: your_api_secret" \
  -d '{
    "device_name": "POS Terminal 1",
    "shop_id": 123
  }'
{
  "success": true,
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "qr_code": "loyaltylt://qr-card?code=abcd1234efgh5678&session=550e8400-e29b-41d4-a716-446655440000",
    "ably_channel": "qr-card:550e8400-e29b-41d4-a716-446655440000",
    "partner_id": 45,
    "partner_name": "Coffee Paradise",
    "shop_id": 123,
    "expires_at": "2024-12-10T16:05:00.000Z"
  }
}

Generate Ably Token

Get a secure Ably JWT token to subscribe to real-time card identification 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 (valid for 60 minutes)
data.expires
integer
Unix timestamp when token expires
data.channel
string
Ably channel name (automatically determined based on session type):
  • For card scan: qr-card:{session_id}
  • For login: qr-login:{session_id}
data.session_type
string
Type of session: card_scan or login
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": 1702234500,
    "channel": "qr-card:550e8400-e29b-41d4-a716-446655440000",
    "session_type": "card_scan"
  }
}

Real-time Integration (Ably)

After generating a session and obtaining an Ably token, subscribe to the WebSocket channel to receive card data in real-time.

Channel Name

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

Event to Subscribe

Event NameDescription
card_identifiedCustomer scanned QR code, card data available

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 card identification
ablyChannel.subscribe('card_identified', (message) => {
  const { card_data } = message.data;
  
  console.log('Customer:', card_data.user.name);
  console.log('Card:', card_data.card_number);
  console.log('Points:', card_data.points);
  
  // Process customer data...
});

Event Payload: card_identified

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "authenticated",
  "card_data": {
    "loyalty_card_id": 12345,
    "card_number": "123-456-789",
    "points": 1500,
    "user": {
      "id": 789,
      "name": "Jonas Jonaitis",
      "email": "[email protected]",
      "phone": "+37060000000"
    },
    "redemption": {
      "enabled": true,
      "points_per_currency": 100,
      "currency_amount": 1.00,
      "min_points": 100,
      "max_points": 10000
    },
    "scanned_at": "2024-12-10T16:02:30.000Z"
  },
  "timestamp": "2024-12-10T16:02:30.500Z"
}

Poll Session Status (Fallback)

Alternative to WebSockets: poll the session status periodically. Use this as a fallback if real-time connection is unavailable.

Endpoint

GET /{locale}/shop/qr-card/status/{sessionId}

Path Parameters

sessionId
string
required
The QR card scan session ID

Response Statuses

StatusDescription
pendingWaiting for customer to scan QR code
authenticatedCustomer scanned QR, card data available
expiredSession expired (after 5 minutes)
curl -X GET "https://staging-api.loyalty.lt/en/shop/qr-card/status/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-10T16:05:00.000Z"
  }
}

Card Data Structure

When a customer scans the QR code, you receive comprehensive loyalty card data:
card_data
object

Complete POS Integration Example

JavaScript
import Ably from 'ably';

class LoyaltyPOS {
  constructor(apiKey, apiSecret) {
    this.apiKey = apiKey;
    this.apiSecret = apiSecret;
    this.baseUrl = 'https://api.loyalty.lt/en/shop';
    this.ably = null;
    this.currentSession = null;
  }

  async startCustomerIdentification() {
    // 1. Generate QR session
    const sessionResponse = await fetch(`${this.baseUrl}/qr-card/generate`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey,
        'X-API-Secret': this.apiSecret
      },
      body: JSON.stringify({
        device_name: 'POS Terminal 1'
      })
    });
    
    const { data: session } = await sessionResponse.json();
    this.currentSession = session;
    
    // 2. Get Ably token (universal endpoint)
    const tokenResponse = await fetch(`${this.baseUrl}/ably/token`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey,
        'X-API-Secret': this.apiSecret
      },
      body: JSON.stringify({
        session_id: session.session_id
      })
    });
    
    const { data: tokenData } = await tokenResponse.json();
    
    // 3. Connect to Ably and subscribe
    this.ably = new Ably.Realtime({ token: tokenData.token });
    const channel = this.ably.channels.get(session.ably_channel);
    
    return new Promise((resolve, reject) => {
      // Set timeout for session expiration
      const timeout = setTimeout(() => {
        this.cleanup();
        reject(new Error('Session expired'));
      }, 5 * 60 * 1000); // 5 minutes
      
      channel.subscribe('card_identified', (message) => {
        clearTimeout(timeout);
        this.cleanup();
        resolve(message.data.card_data);
      });
    });
  }
  
  getQRCodeUrl() {
    return this.currentSession?.qr_code;
  }
  
  cleanup() {
    if (this.ably) {
      this.ably.close();
      this.ably = null;
    }
    this.currentSession = null;
  }
}

// Usage
const pos = new LoyaltyPOS('your_api_key', 'your_api_secret');

try {
  // Display QR code on POS screen
  const qrUrl = await pos.startCustomerIdentification();
  displayQRCode(pos.getQRCodeUrl()); // Your QR code display function
  
  // Wait for customer to scan
  const cardData = await pos.startCustomerIdentification();
  
  console.log(`Welcome ${cardData.user.name}!`);
  console.log(`You have ${cardData.points} points`);
  
  if (cardData.redemption?.enabled) {
    // Calculate available discount
    const maxDiscount = Math.floor(cardData.points / cardData.redemption.points_per_currency) 
                        * cardData.redemption.currency_amount;
    console.log(`Available discount: €${maxDiscount}`);
  }
} catch (error) {
  console.error('Customer identification failed:', error);
}

Error Codes

CodeDescriptionHTTP Status
AUTH_FORBIDDENInvalid or missing API credentials403
RESOURCE_NOT_FOUNDSession not found404
RESOURCE_EXPIREDQR code/session has expired410
RESOURCE_ALREADY_EXISTSQR code already used409
INTERNAL_ERRORServer error500

Best Practices

Use WebSockets

Always prefer Ably WebSockets for instant notifications. Use polling only as a fallback.

Show Countdown

Sessions expire after 5 minutes. Display a countdown timer and auto-refresh QR codes.

Handle Expiration

Automatically generate a new QR session when the current one expires.

Secure Display

Ensure QR code is only visible to the intended customer at the checkout.

Session Timeline

1

Generate Session

POS creates QR card scan session (valid for 5 minutes)
2

Display QR Code

Convert deep link to QR code image on POS screen
3

Subscribe to Events

Connect to Ably WebSocket channel qr-card:{session_id}
4

Customer Scans

Customer opens Loyalty.lt app and scans the QR code
5

Instant Identification

Card data is immediately sent to POS via Ably (no confirmation needed!)
6

Process Transaction

Apply loyalty discounts, award points, or redeem rewards