Back to Developer

QR Codes

Generate branded QR codes with landing pages, scan tracking, and conversion analytics.

Overview

Create QR codes that link to product pages, coupons, custom landing pages, or any URL. Every scan is tracked with geographic and device data. QR codes can be branded with your store colors and logo, and optionally configured with expiration dates or scan limits.

Use QR codes on packaging, receipts, marketing materials, and in-store displays. The analytics dashboard shows scan trends, geographic distribution, and conversion rates tied to campaigns.

Create QR Code

Generate a QR code with an optional brand color, logo, and campaign tag. The response includes the QR code image URL and a short scan URL that tracks every scan before redirecting to the destination.

const whale = new WhaleClient({ apiKey: 'wk_live_...' })

// Create a branded QR code
const qr = await whale.qr.create({
  code: 'spring-sale-tshirt',
  type: 'product',
  destination_url: 'https://your-store.com/products/classic-tshirt?utm_source=qr',
  brand_color: '#0A84FF',
  logo_url: 'https://your-store.com/logo-icon.png',
  campaign_name: 'spring_sale_2026',
  expires_at: '2026-06-01T00:00:00Z',
  max_scans: 10000
})

// Response
{
  "id": "qr_550e8400-e29b-41d4-a716-446655440000",
  "code": "spring-sale-tshirt",
  "type": "product",
  "image_url": "https://vm.whaletools.cloud/v1/stores/{store_id}/qr-codes/spring-sale-tshirt/image",
  "scan_url": "https://vm.whaletools.cloud/s/spring-sale-tshirt",
  "destination_url": "https://your-store.com/products/classic-tshirt?utm_source=qr",
  "total_scans": 0,
  "status": "active",
  "created_at": "2026-03-10T12:00:00.000Z",
  "expires_at": "2026-06-01T00:00:00.000Z",
  "max_scans": 10000
}

// Get QR image in different sizes
// Default: 300x300 PNG
GET /v1/stores/{store_id}/qr-codes/spring-sale-tshirt/image
// Custom size
GET /v1/stores/{store_id}/qr-codes/spring-sale-tshirt/image?size=500
// SVG format
GET /v1/stores/{store_id}/qr-codes/spring-sale-tshirt/image?format=svg

Scan Analytics

Every scan records the device type, geographic location (city-level via geo-IP), and timestamp. Analytics are available per QR code or aggregated across a campaign.

FieldTypeDescription
total_scansnumberTotal number of times the QR code was scanned.
unique_scansnumberUnique devices that scanned the code (deduplicated by fingerprint).
by_cityobjectScan counts grouped by city (geo-IP lookup).
by_deviceobjectScan counts grouped by device type (iOS, Android, Desktop).
by_dayarrayDaily scan counts for the requested time range.
conversion_ratenumberPercentage of scans that resulted in a completed action (purchase, signup).
const whale = new WhaleClient({ apiKey: 'wk_live_...' })

// Get analytics for a specific QR code
const analytics = await whale.qr.analytics('spring-sale-tshirt', {
  from: '2026-03-01',
  to: '2026-03-10'
})

// Response
{
  "code": "spring-sale-tshirt",
  "total_scans": 1842,
  "unique_scans": 1523,
  "conversion_rate": 0.124,
  "by_city": {
    "New York": 342,
    "Los Angeles": 281,
    "Chicago": 198,
    "Houston": 145
  },
  "by_device": {
    "iOS": 1104,
    "Android": 687,
    "Desktop": 51
  },
  "by_day": [
    { "date": "2026-03-01", "scans": 142 },
    { "date": "2026-03-02", "scans": 198 },
    { "date": "2026-03-03", "scans": 231 }
  ]
}

// Get campaign-level analytics
const campaign = await whale.qr.campaignAnalytics('spring_sale_2026')

Landing Pages

Instead of redirecting directly to a URL, QR codes can show a customizable landing page with a title, description, image, and call-to-action button. Landing pages are mobile-optimized and hosted on WhaleTools infrastructure.

const whale = new WhaleClient({ apiKey: 'wk_live_...' })

// Create a QR code with a landing page
const qr = await whale.qr.create({
  code: 'menu-spring-2026',
  type: 'landing_page',
  landing_page: {
    title: 'Spring Menu 2026',
    description: 'Explore our new seasonal dishes crafted with locally sourced ingredients.',
    image_url: 'https://your-store.com/images/spring-menu-hero.jpg',
    cta_text: 'View Full Menu',
    cta_url: 'https://your-store.com/menu/spring-2026',
    background_color: '#1a1a2e',
    text_color: '#ffffff',
    accent_color: '#0A84FF'
  },
  campaign_name: 'spring_menu_launch'
})

// Update landing page content
await whale.qr.update('menu-spring-2026', {
  landing_page: {
    title: 'Spring Menu 2026 — Updated',
    description: 'Now featuring 5 new desserts.'
  }
})

Expiration & Limits

Control how long a QR code remains active and how many times it can be scanned. Expired or maxed-out QR codes show a configurable fallback message instead of redirecting.

const whale = new WhaleClient({ apiKey: 'wk_live_...' })

// Create a limited-use coupon QR code
const qr = await whale.qr.create({
  code: 'flash-sale-50off',
  type: 'coupon',
  destination_url: 'https://your-store.com/checkout?coupon=FLASH50',
  expires_at: '2026-03-11T23:59:59Z',  // 24-hour flash sale
  max_scans: 500,                        // first 500 customers only
  expired_message: 'This offer has ended. Follow us for future deals!'
})

// Check remaining capacity
const status = await whale.qr.get('flash-sale-50off')
// { total_scans: 342, max_scans: 500, remaining: 158, status: "active" }

// Manually deactivate
await whale.qr.deactivate('flash-sale-50off')

// Reactivate with new limits
await whale.qr.update('flash-sale-50off', {
  status: 'active',
  max_scans: 1000,
  expires_at: '2026-03-15T23:59:59Z'
})

API Reference

FieldTypeDescription
codestringUnique identifier for the QR code (used in the scan URL).
typestringQR code type: product, coupon, landing_page, or custom.
destination_urlstringURL the QR code redirects to when scanned.
brand_colorstringHex color for branded QR code styling (e.g., "#0A84FF").
logo_urlstringURL of the logo image embedded in the QR code center.
campaign_namestringCampaign name for grouping analytics (e.g., "spring_sale_2026").
expires_atstringISO 8601 date when the QR code stops redirecting.
max_scansnumberMaximum number of scans before the QR code is deactivated.