Skip to content

Check‐in process one pager

Sebastian Marines edited this page May 1, 2025 · 4 revisions

In previous years where we only had one printer for attendee check in, we relied entirely on Eventbrite. The volunteers in the check in module scanned the QR code of the attendee or searched for a name or their email. This triggered a webhook that sent the attendee data to an SQS queue, consumed by our server attached to the printer and the attendee badge was printed.

For the 2025 edition, we are expecting to host more than one thousand attendees, making it impossible to use a single printer to serve the amount of people doing the check in process, and our current architecture doesn't support using multiple printers.

Current architecture:

flowchart TD
    A[Eventbrite Check-in] -->|Scan QR/Search| B(Webhook)
    B --> C[SQS Queue]
    C --> D[Server]
    D --> E[Single Printer]
    style E fill:#f96,stroke:#333,stroke-width:2px
Loading

Proposed solution

To solve this issues, the proposed solution is to ditch Eventbrite for the checkin process and write a custom wrapper that our volunteers will use on the day of the event to check in attendees. This will allow us to give volunteers credentials tied to a specific printer, allowing us to have multiple queues the day of the event.

flowchart TD
    F[Eventbrite] --> |order.placed| G[Webhook Lambda]
    F --> |attendee.updated| G
    G --> H[(DynamoDB)]
    H --> |DynamoDB Streams| I[Lambda Function]
    I --> J[(OpenSearch)]
    
    K[Custom Check-in Web App] --> |Search/Scan| L[API Gateway]
    L --> M[Lambda Function]
    M --> J
    M --> H
    M --> |Mark as checked-in| N{SQS Queues}
    N --> |Printer 1| O[Printer 1]
    N --> |Printer 2| P[Printer 2]
    N --> |Printer 3| Q[Printer 3]
    
    R[Script] --> |Import existing users| H
    
    style O fill:#f96,stroke:#333,stroke-width:2px
    style P fill:#f96,stroke:#333,stroke-width:2px
    style Q fill:#f96,stroke:#333,stroke-width:2px
Loading

Technical architecture

The web app used by volunteers needs to support the following flows:

  • Scanning a user's QR code
  • Search a user by their name
  • Search a user by their email This requires us to add a search functionality that is fast and reliable. To create this fast check-in process we can make use of the order.placed and attendee.updated events that the Eventbrite API offers.

When a user places an order in Eventbrite, our webhook will receive the order ID, then we call the https://www.eventbriteapi.com/v3/orders/[ORDER_ID]/attendees to get the user profile and write that info to our Dynamo table. At this point of the flow, most of the user information in empty, since Eventbrite asks for the attendee's information in a separate step. The first_name, last_name, email, and name fields are guaranteed to be present at this point.

Right after the user places an order, they will be shown a screen asking for more information. This will trigger the attendee.updated event, we then can query the https://www.eventbriteapi.com/v3/events/[EVENT_ID]/attendees/[ATTENDEE_ID]/ endpoint and get the gender, company, and cell_phone fields as well as the fields present in the first step.

We will need a lambda function that responds to these webhook event and store the information in our dynamo table. Additionally, a lambda function will be triggered with dynamo streams when a user item changes and store the first_name, last_name, and email in an OpenSearch Serverless cluster.

The frontend application will query this OpenSearch indexes, and a lambda function will start the check-in process, marking the user as checked-in and sending the user info to the SQS queue tagged with the appropiate printer

sequenceDiagram
    participant User
    participant Eventbrite
    participant Webhook
    participant DynamoDB
    participant OpenSearch
    participant CheckinApp
    participant Printer
    
    User->>Eventbrite: Places order
    Eventbrite->>Webhook: order.placed event
    Webhook->>DynamoDB: Store basic info
    DynamoDB->>OpenSearch: Index user data
    
    User->>Eventbrite: Updates profile
    Eventbrite->>Webhook: attendee.updated event
    Webhook->>DynamoDB: Update user info
    DynamoDB->>OpenSearch: Update indexed data
    
    User->>CheckinApp: Present QR code
    CheckinApp->>OpenSearch: Search user
    OpenSearch->>CheckinApp: Return user data
    CheckinApp->>DynamoDB: Mark as checked-in
    CheckinApp->>Printer: Send to specific printer queue
    Printer->>User: Print badge
Loading

Support for existing orders

Since there are already more than 500 orders at the time this document was written, we need a script to poll the existing users from the Eventbrite attendees endpoint https://www.eventbriteapi.com/v3/events/[EVENT_ID]/attendees/ and write this info in dynamo. All this information will be automatically indexed in the OpenSearch cluster by the dynamo streams handler.

