Using the Badge Scanner functionality
Feature Purpose:
Utilize the badge scanner feature within the mobile application to scan badges, gather leads, and jot down notes after scanning contacts.
Working with this feature:
This is a core feature within the mobile app where users can use the built-in badge scanner to scan badges. The use of this functionality can be restricted based on visitor and/or exhibitor categories.
Badge scan types
1) Scanning: Available for use by attendees/visitors
2) Lead capture: Available for use by exhibitors and their team membersAdditional details can be found here: Lead Intelligence
Please note there is also the opportunity to offer custom lead questions for lead capture scanning
Scanning Settings (Admin panel → Networking & Matchmaking → Contact sharing → Scanning Settings):
1) Enable Scanned Me List - When this option is ‘ON’, an additional tab is displayed under “My Scans” → Scanned Me - available only for Participants accounts - displays the list of users who have scanned your badge.
2) Allow users to scan badges from colocated events - When this option is ‘ON’ - users have the opportunity to scan badges from other events (Note: The other events should be within the same Database) - For users from colocated events, they are displayed with the tag “Another event” and it is not possible to use networking features like manage meetings/messages for such accounts. Also the categories/roles of such scans will be displayed (if Show category/role in Scan lists in app turned on).
3) Allow users to scan any codes - When this option is ‘ON’, any scanned code will be saved and the information from such barcode or QR code will be displayed (By default if 2 and 3 are ‘OFF’ and if the user scans some code that does not exist in the database, “Not found” will displayed) for such records.Show Exhibitor Logo for team members in Scan lists in app - When this option is ‘ON’ for Team Members, scans will display the exhibitor logo (Company profile).
Show category/role in Scan lists in app - By default, roles/ categories will be displayed under each scan/scanned me and scanned profiles - However, we have an option to control the visibility of these categories/roles - Please refer to this article: APP || Option to hide Custom Participant categories anywhere it appears.
Please note that in case any settings were turned ON/OFF during the event, the scenarios will change and this will be explained in a separate article.
Networking & Matchmaking >>> Contact Sharing:

