The developer's guide to QR code tracking APIs

Technical guide to QR code tracking APIs — how scan events work, what data you can collect, how to build analytics dashboards, and how to integrate QR tracking into your application with Linkbreakers' REST API and SDKs.

Developer
10 min read
By Laurent Schaffner
Updated June 6, 2026

Short answer

This article explains the developer's guide to qr code tracking apis with practical guidance, limits, and implementation details so you can apply it consistently.

QR code tracking works by routing scans through a redirect layer that captures event data before sending the visitor to the final destination. Each scan generates an event record containing device information, geographic location, timestamp, and a visitor fingerprint. The Linkbreakers API exposes this data through REST endpoints for events, visitors, and workspace metrics, enabling developers to build custom analytics dashboards, integrate with business intelligence tools, and trigger real-time workflows from scan activity.

Quick summary

  • Understand the technical flow from QR code scan to event record to analytics pipeline
  • Learn what data a scan captures: device type, OS, browser, geographic location, ISP, ASN, timestamp, and visitor fingerprint
  • Query the events API to retrieve individual scan records with filtering and pagination
  • Use the workspace metrics endpoint for aggregated analytics across all links and QR codes
  • Implement visitor identification using the LBID parameter for cross-session attribution
  • Set up webhooks for real-time notifications when visitors complete workflow steps
  • Follow code examples in TypeScript and Python for common integration patterns
  • Apply rate limiting best practices and error handling for production systems

How QR code tracking works technically

Every trackable QR code encodes a short URL rather than the final destination. This indirection is the mechanism that enables tracking. The sequence looks like this:

JavaScript
Visitor scans QR code
    → Device opens short URL (e.g., https://lbrk.co/abc123)
    → Linkbreakers redirect server receives the HTTP request
    → Server extracts data from request headers and IP address
    → Server creates an event record in the database
    → Server issues an HTTP 302 redirect to the final destination
    → Visitor arrives at the destination page

This entire process adds less than 100 milliseconds of latency. The visitor perceives an instant redirect while the tracking system captures a complete event record.

The redirect server extracts data from three sources:

  1. HTTP headers -- User-Agent for device and browser identification, Accept-Language for language preferences, Referer for scan source context
  2. IP address -- converted to geographic location (country, region, city) through IP geolocation, and resolved to ISP and ASN (Autonomous System Number) for network provider identification
  3. Request metadata -- the short URL slug identifies which link was scanned, the timestamp records when, and any query parameters (including lbid) carry attribution data

What a scan event record contains

Each scan generates an event object with the following data:

Field Description Example
id Unique event identifier (UUID) 0a2d9a3c-3f5a-483e-93e1-10b5bd04ac5b
deviceId Identifier for the device that scanned ec1d32f1-e5aa-4b12-8ad2-26da282b7261
deviceType Category of device DEVICE_TYPE_MOBILE, DEVICE_TYPE_DESKTOP, DEVICE_TYPE_TABLET
os Operating system iOS 18.2, Android 15, Windows 11
browser Browser application Safari, Chrome, Samsung Internet
country ISO 3166-1 country code US, DE, JP
region Administrative region California, Bavaria, Tokyo
city City name San Francisco, Munich, Shibuya
isp Internet service provider Comcast, Deutsche Telekom
asn Autonomous system number AS7922, AS3320
timestamp UTC timestamp of the scan 2026-06-05T14:32:18Z
linkId The link that was scanned f47ac10b-58cc-4372-a567-0e02b2c3d479
visitorId Persistent visitor identifier a1b2c3d4-e5f6-7890-abcd-ef1234567890

Geographic data is derived from IP addresses and provides city-level accuracy. It is not GPS-level precision. Visitors behind VPNs or corporate proxies may show the location of the VPN exit node or proxy server rather than their physical location.

Visitor fingerprinting and deduplication

Not every scan represents a unique person. The same visitor may scan a QR code multiple times, or scan different QR codes across your campaigns. Linkbreakers uses device fingerprinting to deduplicate visitors and build persistent profiles.

The fingerprint combines device characteristics -- browser type, operating system, screen resolution, language settings, and other signals -- into a device identifier. When the same device scans any QR code in your workspace, the system links the new event to the existing visitor profile.

This creates two distinct metrics:

  • Scans (events): the total number of times your QR codes were scanned
  • Visitors: the number of unique people who scanned, after deduplication

The distinction matters for analytics. A QR code on a restaurant menu might show 500 scans but only 200 visitors, indicating repeat usage. A conference badge QR code might show 50 scans and 48 visitors, indicating mostly first-time exchanges.

The Linkbreakers events API

The events API provides access to individual scan records. Use it when you need granular, event-level data for custom analytics, audit trails, or real-time processing.

Listing events

TypeScript
// TypeScript SDK
import { Configuration, EventsApi } from 'linkbreakers';

const config = new Configuration({
  accessToken: process.env.LINKBREAKERS_SECRET_KEY,
  basePath: 'https://api.linkbreakers.com',
});

const eventsApi = new EventsApi(config);

// List recent events across all links
const response = await eventsApi.eventsServiceList({
  pageSize: 50,
});

for (const event of response.events) {
  console.log(`${event.timestamp} | ${event.city}, ${event.country} | ${event.deviceType}`);
}
Python
# Python SDK
from linkbreakers import Configuration, EventsApi

config = Configuration(
    access_token=os.environ["LINKBREAKERS_SECRET_KEY"],
    host="https://api.linkbreakers.com",
)

events_api = EventsApi(config)

# List recent events across all links
response = events_api.events_service_list(page_size=50)

for event in response.events:
    print(f"{event.timestamp} | {event.city}, {event.country} | {event.device_type}")

Filtering events

Events can be filtered by link, tag, date range, and other criteria. This enables targeted queries like "show me all scans for links tagged conference-2026 from the last 7 days."

The API uses cursor-based pagination. Each response includes a nextPageToken that you pass to the subsequent request to retrieve the next page of results. This approach handles large datasets efficiently without the offset-based problems of skipping records.

The workspace metrics API

While the events API returns individual records, the metrics API returns aggregated data for your entire workspace. Use it for dashboard summaries, executive reporting, and trend analysis.

TypeScript
// TypeScript SDK
import { Configuration, MetricsApi } from 'linkbreakers';

const config = new Configuration({
  accessToken: process.env.LINKBREAKERS_SECRET_KEY,
  basePath: 'https://api.linkbreakers.com',
});

const metricsApi = new MetricsApi(config);

const metrics = await metricsApi.metricsServiceGetWorkspaceMetrics({});

console.log(`Total scans: ${metrics.totalScans}`);
console.log(`Unique visitors: ${metrics.totalVisitors}`);
console.log(`Active links: ${metrics.totalLinks}`);
Python
# Python SDK
from linkbreakers import Configuration, MetricsApi

config = Configuration(
    access_token=os.environ["LINKBREAKERS_SECRET_KEY"],
    host="https://api.linkbreakers.com",
)

metrics_api = MetricsApi(config)

metrics = metrics_api.metrics_service_get_workspace_metrics()

print(f"Total scans: {metrics.total_scans}")
print(f"Unique visitors: {metrics.total_visitors}")
print(f"Active links: {metrics.total_links}")

The metrics endpoint returns workspace-wide aggregates. For link-specific analytics, query the events API filtered by link ID and aggregate on your side, or use the dashboard for visual breakdowns.

Visitor identification with LBID

The LBID (Linkbreakers ID) is a base64-encoded identifier that connects a scan event to all subsequent visitor interactions within a workflow. When conversion tracking is enabled, the LBID is appended to the final destination URL as a query parameter:

JavaScript
https://yoursite.com/landing-page?lbid=2VqKhjxtKwC7pBbg

This lets your application attribute conversions back to specific QR code scans. The implementation pattern:

  1. Extract the LBID from the URL query parameters when a visitor lands on your page
  2. Store it in your session or database alongside the visitor's actions
  3. Report conversions back to Linkbreakers by calling the visitors API with the LBID
TypeScript
// Extract LBID on your landing page
const urlParams = new URLSearchParams(window.location.search);
const lbid = urlParams.get('lbid');

if (lbid) {
  // Store for conversion attribution
  sessionStorage.setItem('lbid', lbid);
}

// Later, when the visitor converts (e.g., makes a purchase)
const storedLbid = sessionStorage.getItem('lbid');
if (storedLbid) {
  // Use the Linkbreakers API to report the conversion
  await fetch('https://api.linkbreakers.com/v1/visitors/identify', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      lbid: storedLbid,
      attributes: {
        email: customer.email,
        converted: true,
        order_value: order.total,
      },
    }),
  });
}

The LBID is unique to each scan event. If the same person scans your QR code twice, they get two different LBIDs. However, both events link to the same visitor profile through device fingerprinting, so you maintain a unified view of the person.

Querying visitor profiles

The visitors API provides access to persistent visitor profiles that aggregate data across multiple scan events. Each visitor profile includes:

  • All events (scans) associated with the visitor
  • Device information from each event
  • Geographic data from each scan location
  • Form data collected through workflow steps
  • Lead score calculated from engagement patterns
  • Custom attributes set through the API
TypeScript
// TypeScript SDK
import { Configuration, VisitorsApi } from 'linkbreakers';

const config = new Configuration({
  accessToken: process.env.LINKBREAKERS_SECRET_KEY,
  basePath: 'https://api.linkbreakers.com',
});