Webhooks

order.placed

Example payload

{
	"api_url": "https://www.eventbriteapi.com/v3/orders/12348484523/",
	"config": {
		"webhook_id": "14293353",
		"endpoint_url": "https://sebastianmarines.free.beeceptor.com",
		"action": "order.placed",
		"user_id": "1715983222873"
	}
}

When we receive this payload, we need to extract the api_url field and append attendees/ to the URL. For example, for the previous payload the processed url would be https://www.eventbriteapi.com/v3/orders/12348484523/attendees/. This will give us the API endpoint to query the attendee information for this order.

Then, we can query this endpoint and get the following:

Warning

If profile information shows "Info requested", fail and try in a few seconds

{
    "pagination": {
        "object_count": 1,
        "page_number": 1,
        "page_size": 50,
        "page_count": 1,
        "has_more_items": false
    },
    "attendees": [
        {
            "costs": {},
            "resource_uri": "https://www.eventbriteapi.com/v3/orders/12348484523/attendees/20220251343/",
            "id": "20220251343",
            "changed": "2025-05-01T00:10:39Z",
            "created": "2025-05-01T00:10:39Z",
            "quantity": 1,
            "variant_id": null,
            "profile": {
                "first_name": "Info Requested",
                "last_name": "Info Requested",
                "email": "Info Requested",
                "name": "Info Requested Info Requested",
                "addresses": {}
            },
            "barcodes": [
                {
                    "status": "unused",
                    "barcode": "1234848452320220251343001",
                    "created": "2025-05-01T00:10:42Z",
                    "changed": "2025-05-01T00:10:42Z",
                    "checkin_type": 0,
                    "is_printed": false,
                    "qr_code_url": "https://www.eventbriteapi.com/qrcode/1234848452320220251343001/?sig=[REDACTED]"
                }
            ],
           ...
           "event_id": "1263605089839",
           "order_id": "12348618343",
           "ticket_class_id": "2368027203"
        }
    ]
}

attendee.updated

Example payload

{
	"config": {
		"webhook_id": "14293353",
		"action": "attendee.updated",
		"endpoint_url": "https://sebastianmarines.free.beeceptor.com",
		"user_id": "1715983222873"
	},
	"api_url": "https://www.eventbriteapi.com/v3/events/1263605089839/attendees/20220251343/"
}

When we query the API URL:

{
    "costs": {},
    "resource_uri": "https://www.eventbriteapi.com/v3/events/1263605089839/attendees/20220251343/",
    "id": "20220251343",
    "changed": "2025-05-01T00:27:23Z",
    "created": "2025-05-01T00:10:39Z",
    "quantity": 1,
    "variant_id": null,
    "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "addresses": {},
        "gender": "male",
        "company": "Amazon",
        "name": "John Doe",
        "cell_phone": "1111111111",
        "email": "builders@awscommunity.mx",
        "job_title": "Cloud Engineer"
    },
    "barcodes": [
        {
            "status": "unused",
            "barcode": "1234848452320220251343001",
            "created": "2025-05-01T00:10:42Z",
            "changed": "2025-05-01T00:10:42Z",
            "checkin_type": 0,
            "is_printed": false
        }
    ],
    "answers": [
        {
            "answer": "Meetup.com",
            "question": "¿Cómo te enteraste del evento?",
            "type": "multiple_choice",
            "question_id": "290491363"
        },
        {
            "question": "Staff: ¿A qué equipo perteneces?",
            "type": "text",
            "question_id": "291000073"
        },
        {
            "question": "AWS Community: ¿A qué programa perteneces?",
            "type": "multiple_choice",
            "question_id": "291000193"
        },
        {
            "question": "¿A qué AWS User Group perteneces?",
            "type": "multiple_choice",
            "question_id": "291012273"
        },
        {
            "question": "¿Cuál comunidad?",
            "type": "text",
            "question_id": "290494743"
        },
        {
            "question": "¿Cuál AWS User Group?",
            "type": "multiple_choice",
            "question_id": "290494753"
        },
        {
            "question": "¿Quién?",
            "type": "text",
            "question_id": "290494763"
        },
        {
            "question": "¿Cuál?",
            "type": "text",
            "question_id": "290494783"
        }
    ],
    ...
}

Ticket classes

Ticket ID Class
2263813303 Asistente general
2368016273 Sponsor
2368023593 Speaker
2368027203 Staff
2368025563 Community
2368030673 VIP
2368030773 Volunteer

Clone this wiki locally