Overview
The public-checkin edge function handles attendee check-ins from the public check-in page. It includes built-in rate limiting to prevent abuse and uses an atomic database operation to ensure check-in integrity.
This is a public endpoint (no authentication required). It implements aggressive rate limiting: max 10 check-ins per IP per 10-second window.
Endpoint
POST /functions/v1/public-checkin
Authentication
No authentication required. This endpoint is publicly accessible for self-service check-ins.
Rate Limiting
From source/supabase/functions/public-checkin/index.ts:12-44:
const WINDOW_MS = 10_000; // 10-second window
const MAX_REQUESTS = 10; // max 10 check-ins per IP per window
function isRateLimited(ip: string): boolean {
const now = Date.now();
let bucket = buckets.get(ip);
if (!bucket) {
bucket = { timestamps: [] };
buckets.set(ip, bucket);
}
// Prune old entries
bucket.timestamps = bucket.timestamps.filter((t) => now - t < WINDOW_MS);
if (bucket.timestamps.length >= MAX_REQUESTS) {
return true;
}
bucket.timestamps.push(now);
return false;
}
Request Body
UUID of the event. Must be valid UUID format.
Unique identifier of the attendee. Max 50 characters.
Check-in method. Defaults to "self_service". Max 50 characters.
Email address for verification (if required). Max 255 characters.
Example Request
curl -X POST 'https://<project-ref>.supabase.co/functions/v1/public-checkin' \
-H 'Content-Type: application/json' \
-d '{
"event_id": "123e4567-e89b-12d3-a456-426614174000",
"unique_id": "ATT-12345",
"method": "qr_scan"
}'
Example with Email Verification
curl -X POST 'https://<project-ref>.supabase.co/functions/v1/public-checkin' \
-H 'Content-Type: application/json' \
-d '{
"event_id": "123e4567-e89b-12d3-a456-426614174000",
"unique_id": "ATT-12345",
"email": "[email protected]",
"method": "manual_entry"
}'
Response
Success Response (200 OK)
Result object from the atomic check-in operation. Contains check-in status and details.
{
"result": {
"success": true,
"status": "checked_in",
"attendee_id": "abc12345-e89b-12d3-a456-426614174001",
"checked_in_at": "2026-03-04T10:30:00.000Z"
}
}
Error Responses
400 Bad Request
Returned when request parameters are invalid.
{
"error": "Invalid event_id"
}
{
"error": "Invalid unique_id"
}
{
"error": "Invalid email"
}
405 Method Not Allowed
Returned when using a method other than POST.
{
"error": "Method not allowed"
}
429 Too Many Requests
Returned when rate limit is exceeded.
{
"error": "Too many requests. Please wait a moment and try again."
}
Response includes Retry-After: 10 header indicating seconds to wait.
500 Internal Server Error
Returned when check-in operation fails.
{
"error": "Check-in failed"
}
{
"error": "Internal server error"
}
Implementation Details
IP Detection
From source/supabase/functions/public-checkin/index.ts:62-65:
const ip =
req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
req.headers.get("cf-connecting-ip") ||
"unknown";
From source/supabase/functions/public-checkin/index.ts:86-109:
// Validate UUID format for event_id
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!event_id || typeof event_id !== "string" || !uuidRegex.test(event_id)) {
return new Response(
JSON.stringify({ error: "Invalid event_id" }),
{ status: 400 }
);
}
if (!unique_id || typeof unique_id !== "string" || unique_id.length > 50) {
return new Response(
JSON.stringify({ error: "Invalid unique_id" }),
{ status: 400 }
);
}
if (email && (typeof email !== "string" || email.length > 255)) {
return new Response(
JSON.stringify({ error: "Invalid email" }),
{ status: 400 }
);
}
Atomic Check-in Operation
From source/supabase/functions/public-checkin/index.ts:111-123:
// Use service role to call the atomic RPC (bypasses RLS)
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
const rpcParams: Record<string, unknown> = {
_event_id: event_id,
_unique_id: unique_id,
_method: sanitizedMethod,
};
if (email) rpcParams._email = email;
const { data, error } = await supabase.rpc("public_atomic_checkin", rpcParams);
The public_atomic_checkin RPC function handles:
- Verifying the event’s check-in page is active
- Finding the attendee by unique_id
- Optionally verifying email if provided
- Atomically updating check-in status
- Preventing duplicate check-ins
Security Features
- Rate Limiting: In-memory sliding window rate limiter per IP address
- Periodic Cleanup: Memory leak prevention with 60-second cleanup interval
- UUID Validation: Strict validation of event_id format
- Input Sanitization: Method string truncated to 50 characters
- Service Role Bypass: Uses service role to bypass RLS for atomic operation
- Atomic Operation: Database RPC ensures check-in integrity
Use Cases
- Self-Service Check-in: Attendees check themselves in via QR code scan
- Manual Entry: Staff manually enter unique_id for attendees
- Email Verification: Optional email verification for additional security
- Public Kiosk: Unmanned check-in stations at event entrances