Video chapters
- AppEngine Events and Devices
- Admission and benefits are different operations
- Start with the maintained event contract
- Bind the gate to its actual configuration
- Issue tickets before presenting codes
- Admit, then test duplicate and capacity
- Record exit before allowing a return
- Preview, redeem and exhaust an entitlement
- Separate tickets, QR codes and venue credentials
- Request a fresh QR close to entry
- Import, assign and revoke a venue code
- Read badge data and inspect dispatch
- Route real devices deliberately
- Refresh occupancy and filter device events
- Configure a separate crew-access policy
- Grant and deny without claiming a door opened
- Recover from the recorded decision
- Download the venue client and examples
Written guide
Companion source code
Get the course source code on GitHub. The folder includes the supplied examples and setup instructions. Keep credentials in your own environment; use the maintained API reference when adapting recorded request examples.
Build a staff application that admits guests, keeps occupancy accurate, handles permitted re-entry, redeems included benefits and connects tickets to venue credentials. AppEngine supplies the decisions and saved records; your client presents them to staff. Badge preparation and device routing are included, followed by a separate crew-access example.
The current software rehearsal is complete through admission, capacity, benefits, credential revocation, QR rotation, console badge routing and a keyed crew-access decision. Physical badge printing and door movement are not demonstrated. The crew reader has no lock triggers, and the print example explicitly selects the console test adapter. Keep those boundaries visible when extending this application to real hardware.
Download the runnable venue client and examples ZIP. Runnable JavaScript, TypeScript and Python companion. The existing official Flutter example is appmint-examples/appmint_flutter_events_demo; it demonstrates a real custom-client structure, not a second AppEngine backend. Its older creation comments predate current CRM defaults.
1. Understand the three different decisions
An entrance moves a ticket holder inside or outside a venue. A benefit point checks or consumes something the ticket includes, such as one lunch. A crew reader applies building-access rules to a person's access credential. Do not route all three through the same action merely because they begin with a scanned code.
The client selects an event, a zone and a configured scan point. AppEngine decides whether the selected operation is allowed and saves its result. The screen should show the holder, the exact decision and the updated count or entitlement. A green button is not proof of admission.
2. Obtain a staff session and use the maintained reference
Complete API Foundations §7: staff sign-in, including any verification or temporary-password change. You need the API address, organization ID and a completed staff session. A customer session is not the gate operator. For the browser-to-host boundary, complete Custom API Gateway §§5–7: opaque session, cookie lifetime and permitted routes. Its review video provides chapter buttons at 1:57, 2:35 and 3:21. The trusted sample host keeps the staff token outside the browser and supplies the ordinary Bearer header through the official SDK's supported injected transport.
Open the maintained AppEngine API reference. Find CRM Events → Create event, then the Events operations for check-in, occupancy, ticket types and benefits. Inspect the selected operation's request and response beside its current description. Use the live reference for changing field lists; this course teaches the sequence and decisions that connect them.
Keep returned identifiers rather than inventing them: event sk, ticket-type sk, ticket sk, and the configured zone/point IDs. A ticket's static printed code is its name. For an existing customer holder, select the actual customer in Customers and use that customer's sk; a staff user is a different record. This controlled rehearsal uses holder names without email and free tickets. Ticket issuance suppresses its notification, but first admission has independent notification behavior when an email is present. Do not infer that one skip flag suppresses every later message.
3. Configure the event, zone and two scan points
Create an event through POST /crm/events/create, not a generic Repository event write. Supply a title, future startTime, an endTime after it, and the intended timezone. Current AppEngine fills datatype, new-record flag, author, event number, draft status and missing slug/date values. Save a deliberate slug when your application needs one; older examples that require callers to supply every internal field are no longer the minimum contract.
The controlled example is Harbor Summit Developer Lab, 17 November 2026, 09:00–17:00 America/Chicago. For your own exercise choose a future date. Configure Main Hall with capacity 1 so two test tickets can expose the capacity rule without hundreds of guests.
Create the ticket entrance and benefit point inside the event configuration. Add a separate wristband entrance when introducing imported credentials:
| Point | Zone | Scan type | Other consequential configuration |
|---|---|---|---|
| Front gate | Main Hall | entrance | Active; static ticket code matching |
| Lunch counter | Main Hall | eligibility | Accepts the lunch perk; auto-claim enabled |
Read the saved event before continuing. Match your client selectors to its actual zone and point IDs. Do not use a label from another event. The live wrong-point check returned “Scan point is unavailable for this event.”
An event begins as draft. Use its publish action, then live action, and reread its status. Publishing and going live are separate operations; neither admits anyone. Keep event completion/cancellation separate from the gate's entry and exit actions.