const visitorsApi = new VisitorsApi(config);

// List visitors with pagination
const response = await visitorsApi.visitorsServiceList({
  pageSize: 25,
});

for (const visitor of response.visitors) {
  console.log(`Visitor ${visitor.id} | Score: ${visitor.leadScore} | Events: ${visitor.eventCount}`);
}

// Get a specific visitor by ID
const visitor = await visitorsApi.visitorsServiceGet({
  id: 'visitor-uuid-here',
});
Python
# Python SDK
from linkbreakers import Configuration, VisitorsApi

config = Configuration(
    access_token=os.environ["LINKBREAKERS_SECRET_KEY"],
    host="https://api.linkbreakers.com",
)

visitors_api = VisitorsApi(config)

# List visitors with pagination
response = visitors_api.visitors_service_list(page_size=25)

for visitor in response.visitors:
    print(f"Visitor {visitor.id} | Score: {visitor.lead_score} | Events: {visitor.event_count}")

Webhook integration for real-time notifications

Polling the events API works for batch analytics but introduces latency. For real-time reactions to scan activity -- sending a Slack notification when a VIP scans your card, updating a CRM when a lead fills out a form, triggering an inventory check when a product QR is scanned -- use webhooks.

Webhooks trigger when visitors complete workflow steps configured as EXIT actions. Linkbreakers sends an HTTP POST to your endpoint with a JSON payload containing the full event and visitor data.

Webhook payload structure

JSON
{
  "event": {
    "id": "0a2d9a3c-3f5a-483e-93e1-10b5bd04ac5b",
    "deviceId": "ec1d32f1-e5aa-4b12-8ad2-26da282b7261",
    "device": {
      "deviceType": "DEVICE_TYPE_MOBILE",
      "os": "iOS 18.2",
      "browser": "Safari"
    },
    "country": "US",
    "city": "San Francisco",
    "timestamp": "2026-06-05T14:32:18Z"
  },
  "visitor": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "attributes": {
      "email": "visitor@example.com",
      "name": "Jane Doe"
    }
  },
  "link": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "name": "Conference Badge QR"
  }
}

Configuring webhooks

Create webhooks through the webhook dashboard or the API. Key configuration:

  • Endpoint URL: the HTTPS URL where Linkbreakers sends POST requests
  • Link targeting: optionally restrict the webhook to fire only for specific links or QR codes, enabling different automation for different campaigns
  • Retry policy: failed deliveries are retried automatically. After repeated failures, the webhook is auto-disabled to protect your infrastructure

Webhook capacity varies by plan: 1 endpoint on Free, 10 on Pro, unlimited on Enterprise.

Handling webhooks in your application

TypeScript
// Express.js webhook handler
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks/linkbreakers', (req, res) => {
  const { event, visitor, link } = req.body;

  console.log(`Scan on "${link.name}" from ${event.city}, ${event.country}`);

  if (visitor.attributes?.email) {
    // Visitor identified -- sync to CRM
    syncToCRM({
      email: visitor.attributes.email,
      source: link.name,
      scannedAt: event.timestamp,
      location: `${event.city}, ${event.country}`,
    });
  }

  // Respond 200 to acknowledge receipt
  res.status(200).json({ received: true });
});
Python
# Flask webhook handler
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/linkbreakers', methods=['POST'])
def handle_webhook():
    data = request.json
    event = data['event']
    visitor = data['visitor']
    link = data['link']

    print(f"Scan on \"{link['name']}\" from {event['city']}, {event['country']}")

    if visitor.get('attributes', {}).get('email'):
        # Visitor identified -- sync to CRM
        sync_to_crm(
            email=visitor['attributes']['email'],
            source=link['name'],
            scanned_at=event['timestamp'],
            location=f"{event['city']}, {event['country']}",
        )

    return jsonify(received=True), 200

Always respond with a 2xx status code promptly. If your processing takes time, acknowledge the webhook immediately and process asynchronously.

Building a custom analytics dashboard

A common integration pattern is pulling data from the Linkbreakers API into a custom dashboard. Here is a minimal example that fetches workspace metrics and recent events:

TypeScript
// Dashboard data fetcher
import { Configuration, MetricsApi, EventsApi } from 'linkbreakers';

const config = new Configuration({
  accessToken: process.env.LINKBREAKERS_SECRET_KEY,
  basePath: 'https://api.linkbreakers.com',
});

