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=svgScan 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.
| Field | Type | Description |
|---|---|---|
| total_scans | number | Total number of times the QR code was scanned. |
| unique_scans | number | Unique devices that scanned the code (deduplicated by fingerprint). |
| by_city | object | Scan counts grouped by city (geo-IP lookup). |
| by_device | object | Scan counts grouped by device type (iOS, Android, Desktop). |
| by_day | array | Daily scan counts for the requested time range. |
| conversion_rate | number | Percentage 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
| Field | Type | Description |
|---|---|---|
| code | string | Unique identifier for the QR code (used in the scan URL). |
| type | string | QR code type: product, coupon, landing_page, or custom. |
| destination_url | string | URL the QR code redirects to when scanned. |
| brand_color | string | Hex color for branded QR code styling (e.g., "#0A84FF"). |
| logo_url | string | URL of the logo image embedded in the QR code center. |
| campaign_name | string | Campaign name for grouping analytics (e.g., "spring_sale_2026"). |
| expires_at | string | ISO 8601 date when the QR code stops redirecting. |
| max_scans | number | Maximum number of scans before the QR code is deactivated. |