Important notes for Organisers:
Organisers can also download exhibitor scanning data in the Data Import/Export section through Exhibitors Scanned Contacts report.
Which exhibitors are able to scan badges can be controlled through the mobile app builder
Scanning works both online and offline. When scanning offline, data is saved locally until a connection is established.
Under Event Set Up > Networking & Matchmaking > Contact Sharing, Organizers can set which actions should count as a lead and whether the lead’s private information, such as email, should be hidden for any actions.
Badge Scan Flow Documentation
Overview
The badge scanning feature allows exhibitors/staff to scan attendee badges (barcodes/QR codes) at events to capture leads. This document explains the complete flow from mobile app scan to backend processing.
1. Mobile App Flow
Scanning Process
User scans badge using mobile device camera
Mobile app receives raw value (e.g., "1001610300685348" or "ABC123")
Mobile attempts local resolution:
Searches local/synced database for barcode → account_id mapping
If found: Resolves to account ID
If not found: Uses raw scanned value
Payload Generation
When mobile CAN resolve (has account data synced):
{
"scan_id": 67890, // ← Found account ID
"barcode": "ABC123", // ← Original scanned barcode
"account_id": 45443, // Scanner's ID
"team_member_id": 45443, // Scanner's ID (duplicate)
"event_id": 41,
"token": "auth_token...",
"badge_scan": "true",
"time": 1678901234,
"rate": 0,
"new": true,
"contacted": false,
"notes": "",
"edit": 0,
"lead_questions": [],
"product": [],
"uuid": "device-uuid"
}
When mobile CANNOT resolve (not synced/offline):
{
"scan_id": "1001610300685348", // ← Raw scanned value
"barcode": "1001610300685348", // ← Same (couldn't resolve)
"account_id": 45443,
// ... rest same
}
Key indicator: If scan_id === barcode, mobile couldn't resolve.
2. Backend Processing Flow
Entry Point
ApiController::scanBadgeAction() (line 20736) ↓ ApiController::processAccountScan() (line ~21025)
Step 1: Account Lookup by ID
Purpose: Try to use scan_id as direct account ID (fast path)
$scannedAccount = false;
if ($scanId) {
// FIX: Check PostgreSQL INTEGER limit
if ($scanId <= 2147483647) {
$scannedAccount = Account::getInstanceCached((int)$scanId);
}
if (!$scannedAccount) {
$exit = true;
$errorCode = 1;
$reason[] = 'Wrong scan id';
}
}
Possible outcomes:
✅ Account found → Use it (fast path)
❌ Account not found → Continue to barcode lookup
❌ scan_id too large (> 2,147,483,647) → Skip query, continue to barcode lookup
Step 2: Barcode Lookup
Purpose: If account lookup failed, search by barcode value
if (!$scannedAccount && $barcode) {
// Check "Any Code Scans" config
$anyCodeScans = ExpoConfig::getJsonValue(..., 'any_scans');
// Search in account_attr table
$account = AccountAttr::getAccountForScan($barcode);
// Searches: attr_key IN ('external_qr', 'external_barcode', 'barcode_new', 'external_code')
// AND attr_val = $barcode
if (!$account) {
// Try special QR format: "accountId, barcode, data"
$account = AccountAttr::getAccountForScanByQrCode($barcode);
}
if ($account && $account->id) {
$scanId = $account->id; // Replace with found account ID
$exit = false;
} elseif ($anyCodeScans) {
// Save to account_scans_code table
return $this->addAnyScanRow(...);
} else {
$exit = true;
$errorCode = 2;
$reason[] = 'barcode not found in DB';
}
}
Step 3: Save to account_scans
If account was found (either by ID or barcode lookup), save the scan record.
3. Configuration Options
3.1 protected_scan_id_filter
Location: additionalRouterSettings->protected_scan_id_filter Type: Boolean (checked with isset()) Effect: Forces scan_id to 0, completely bypassing account ID lookup
// Line 21051-21053
if (isset($this->additionalRouterSettings->protected_scan_id_filter)) {
$scanId = 0; // Force barcode-only lookup
}
When to use:
Security: Don't trust scan_id from mobile app
Testing: Force all scans through barcode lookup path
Example config:
{
"protected_scan_id_filter": true
}
Note: Setting to false won't work! isset() checks key existence, not value. Must remove key entirely to disable.
3.2 any_scans (Any Code Scans Mode)
Location: expo_config table → shared_settings.any_scans Type: Boolean Effect: Allows saving unregistered barcodes to account_scans_code table
// Line 21125-21130
$anyCodeScans = (bool)ExpoConfig::getJsonValue(
$event_id,
ExpoConfigBlockEnum::GENERAL,
ExpoConfigNameEnum::SHARED_SETTINGS,
ExpoConfigFieldEnum::SHARED_SETTINGS_ANY_SCANS
);
When enabled (true):
Unknown barcodes → Saved to
account_scans_codetableNo error returned
Captures all scans even for non-registered attendees
When disabled (false):
Unknown barcodes → Error: "barcode not found in DB" (code 2)
Scan rejected
Only registered attendees can be scanned
Example SQL:
SELECT value FROM expo_config
WHERE event_id = 41
AND block = 'general'
AND name = 'shared_settings'
AND value::jsonb->>'any_scans' = 'true';
4. Processing Scenarios
Scenario A: Mobile Resolved + Account Exists (Fast Path)
Request:
{"scan_id": 67890, "barcode": "ABC123"}
Backend:
getInstanceCached(67890)→ ✅ FoundSkip barcode lookup (fast!)
Save to
account_scans:scan_id = 67890code = "ABC123"
Result: ✅ Success, ~10ms
Scenario B: Mobile Didn't Resolve + Barcode Found (Slow Path)
Request:
{"scan_id": "1001610300685348", "barcode": "1001610300685348"}
Backend:
getInstanceCached(1001610300685348)→ ❌ Not found (huge value, skipped by our fix)Search
account_attrfor barcode → ✅ Found account 67890Replace
scan_id = 67890Save to
account_scans:scan_id = 67890code = "1001610300685348"
Result: ✅ Success, ~50ms (slower due to barcode search)
Scenario C: Unknown Barcode + Any Code Scans Enabled
Request:
{"scan_id": "999999999", "barcode": "999999999"}
Backend:
getInstanceCached(999999999)→ ❌ Not foundSearch barcode → ❌ Not found
Check
any_scansconfig → ✅ EnabledSave to
account_scans_code:code = "999999999"event_id = 41
Result: ✅ Success (unknown code captured)
Scenario D: Unknown Barcode + Any Code Scans Disabled
Request:
{"scan_id": "999999999", "barcode": "999999999"}
Backend:
getInstanceCached(999999999)→ ❌ Not foundSearch barcode → ❌ Not found
Check
any_scansconfig → ❌ DisabledReturn error
Response:
{
"error": true,
"code": 2,
"msg": "barcode not found in DB"
}
Result: ❌ Error (scan rejected)
Scenario E: Special QR Code Format
Request:
{"scan_id": "67890, 11111, John", "barcode": "67890, 11111, John"}
Backend:
getInstanceCached("67890, 11111, John")→ ❌ Not found (not numeric)Search barcode → ❌ Not found (exact match fails)
Parse QR format:
Extract
accountId = 67890Extract
barcode = 11111Search:
account_id = 67890 AND barcode_new = '11111'→ ✅ Found
Save to
account_scans:scan_id = 67890code = "67890, 11111, John"
Result: ✅ Success (QR format parsed)
5. Database Schema
account
id INTEGER PRIMARY KEY -- Max: 2,147,483,647
account_attr
account_id INTEGER -- Foreign key to account.id
attr_key VARCHAR -- 'barcode_new', 'external_qr', 'external_barcode', 'external_code'
attr_val TEXT -- Barcode value (no limit!)
account_scans
id SERIAL PRIMARY KEY
account_id INTEGER -- Scanner's ID
scan_id INTEGER -- Scanned person's account ID (Max: 2,147,483,647)
code TEXT -- Original barcode value
event_id INTEGER
add_time INTEGER
-- ... other fields
account_scans_code
id SERIAL PRIMARY KEY
code VARCHAR -- Barcode value (for unknown codes)
event_id INTEGER
account_id INTEGER -- Scanner's ID
-- ... other fields
6. Barcode Types Supported
Type | attr_key | Example Value | Purpose |
|---|---|---|---|
Standard Barcode |
| "320635686543" | Event-printed badge barcode |
External QR |
| "1001610300685348" | External system QR code |
External Barcode |
| "XYZ999" | External system barcode |
External Code |
| "CODE123" | Generic external code |
All types searched by AccountAttr::getAccountForScan() (line 418)
7. Edge Cases & Known Issues
Edge Case 1: Barcode Value Equals Different Account ID (Collision)
Scenario:
User A:
account_id = 5000,barcode = "999888"User B:
account_id = 8888,barcode = "5000"
Scan User B's badge:
{"scan_id": "5000", "barcode": "5000"}
Backend:
getInstanceCached(5000)→ ✅ Finds User A (wrong person!)Saves scan attributed to User A ❌
Risk: Low (most barcodes are 12-digit EAN codes, unlikely to collide)
Mitigation: None currently implemented. Would require validating found account owns the barcode.
Edge Case 2: isset() Behavior with protected_scan_id_filter
Setting to false won't disable:
{"protected_scan_id_filter": false}
if (isset($this->additionalRouterSettings->protected_scan_id_filter)) {
// Still TRUE! isset() checks key existence, not value
$scanId = 0;
}
Must remove key entirely to disable.
8. Performance Considerations
Path | Operations | Typical Time |
|---|---|---|
Fast Path | 1 cache lookup (account by ID) | ~10ms |
Slow Path | 1 cache lookup (fails) + 1-3 DB queries (barcode search) | ~50-100ms |
Any Code Scans | Same as slow path + 1 INSERT to account_scans_code | ~60-120ms |
Optimization: Mobile app should sync data regularly to maximize fast path usage.
9. Testing
Test Different Scenarios
Normal scan (mobile resolved):
curl -X POST http://api/scanBadge \
-H "Content-Type: application/json" \
-d '{"scan_id": 67890, "barcode": "ABC123", "event_id": 41, ...}'
External QR (mobile didn't resolve):
curl -X POST http://api/scanBadge \
-H "Content-Type: application/json" \
-d '{"scan_id": "1001610300685348", "barcode": "1001610300685348", "event_id": 41, ...}'
Unknown code (any_scans enabled):
curl -X POST http://api/scanBadge \
-H "Content-Type: application/json" \
-d '{"scan_id": "999999", "barcode": "999999", "event_id": 41, ...}'
Check Configuration
Query expo_config for any_scans:
SELECT value::jsonb->>'any_scans' as any_scans_enabled
FROM expo_config
WHERE event_id = 41
AND block = 'general'
AND name = 'shared_settings';
Check router settings for protected_scan_id_filter:
-- Config stored in app_settings.additional_params or similar
SELECT additional_params::jsonb->>'protected_scan_id_filter'
FROM app_settings
WHERE event_id = 41;
10. Files Modified
/application/backend/app/controllers/ApiController.php
Line 21090-21093: Integer overflow fix
if ($scanId <= 2147483647) {
$scannedAccount = Account::getInstanceCached((int)$scanId);
}
11. Summary
Key Points:
Mobile app tries to resolve barcode → account ID locally
Backend receives either:
scan_id = account_id(resolved) → Fast pathscan_id = barcode(not resolved) → Slow path via barcode lookup
Configuration options control behavior:
protected_scan_id_filter→ Force barcode lookup onlyany_scans→ Allow/reject unknown barcodes
Integer overflow fix prevents crash on huge barcode values
Multiple barcode types supported (barcode_new, external_qr, etc.)
Flow Diagram:
Scan Badge
↓
Mobile App
├─→ Found in local DB → scan_id = account_id
└─→ Not found → scan_id = barcode
↓
Backend API
├─→ protected_scan_id_filter? → Force barcode lookup
├─→ scan_id valid? → getInstanceCached()
│ ├─→ Found → Use account (FAST)
│ └─→ Not found → Search barcode (SLOW)
└─→ Barcode lookup
├─→ Found → Use account
└─→ Not found
├─→ any_scans enabled? → Save to account_scans_code
└─→ any_scans disabled → Error
Troubleshooting:
Question: As an event organizer, I don’t want to see repeated badge scans, can I disable this feature?
Answer: Yes, 'Show repeated badge scans is disabled by default and needs to be turned on in case it needs to be used.
Question: I scanned a visitor’s badge but it was not captured as a lead and does not show up under the ‘Scanned badges list’, how can I fix this?
Answer: There could be a sync delay as this data is automatically saved in ExpoPlatform’s database at the same time, eliminating the possibility of any lost leads should the exhibitor forget to click the Save button.
Question: I am a team member and after I scanned a visitor’s badge, there was no option to take notes.
Answer: Make sure that ‘Lead capture’ is enabled for all. The notes on the leads can be only added if Lead Capture is enabled for all, it can be enabled from Exhibitor Manual>>Global Settings>>Lead Capture

Question: Can I scan a badge directly using my phone’s camera?
Answer: It is possible to scan a badge outside of the event mobile app, for example with a phone camera or QR code scanning app. If:
Badge is scanned and user has our app installed, the app is opened, user is asked to login (if unlogged), app opens Scan Badges page and regular flow of badge scanning proceeds.
Badge is scanned and user has not installed our app, the event is opened in mobile browser, user is asked to login (if unlogged) and then redirected to the public page of scanned user.
Badge is scanned and badge has an external QR code and user has not installed our app, then nothing happens.
Question: Can an exhibitor export leads directly from the app?
Answer: In addition to viewing scanned badges within the app, exhibitors can also export their leads as follows:
Method 1:
In the mobile app, on the Scan Badges page, there is an export link at the top of the screen. When clicked, this will trigger an email to that team member/exhibitor with an attached spreadsheet of leads that includes (depending on organizer configuration):

Name
Email
Company
Position
Address
Phone
Star Rating
Notes added
Products the lead was interested in
Type of client (new/old)
Time of scan
The email will also contain a file for Scanned Me that includes similar data (depending on the organizer configuration).
Method 2:
On the web, under Profile Info, there is a Download Leads and Download Scanned Me button. Clicking on either button triggers an immediate download to the user’s computer.

With both methods, it’s important to note:
When accessed using the exhibitor profile this download will include all web and all scanned leads for all team members. The downloaded spreadsheet will have an additional column for Lead Owner(Team Member who has scanned the lead)
When accessed using a personal profile this download will include all web and all scanned leads for just that person.
When the user scans a random QR code from the internet, the app shows- “not found” in the scan list page.
Question: I suddenly lost internet on my phone but need to scan a few visitor badges, can the scanning work offline?
Answer: Scanning works both online and offline. When scanning offline, data is saved locally until a connection is established.
Question: Why is the category/role of the user displayed under the ‘Scanned Badges’ section?
Answer:
Before
| Now | Benefits |
By default the logo & category/role of the user was displayed on the cards, in the scanned badges section | New setting: Event Setup → Networking → Contact Sharing By default: OFF | More control on what should/should not be visible to the end users
|