async function getDashboardData() {
  const metricsApi = new MetricsApi(config);
  const eventsApi = new EventsApi(config);

  // Fetch in parallel
  const [metrics, recentEvents] = await Promise.all([
    metricsApi.metricsServiceGetWorkspaceMetrics({}),
    eventsApi.eventsServiceList({ pageSize: 20 }),
  ]);

  return {
    summary: {
      totalScans: metrics.totalScans,
      uniqueVisitors: metrics.totalVisitors,
      activeLinks: metrics.totalLinks,
    },
    recentScans: recentEvents.events.map(e => ({
      time: e.timestamp,
      location: `${e.city}, ${e.country}`,
      device: e.deviceType,
      link: e.linkId,
    })),
  };
}

For more advanced BI integration patterns -- connecting to Tableau, Power BI, or custom data warehouses -- see the Analytics API reference and the business intelligence integration guide.

Authentication

All API requests require a bearer token in the Authorization header:

JavaScript
Authorization: Bearer YOUR_WORKSPACE_TOKEN

Create tokens at Dashboard > API Tokens. Use separate tokens for development and production environments. Tokens remain valid until explicitly revoked.

For a detailed comparison of token types, see secret key vs publishable key and API authentication methods.

Rate limits and best practices

The API implements workspace-level rate limiting that varies by plan. Headers on each response indicate your current usage:

JavaScript
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 994
X-RateLimit-Reset: 1717600000

Best practices for production integrations:

  • Cache aggressively. Workspace metrics change with every scan but do not need to be refreshed more than once per minute for most dashboards.
  • Use pagination correctly. Always use the nextPageToken from the response rather than constructing page offsets manually. Cursor-based pagination is stable against concurrent inserts.
  • Handle 429 responses. When rate-limited, back off using the X-RateLimit-Reset header value. Do not retry immediately in a tight loop.
  • Use webhooks for real-time. If you need to react to scans within seconds, set up webhooks instead of polling the events API.
  • Store tokens securely. Use environment variables or a secrets manager. Never commit tokens to source control.
  • Filter server-side. Use API query parameters to filter events by link, tag, or date range rather than fetching all events and filtering client-side.
  • Implement idempotency. Event IDs are UUIDs. Use them to deduplicate if your webhook handler might process the same event twice due to retries.

SDKs and API reference

Linkbreakers provides official SDKs in five languages:

Language Package Quickstart
TypeScript linkbreakers TypeScript SDK quickstart
Python linkbreakers Python SDK quickstart
Go linkbreakers-go Go SDK quickstart
Java linkbreakers-java Java SDK quickstart
Rust linkbreakers Rust SDK quickstart

Full API documentation with endpoint schemas and request/response examples is available at linkbreakers.com/help/api.

Frequently asked questions

How much latency does QR code tracking add? The redirect typically adds less than 100 milliseconds. The tracking is synchronous with the redirect -- the event is recorded and the visitor is redirected in the same request. There is no separate analytics call that could delay the user experience.

Can I track scans without using the Linkbreakers dashboard? Yes. The API provides complete programmatic access to all tracking data. You can build an entirely custom analytics experience using the events, visitors, and metrics endpoints without ever opening the dashboard.

How accurate is the geographic location data? IP-based geolocation provides city-level accuracy for most scans. It is not GPS precision. Visitors using VPNs, corporate proxies, or mobile carriers with centralized IP pools may show inaccurate locations. Accuracy varies by region and ISP.

What happens to tracking data if a QR code destination changes? Historical event data is preserved. If you change a link's destination, future scans are tracked under the same link ID with the new destination. Analytics continuity is maintained.

Can I export raw event data for use in external tools? Yes. The events API supports paginated export of all scan records. For large-scale exports, use time-range filtering to batch requests efficiently. Response format is JSON, which can be transformed for import into BI tools, data warehouses, or spreadsheets.

How long is tracking data retained? Data retention varies by plan. Consult your plan details for specific retention windows. API access to historical data follows the same retention policy as the dashboard.

Can I track scans across multiple QR codes as a single campaign? Yes. Use tags to group links and QR codes into campaigns. Filter the events API by tag to aggregate analytics across all QR codes in a campaign.

Limits and caveats

  • Feature availability and limits can vary by plan and workspace setup.
  • Rate limits are enforced at the workspace level. High-volume integrations should implement backoff and caching.
  • Geographic accuracy depends on IP geolocation and is not GPS-level. VPNs and proxies can affect accuracy.
  • Visitor fingerprinting is probabilistic. Users who clear browser data or switch devices may appear as new visitors.
  • Webhook delivery is best-effort with automatic retries. Design your integration to handle occasional duplicate deliveries.
  • Results depend on correct implementation, attribution setup, and data quality controls.
  • Regulatory and privacy obligations vary by jurisdiction and use case.

About the Author

LS

Laurent Schaffner

Founder & Engineer at Linkbreakers

Passionate about building tools that help businesses track and optimize their digital marketing efforts. Laurent founded Linkbreakers to make QR code analytics accessible and actionable for companies of all sizes.