The sample client reads the saved event, Main Hall capacity and initial ticket/benefit points. The credential entrance is added later; this is actual saved configuration, not an event-creation recording.
4. Create a ticket type and issue controlled tickets
Create an event_ticket_type record through Repository using the saved event ID in data.event. Set its title, price0, currency, capacity, isActive:true, zone access, allowReentry:true and reentryLimit:1. The last two fields allow the initial admission and one return after an exit. They do not permit repeat scans while the holder remains inside.
Add a lunch perk with its own ID, quantity1 and the Lunch counter in its scan-point filter. The point accepts that perk ID; its relationship to the type is configuration, not the words printed on the button. Read the event's ticket-types operation to confirm the type is available.
Issue the ticket with POST /events/:eventId/tickets, selecting that type and the holder. Retain both ticket sk and static name. Use the issuance action, not a manually inserted ticket record: AppEngine creates the ticket, copied entitlement state and issue counts. This rehearsal issues named tickets without payment or email; it does not establish checkout or delivery.
The downloadable companion's Venue.issue follows that bounded exercise. The client should retain the issued record ID even if a later action returns a shorter ticket object without sk.
5. Admit directly and read the result
Choose Front gate and submit the ticket code to POST /events/checkin with the selected event, zone and checkpoint. Do not require verify-scan eligibility before first admission. The current verification action is for benefits and requires the holder already inside; that old sequence would block a legitimate first entry.
AppEngine checks the ticket, event/point/zone/type rules, existing inside state, re-entry allowance and capacity. Treat success:false as a denied admission even when the HTTP request succeeded. Show its reason; do not translate every200/201 into a green success state.
Continuous demonstration. Submit Ada's code once, show the saved entry, then reread occupancy. Main Hall changes from0 to1. Submit the same code again: the result is “Ticket is already inside. Record an exit before re-entry.” Count stays1. Submit Noah's code while that single place is occupied: “Main Hall is at capacity. Wait for an exit.”
The companion preserves a known decision if the subsequent occupancy GET fails: it returns the decision with occupancy:null and a refresh error. Tell staff that the action saved and the count could not refresh; retry the read. If the write itself timed out, its outcome is uncertain: inspect ticket/history before repeating it.

Actual AppEngine refusal displayed by the custom client: Noah remains outside while Main Hall is full. Occupancy is reread separately.
6. Exit and permitted re-entry
Call checkout for the selected ticket and gate, then reread occupancy. The completed rehearsal retained the ticket's status checked_in, cleared currentZone, added an outward movement and reduced occupancy to0. Status alone is not current presence. Use the movements and current occupancy returned by the platform.
Bring Ada back once: admission succeeds. Record another exit, then try another return: “Re-entry limit reached for this ticket.” Noah can use the newly available place. These are different refusals from “already inside” and “at capacity”; show the actual reason rather than a generic invalid-ticket message.
Occupancy is a current headcount. Statistics include several categories: entrance movements, eligibility scans and denied attempts are not interchangeable. Do not describe a total scan count as total people in the hall. The saved rehearsal statistics demonstrate that distinction.
7. Preview and redeem an included benefit
Choose Lunch counter after the holder has entered. Submit the code to POST /events/verify-scan with redeem:false. The recorded preview returned eligible:true, claimed:false; it did not consume lunch or increase headcount. The same request while the holder was outside returned an explicit instruction to record entry first.
When staff confirms the actual benefit operation, submit with redeem:true. At this configured auto-claim point, the result becomes claimed:true. Read the ticket's perks to show one used entitlement. Another attempt returns “Benefit already fully used.”
Verification can record an eligibility attempt and, unless explicitly disabled, can redeem at an auto-claim point. Do not use a repeating verification call to animate a waiting screen. Preview and redemption are deliberate actions. An eligibility-only point is not an entrance.

Actual benefit outcome after one redemption: one voucher claimed, zero remaining and the next redemption refused. This does not create another admission.
8. Handle signed QR codes and venue credentials
A fresh ticket QR comes from GET /events/tickets/:ticketId/qr. Despite its GET method, this refreshes stored QR data. Request it intentionally, show it to the holder and use it promptly. Current signed QR validation has a60-second default age limit. Admission rotates its secret and clears the previous code.
The software rehearsal obtained a fresh QR, validated it, admitted its ticket, then verified that the old signed payload was rejected. A static ticket code remains a different input; it still faces admission and re-entry rules. The client must never generate its own AppEngine signature.
A venue wristband or badge uses an event_credential. Import a unique code into the event's credential stock, assign it to the selected ticket ID and confirm the saved association. The current assignment helper's QR parsing is not equivalent to validated admission; this example deliberately assigns the already selected ticket ID.
At a point configured to match credential_code, submit that credential. The proof also exercised the documented fallback resolver without a checkpoint. A point explicitly configured for ticket_code need not match a wristband: choose the right matching method rather than treating all codes as interchangeable. Revoke the credential and confirm that another scan is refused. The recorded revoked code returned “No ticket found for this code.”
Typed codes in the sample are labelled manual entry. They demonstrate AppEngine decisions, not a physical scanner read.
Use the additional Wristband entrance with scanType:entrance, matchField:credential_code and the same Main Hall zone. Pass its ID wristband-gate as checkpoint when testing an imported credential. The ticket-code Front gate deliberately uses a different resolver; assigning a wristband does not change that point’s matching rule. The corrected example uses admitCredential, then tests revocation at the same credential point.

