-
Notifications
You must be signed in to change notification settings - Fork 2
Check‐in process one pager
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
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
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.placedandattendee.updatedevents 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
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.
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"
}
]
}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 ID | Class |
|---|---|
| 2263813303 | Asistente general |
| 2368016273 | Sponsor |
| 2368023593 | Speaker |
| 2368027203 | Staff |
| 2368025563 | Community |
| 2368030673 | VIP |
| 2368030773 | Volunteer |