Skip to main content

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"
}
status
string
Check-in status: success, already_checked_in, or not_found
attendee_name
string
Name of the checked-in attendee
attendee_unique_id
string
Unique ID of the attendee

QR Code Check-in

For QR code scanning, the unique ID is encoded in the QR code:
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

status
string
success, already_checked_in, or not_found
attendee_name
string
Name of the attendee
attendee_unique_id
string
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

id
string
Attendee UUID
name
string
Attendee name
unique_id
string
Unique check-in ID
checked_in
boolean
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

Build docs developers (and LLMs) love