Actual recorded client: wristband-gate uses credential_code. After a successful admission, revocation removes that credential’s usable ticket lookup. The refused repeat does not remove the holder; a separate exit records departure.
9. Prepare the badge and route its print request
Read GET /events/tickets/:ticketId/badge. The response has badgeData plus the selected template or null. Use the actual holder, event title and ticket code in the preview. The current rehearsal has template:null; a course-designed preview is not proof that a stored template rendered.
Discover adapters and configured devices from the maintained device-integration reference. Dispatch uses body {capability,data}; device, servicePoint, location and explicit test adapter are routing query choices. Pin the intended gate printer instead of sending to an arbitrary first match.
For structured printing the current payload uses ticketType:"label"; each line has kind. Text lines carry text. A barcode line carries data and type, such as CODE128. Keep the copyable full payload in the companion rather than reading it line by line in the film.
The completed software rehearsal selected adapter=console and received an acknowledgement. No printer was controlled, no paper appeared, and no badge was marked printed. The console accepts a dispatch; it does not prove physical formatting. For a real installation inspect the actual badge, including its readable code, before the separate badge/printed action. The sample refuses that action without explicit operator confirmation.

Badge preview from returned data and acknowledged console dispatch. The explicit boundary remains: physical output was not verified and no printed marker was sent.
10. Connect a hub and receive scanner events
The existing Hub/Devices manuscripts are drafts, so they cannot substitute a demonstrated installation. The recorded public download destination is Hub Agent. Its installation evidence used the real agent with explicitly simulated peripherals. Consult current supported-machine instructions before installation.
Create your hub, retain its name, and securely obtain its connection fields: Server, OrgId, HubName and ApiKey. Install/configure that agent on the intended machine. Check live-states, the selected hub's state and the exact endpoint before routing a device command. Keep the key offscreen and out of source downloads.
A staff client uses Socket.IO namespace /device-events with auth:{token,orgId,hub}. Wait for the explicit authenticate success response. Filter incoming event messages by expected hub and endpoint before processing scan codes; a connection is not proof of authentication or a physical read. Re-read occupancy after your own actions and after reconnect.
The current gateway's data_changed allowlist does not include event/event_ticket/event_checkin. Do not promise automatic event headcount updates from those messages. The scanner stream and record-change stream are distinct. The separate live connection check completed staff authentication, disconnected and authenticated again, reread occupancy after each connection, and received an invalid-token refusal for a bad session. No hub was connected and no physical scan was emitted. Connection/authentication evidence and physical scan output remain separate.
11. Make a decision-only crew reader
Crew access uses access_zone, access_point, access_device and access_credential. It does not reuse the event ticket type. The setup administrator needs Access-area permission and the actual staff user's record sk, obtained from Users or the authenticated profile.
Create an owned crew zone allowing that user, a named access point linked to the zone, and a card-reader device linked to the point. Leave triggers:[]. Generate its device key once and store it privately. Issue the controlled card code to the selected staff holder through the access credential action.
The reader calls /access-control/read with orgid and x-device-key, plus its device name, code and method. It does not use the customer's token. The completed rehearsal returned result:granted, an access-event reference and unlocked:false. The rules were evaluated and logged; nothing opened.
Suspend that credential, read it again and inspect the denied result. Read its history to connect the credential change and the decision. The software proof uses only the owned reader and no actuator. Real lock wiring, remote unlock, zone lockdown, PINs and replacement-card policies require their own configured devices and outcome checks; do not trigger an unrelated reader or door for this course.

Actual no-trigger crew reader: the suspended credential is denied. The screen shows zero actuator triggers; it does not show a physical door opening.
12. Recovery and important alternatives
An unknown or revoked code, a cancelled ticket, a wrong point, a full zone and an exhausted benefit are distinct states. Preserve the server's meaning and show the selected event/point beside it. On a connection failure, avoid creating another entry just because the screen did not receive the first response. Recover by reading the ticket and occupancy, then perform only the still-needed operation.
For camera scanning or a Bluetooth keyboard scanner, feed the decoded code into the same deliberate admission action. For hub hardware, authenticate and filter the input stream first. Do not describe a typed entry, simulated device event or console acknowledgement as a physical scan or print.
Paid ticket checkout, customer booking, transfers/refunds, sessions/participants and pre-generated stock are additional Events operations. Find their operation families in the Events reference overview and inspect the selected operation in the maintained API reference. These are further integration paths, not completed demonstrations or required prerequisites for this venue check-in exercise. A free staff-issued ticket does not demonstrate paid checkout or refund settlement. Additional crew PIN/validity/lockdown rules are separate from the no-trigger decision exercised here.
13. Continue with the runnable client
The companion follows the official JavaScript SDK request interface, provides typed TypeScript examples and a Python standard-library host adapter, and points to the existing official Flutter Events demo. Its tests cover meaningful boundaries: direct admission, explicit non-redeeming benefit preview, no blind write retry, retaining a saved decision when a count refresh fails, and no physical-output claim after console dispatch.
The finished software result is an explainable venue client: it knows which operation belongs to the selected point, shows the saved outcome and handles refusal without corrupting the count. Keep the two remaining hardware facts separate: a print job must produce a readable badge, and a granted door decision must be followed by independently observed actuator behavior before either is called physically complete.