Overview
The Check-ins API enables you to check in attendees at your events using manual entry, QR code scanning, or self-service. Check-ins are atomic operations that prevent duplicate check-ins and track the method used.
Check-in Methods
PassTru supports three check-in methods:
Manual Check-in
Check in an attendee from the management interface:
const { data : attendee , error } = await supabase
. from ( 'attendees' )
. update ({
checked_in: true ,
checked_in_at: new Date (). toISOString (),
checkin_method: 'manual' ,
portal_active: true
})
. eq ( 'id' , attendeeId )
. select ()
. single ()
if ( error ) {
console . error ( 'Error checking in attendee:' , error )
} else {
console . log ( 'Attendee checked in:' , attendee )
}
Parameters
Public Check-in (Self-Service)
Attendees can check themselves in using the public check-in page. This uses an edge function for atomic operations:
const { data , error } = await supabase . functions . invoke ( 'public-checkin' , {
body: {
event_id: eventId ,
unique_id: 'K7M9P2Q5' ,
email: '[email protected] ' , // Optional for self-service
method: 'self_service' // Or 'qr_scan'
}
})
if ( error ) {
console . error ( 'Check-in failed:' , error )
} else {
console . log ( 'Check-in result:' , data )
}
Request Body
Response
{
"status" : "success" ,
"attendee_name" : "Jane Smith" ,
"attendee_unique_id" : "K7M9P2Q5"
}
Check-in status: success, already_checked_in, or not_found
Name of the checked-in attendee
Unique ID of the attendee
QR Code Check-in
For QR code scanning, the unique ID is encoded in the QR code:
Generate QR Code
Scan and Check-in
import QRCode from 'react-qr-code'
// In your component
< QRCode
value = {attendee. unique_id }
size = { 200 }
level = "M"
/>
Atomic Check-in Function
The public_atomic_checkin database function ensures check-ins are atomic and prevent race conditions:
const { data : result } = await supabase . rpc ( 'public_atomic_checkin' , {
_event_id: eventId ,
_unique_id: 'K7M9P2Q5' ,
_email: '[email protected] ' , // Optional
_method: 'self_service'
})
if ( result && result . length > 0 ) {
const checkin = result [ 0 ]
console . log ( 'Check-in status:' , checkin . status )
console . log ( 'Attendee:' , checkin . attendee_name )
}
Response
success, already_checked_in, or not_found
Unique ID of the attendee
Check-in Lookup
Verify if an attendee exists before attempting check-in:
const { data : lookup } = await supabase . rpc ( 'public_checkin_lookup' , {
_event_id: eventId ,
_unique_id: 'K7M9P2Q5' ,
_email: '[email protected] ' // Optional
})
if ( lookup && lookup . length > 0 ) {
const attendee = lookup [ 0 ]
console . log ( 'Attendee found:' , attendee . name )
console . log ( 'Already checked in:' , attendee . checked_in )
} else {
console . log ( 'Attendee not found' )
}
Response
Whether already checked in
Search for Check-in
Search attendees by unique ID, email, or name for manual check-in:
const searchTerm = 'K7M9'
const { data : results } = await supabase
. from ( 'attendees' )
. select ( 'id, name, email, unique_id, checked_in, checked_in_at, checkin_method' )
. eq ( 'event_id' , eventId )
. or ( `unique_id.ilike.% ${ searchTerm } %,email.ilike.% ${ searchTerm } %,name.ilike.% ${ searchTerm } %` )
. limit ( 20 )
if ( results ) {
results . forEach ( attendee => {
console . log ( ` ${ attendee . name } - ${ attendee . checked_in ? 'Checked In' : 'Pending' } ` )
})
}
Get Check-in Statistics
Retrieve check-in counts for an event:
const { data : event } = await supabase
. from ( 'events' )
. select ( 'total_attendees, checked_in_count' )
. eq ( 'id' , eventId )
. single ()
if ( event ) {
console . log ( `Check-ins: ${ event . checked_in_count } / ${ event . total_attendees } ` )
const percentage = ( event . checked_in_count / event . total_attendees * 100 ). toFixed ( 1 )
console . log ( `Completion: ${ percentage } %` )
}
Check-in Analytics
Analyze check-in methods and timing:
const { data : attendees } = await supabase
. from ( 'attendees' )
. select ( 'checkin_method, checked_in_at' )
. eq ( 'event_id' , eventId )
. eq ( 'checked_in' , true )
if ( attendees ) {
// Count by method
const methodCounts = attendees . reduce (( acc , a ) => {
acc [ a . checkin_method || 'unknown' ] = ( acc [ a . checkin_method || 'unknown' ] || 0 ) + 1
return acc
}, {} as Record < string , number >)
console . log ( 'Check-ins by method:' , methodCounts )
// Check-in times
const times = attendees
. map ( a => new Date ( a . checked_in_at ! ))
. sort (( a , b ) => a . getTime () - b . getTime ())
console . log ( 'First check-in:' , times [ 0 ])
console . log ( 'Latest check-in:' , times [ times . length - 1 ])
}
Undo Check-in
Revert a check-in if needed:
const { data : attendee , error } = await supabase
. from ( 'attendees' )
. update ({
checked_in: false ,
checked_in_at: null ,
checkin_method: null
})
. eq ( 'id' , attendeeId )
. select ()
. single ()
if ( error ) {
console . error ( 'Error undoing check-in:' , error )
} else {
console . log ( 'Check-in reverted for:' , attendee . name )
}
Undoing check-ins should be done carefully and may require audit logging for compliance.
Rate Limiting
The public check-in endpoint includes rate limiting to prevent abuse:
const { data , error } = await supabase . functions . invoke ( 'public-checkin' , {
body: { /* ... */ }
})
if ( error ) {
// Check for rate limit errors (429)
const message = error . message || ''
if ( message . includes ( '429' ) || message . toLowerCase (). includes ( 'too many' )) {
console . error ( 'Too many requests. Please wait and try again.' )
} else {
console . error ( 'Check-in failed:' , error )
}
}
Real-time Check-in Updates
Subscribe to check-in events in real-time:
const channel = supabase
. channel ( 'checkins' )
. on (
'postgres_changes' ,
{
event: 'UPDATE' ,
schema: 'public' ,
table: 'attendees' ,
filter: `event_id=eq. ${ eventId } `
},
( payload ) => {
const attendee = payload . new
if ( attendee . checked_in ) {
console . log ( 'New check-in:' , attendee . name )
// Update UI with new check-in
}
}
)
. subscribe ()
// Cleanup
return () => {
supabase . removeChannel ( channel )
}
Error Handling
Common check-in errors:
Attendee not found : Invalid unique ID or email
Already checked in : Duplicate check-in attempt
Rate limit exceeded : Too many requests in short time
Event inactive : Check-in page is not active
const handleCheckIn = async ( uniqueId : string , email ?: string ) => {
const { data , error } = await supabase . functions . invoke ( 'public-checkin' , {
body: {
event_id: eventId ,
unique_id: uniqueId ,
email: email ,
method: email ? 'self_service' : 'qr_scan'
}
})
if ( error ) {
console . error ( 'Check-in error:' , error )
return { success: false , error: error . message }
}
if ( data . status === 'not_found' ) {
return { success: false , error: 'Attendee not found' }
}
if ( data . status === 'already_checked_in' ) {
return { success: false , error: 'Already checked in' }
}
return { success: true , data }
}
Next Steps
Email API Send confirmation emails after check-in
Events API Manage event settings and branding