Overview
PassTru’s QR code check-in system enables fast, contactless attendee verification using automatically generated QR codes and browser-based scanning.
How It Works
QR Code Generation
Each attendee receives a unique QR code containing their portal URL when added to an event.
Attendee Receipt
QR codes are delivered via confirmation email or accessible through the attendee portal.
Check-In Scanning
Staff or attendees scan QR codes using the public check-in page’s camera scanner.
Instant Verification
System validates the code, marks attendance, and activates the attendee portal.
QR Code Generation
QR codes are generated client-side using the react-qr-code library:
src/pages/public/AttendeePortal.tsx
import QRCode from "react-qr-code" ;
< QRCode
value = { portalUrl }
size = { 200 }
bgColor = "#ffffff"
fgColor = "#000000"
level = "M"
/>
QR Code Content
Each QR code encodes the attendee’s unique portal URL:
https://yourdomain.com/{org-slug}/{event-slug}/attendee/{unique-id}
Example QR Code URL :https://passtru.com/acme-events/annual-gala-2026/attendee/AB12CD34
Scanning Methods
PassTru supports two check-in methods:
Camera-Based Scanning Uses the device camera to scan QR codes:
Opens device camera (requires permission)
Real-time QR detection
Automatic parsing and validation
Instant check-in feedback
Implementation :src/components/QrScanner.tsx
const scanner = new Html5Qrcode ( scannerId );
scanner . start (
{ facingMode: "environment" },
{ fps: 10 , qrbox: { width: 250 , height: 250 } },
( decodedText ) => {
onScan ( decodedText );
scanner . stop ();
}
);
Fallback Method For cases where camera scanning isn’t available:
Enter email address
Enter 8-character unique ID
Manual validation
Same verification process
Use Cases :
Camera permission denied
Poor lighting conditions
Damaged QR codes
Devices without cameras
Check-In Flow
1. Access Check-In Page
Navigate to the public check-in URL:
/{org-slug}/{event-slug}/welcome
2. Select Check-In Method
Two prominent buttons on the check-in page:
src/pages/public/PublicCheckIn.tsx
< Button onClick = { () => setMode ( "qr" ) } >
< QrCode className = "mr-3 h-6 w-6" />
Scan My QR Code
</ Button >
< Button variant = "outline" onClick = { () => setMode ( "manual" ) } >
< Keyboard className = "mr-3 h-6 w-6" />
Manual Check-In
</ Button >
The check-in process validates attendee identity and updates their status:
src/pages/public/PublicCheckIn.tsx
const performCheckIn = async ( attendeeUid : string , attendeeEmail ?: string ) => {
const body = {
event_id: event . id ,
unique_id: attendeeUid . trim (),
method: attendeeEmail ? "self_service" : "qr_scan" ,
... ( attendeeEmail && { email: attendeeEmail . trim () })
};
const { data , error } = await supabase . functions . invoke (
"public-checkin" ,
{ body }
);
// Handle response statuses
};
4. Check-In Responses
Success
Already Checked In
Not Found
Event Inactive
{
"status" : "success" ,
"attendee_name" : "John Doe" ,
"attendee_unique_id" : "AB12CD34"
}
Mark attendee as checked in
Set check-in timestamp
Activate attendee portal
Display success message
{
"status" : "already_checked_in" ,
"attendee_name" : "John Doe" ,
"attendee_unique_id" : "AB12CD34"
}
Show informational message
Display success screen anyway
Allow portal access
{
"status" : "not_found"
}
Display error message
Prompt to verify details
Remain on check-in screen
{
"status" : "event_inactive"
}
Check-in disabled by organizer
Show appropriate message
QR Scanner Component
The scanner uses html5-qrcode for browser-based QR detection:
src/components/QrScanner.tsx
import { Html5Qrcode } from "html5-qrcode" ;
export function QrScanner ({ onScan , onError , scanning , onStop }) {
const scannerRef = useRef < Html5Qrcode | null >( null );
useEffect (() => {
if ( ! scanning ) return ;
const scanner = new Html5Qrcode ( "qr-scanner-region" );
scannerRef . current = scanner ;
scanner . start (
{ facingMode: "environment" }, // Use rear camera
{ fps: 10 , qrbox: { width: 250 , height: 250 } },
( decodedText ) => {
onScan ( decodedText );
scanner . stop ();
}
). catch (( err ) => {
onError ?.( err . message );
});
return () => {
scanner . stop ();
scanner . clear ();
};
}, [ scanning ]);
return < div id = "qr-scanner-region" /> ;
}
Scanner Configuration
facingMode
string
default: "environment"
Camera selection: "environment" for rear camera, "user" for front
Frames per second for QR detection (10 FPS for optimal performance)
qrbox
object
default: "{ width: 250, height: 250 }"
Scanning area dimensions in pixels
URL Parsing
The system supports two QR code formats:
try {
const url = new URL ( decodedText );
const parts = url . pathname . split ( "/" ). filter ( Boolean );
// Expected: [org-slug, event-slug, "attendee", unique-id]
if ( parts . length >= 4 && parts [ 2 ] === "attendee" ) {
await performCheckIn ( parts [ 3 ]);
}
} catch { /* fallback to format 2 */ }
// If URL parsing fails, check if it's just the unique ID
if ( / ^ [ A-Z0-9 ] {6,10} $ / i . test ( decodedText . trim ())) {
await performCheckIn ( decodedText . trim ());
}
Both formats are supported for flexibility. Attendee portals generate full URLs, but manual codes can contain just the unique ID.
Check-In Methods Tracking
Each check-in records the method used:
const { error } = await supabase
. from ( "attendees" )
. update ({
checked_in: true ,
checked_in_at: new Date (). toISOString (),
checkin_method: "qr_scan" , // or "self_service", "manual"
portal_active: true
})
. eq ( "id" , attendeeId );
Method Types
qr_scan Scanned via QR code on public check-in page
self_service Manual entry on public check-in page
manual Manually checked in by event staff via Event Portal
Rate Limiting
The check-in endpoint includes rate limiting to prevent abuse:
src/pages/public/PublicCheckIn.tsx
if ( fnError ) {
const msg = fnError . message ;
if ( msg . includes ( "429" ) || msg . toLowerCase (). includes ( "too many" )) {
toast . error ( "Too many requests. Please wait a moment and try again." );
return ;
}
}
Rate Limit : Excessive check-in attempts from the same device will trigger a temporary block. Wait a few moments before retrying.
Post Check-In Experience
After successful check-in:
Success Message : Customizable via branding templates
Attendee Info : Display selected fields (name, table, seat, etc.)
Portal Link : Direct link to attendee’s personal portal
src/pages/public/PublicCheckIn.tsx
< Card className = "border-primary" >
< CardContent >
< PostCheckinTemplate
branding = { event . branding_postcheckin }
variables = { postCheckinVars }
/>
< Button asChild >
< a href = { `/ ${ orgSlug } / ${ eventSlug } /attendee/ ${ attendeeUniqueId } ` } >
View My Portal
</ a >
</ Button >
</ CardContent >
</ Card >
Camera Permissions
The scanner requires camera access:
Browser Prompt
Browser displays permission request when QR scanner is activated.
User Grant
User must allow camera access for scanning to work.
Error Handling
If denied, show error message and suggest manual entry option.
Permission Error Handling
src/components/QrScanner.tsx
if ( cameraError ) {
return (
< div className = "border-destructive/50 bg-destructive/10" >
< CameraOff className = "h-8 w-8 text-destructive" />
< p > Camera Error </ p >
< p > { cameraError } </ p >
< Button onClick = { onStop } > Close </ Button >
</ div >
);
}
Check-In Page Activation
Event Managers and Clients can toggle check-in page availability:
src/pages/event/CheckInManagement.tsx
const toggleCheckinPage = async () => {
const newValue = ! checkinPageActive ;
const { error } = await supabase
. from ( "events" )
. update ({ checkin_page_active: newValue })
. eq ( "id" , eventId );
toast . success (
newValue
? "Check-in page activated"
: "Check-in page deactivated"
);
};
Use Case : Disable check-in before the event starts or after it concludes to prevent early/late arrivals.
Manual Staff Check-In
Event staff can manually check in attendees from the Event Portal:
src/pages/event/CheckInManagement.tsx
const manualCheckIn = async ( attendee ) => {
const { error } = await supabase
. from ( "attendees" )
. update ({
checked_in: true ,
checked_in_at: new Date (). toISOString (),
checkin_method: "manual" ,
portal_active: true ,
})
. eq ( "id" , attendee . id );
};
Staff search for attendees by:
Unique ID
Email address
Name
Results show:
Attendee name
Email
Unique ID
Check-in status
Click Check In button to:
Mark as checked in
Record timestamp
Activate portal