Skip to main content
Skip table of contents

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 members

    • Additional 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 isON’ - 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:

Screenshot 2024-05-04 at 11.38.22.png

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

  1. User scans badge using mobile device camera

  2. Mobile app receives raw value (e.g., "1001610300685348" or "ABC123")

  3. 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):

CODE
{
  "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):

CODE
{
  "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)

CODE
$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

CODE
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

CODE
// 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:

CODE
{
  "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

CODE
// 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_code table

  • No 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:

CODE
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:

CODE
{"scan_id": 67890, "barcode": "ABC123"}

Backend:

  1. getInstanceCached(67890) → ✅ Found

  2. Skip barcode lookup (fast!)

  3. Save to account_scans:

    • scan_id = 67890

    • code = "ABC123"

Result: ✅ Success, ~10ms


Scenario B: Mobile Didn't Resolve + Barcode Found (Slow Path)

Request:

CODE
{"scan_id": "1001610300685348", "barcode": "1001610300685348"}

Backend:

  1. getInstanceCached(1001610300685348) → ❌ Not found (huge value, skipped by our fix)

  2. Search account_attr for barcode → ✅ Found account 67890

  3. Replace scan_id = 67890

  4. Save to account_scans:

    • scan_id = 67890

    • code = "1001610300685348"

Result: ✅ Success, ~50ms (slower due to barcode search)


Scenario C: Unknown Barcode + Any Code Scans Enabled

Request:

CODE
{"scan_id": "999999999", "barcode": "999999999"}

Backend:

  1. getInstanceCached(999999999) → ❌ Not found

  2. Search barcode → ❌ Not found

  3. Check any_scans config → ✅ Enabled

  4. Save to account_scans_code:

    • code = "999999999"

    • event_id = 41

Result: ✅ Success (unknown code captured)


Scenario D: Unknown Barcode + Any Code Scans Disabled

Request:

CODE
{"scan_id": "999999999", "barcode": "999999999"}

Backend:

  1. getInstanceCached(999999999) → ❌ Not found

  2. Search barcode → ❌ Not found

  3. Check any_scans config → ❌ Disabled

  4. Return error

Response:

CODE
{
  "error": true,
  "code": 2,
  "msg": "barcode not found in DB"
}

Result: ❌ Error (scan rejected)


Scenario E: Special QR Code Format

Request:

CODE
{"scan_id": "67890, 11111, John", "barcode": "67890, 11111, John"}

Backend:

  1. getInstanceCached("67890, 11111, John") → ❌ Not found (not numeric)

  2. Search barcode → ❌ Not found (exact match fails)

  3. Parse QR format:

    • Extract accountId = 67890

    • Extract barcode = 11111

    • Search: account_id = 67890 AND barcode_new = '11111' → ✅ Found

  4. Save to account_scans:

    • scan_id = 67890

    • code = "67890, 11111, John"

Result: ✅ Success (QR format parsed)


5. Database Schema

account

CODE
id          INTEGER PRIMARY KEY  -- Max: 2,147,483,647

account_attr

CODE
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

CODE
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

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

barcode_new

"320635686543"

Event-printed badge barcode

External QR

external_qr

"1001610300685348"

External system QR code

External Barcode

external_barcode

"XYZ999"

External system barcode

External Code

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:

CODE
{"scan_id": "5000", "barcode": "5000"}

Backend:

  1. getInstanceCached(5000) → ✅ Finds User A (wrong person!)

  2. 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:

CODE
{"protected_scan_id_filter": false}
CODE
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

  1. Normal scan (mobile resolved):

CODE
curl -X POST http://api/scanBadge \
  -H "Content-Type: application/json" \
  -d '{"scan_id": 67890, "barcode": "ABC123", "event_id": 41, ...}'
  1. External QR (mobile didn't resolve):

CODE
curl -X POST http://api/scanBadge \
  -H "Content-Type: application/json" \
  -d '{"scan_id": "1001610300685348", "barcode": "1001610300685348", "event_id": 41, ...}'
  1. Unknown code (any_scans enabled):

CODE
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:

CODE
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:

CODE
-- 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

CODE
if ($scanId <= 2147483647) {
    $scannedAccount = Account::getInstanceCached((int)$scanId);
}

11. Summary

Key Points:

  1. Mobile app tries to resolve barcode → account ID locally

  2. Backend receives either:

    • scan_id = account_id (resolved) → Fast path

    • scan_id = barcode (not resolved) → Slow path via barcode lookup

  3. Configuration options control behavior:

    • protected_scan_id_filter → Force barcode lookup only

    • any_scans → Allow/reject unknown barcodes

  4. Integer overflow fix prevents crash on huge barcode values

  5. Multiple barcode types supported (barcode_new, external_qr, etc.)

Flow Diagram:

CODE
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

 

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.