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:
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:
- HTTP headers --
User-Agentfor device and browser identification,Accept-Languagefor language preferences,Refererfor scan source context - 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
- 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 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 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 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 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:
https://yoursite.com/landing-page?lbid=2VqKhjxtKwC7pBbg
This lets your application attribute conversions back to specific QR code scans. The implementation pattern:
- Extract the LBID from the URL query parameters when a visitor lands on your page
- Store it in your session or database alongside the visitor's actions
- Report conversions back to Linkbreakers by calling the visitors API with the LBID
// 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 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 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
{
"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
// 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 });
});
# 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:
// 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:
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:
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
nextPageTokenfrom 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-Resetheader 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
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.
Related Articles
How QR code tracking works: from scan to analytics
See the scan to redirect to analytics flow, the difference between static and dynamic QR codes, plus real-world examples and FAQs.
Programmatic QR code generation: API vs AI agents
Compare three approaches to programmatic QR code generation — REST API with SDKs, CLI automation, and AI agents via MCP. Understand when to use each method and how Linkbreakers supports all three.
How to use the Linkbreakers API
Complete guide to integrating with the Linkbreakers API - create QR codes, manage links, customize designs, track analytics, and automate workflows programmatically.
On this page
Need more help?
Can't find what you're looking for? Get in touch with our support team.
Contact Support