Guests API

Guests API — Overview

Attendee CRUD — single and bulk, lookup and search, door-side admission, and atomic create-and-send via the Comms surface.

The Guests API is the largest surface in Qflow — everything you do with attendees from sign-up through to the door. It covers single and bulk attendee management, a wide set of lookup/search shapes (by-tag, by-barcode, by-email, by-info, by-ticket, by-name…), admission (checkin / checkout / undocheckin), and a guest-blocking flow (single toggle plus bulk block).

URL pattern

All endpoints on this page live under:

https://api.qflowhub.io/checkin/v1/api/<endpoint>

The /api/ prefix matters — Guests is segment-versioned (same shape as Events, unlike Comms which is path-stripped). Every request needs both an OAuth bearer token AND an Ocp-Apim-Subscription-Key header — see Authentication.

What it does

Quick start

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id":        "<a fresh GUID you generate>",
    "eventId":   "<eventId from /api/event>",
    "firstName": "Joe",
    "lastName":  "Bloggs",
    "email":     "joe@example.com"
  }'

Save the returned id (it'll match what you sent — caller-supplied ids are honoured for idempotency). To send a welcome email in the same call, see the atomic create-and-send pattern in Guests.

Read in this order

  1. Guests — the single-record shape + atomic create-and-send.
  2. Bulk operations — when you have lots to insert at once.
  3. Lookup & search — every read path.
  4. Admission — door-side check-in flow.
  5. Blocking — when you need to keep someone out.

For full request/response schemas + try-it, see the auto-generated API Reference.

Guests API

Guests

Single-attendee CRUD plus the atomic create-and-send pattern that bundles an email send into the create call.

The shape of an attendee

Every attendee row carries the same surface: an id (GUID, caller-supplied for idempotency), a name / email / barcode, the parent eventId, optional tags / plusOnes / additionalInfo / image, and server-managed admitted / discharged / blockedDate timestamps that get set by the admission and blocking endpoints.

The shape is symmetric: the body you POST is the same shape you GET back. Server-managed fields (admitted, admittedPlusOnes, discharged, etc.) are ignored on create and populated by their dedicated endpoints.

Creating an attendee

POST /api/guest. Only eventId is required — everything else is optional. Supply your own id (GUID) to make the create idempotent (a retry returns the existing record rather than creating a duplicate); omit it and the server generates one, at the cost of retry-safety.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id":             "a4f1b2c3-9876-4321-abcd-1234567890ef",
    "eventId":        "<eventId>",
    "firstName":      "Joe",
    "lastName":       "Bloggs",
    "email":          "joe@example.com",
    "barcode":        "Q-2026-001",
    "tags":           "VIP",
    "plusOnes":       1,
    "additionalInfo": "Seat 12B"
  }'

Key field notes:

  • id — supply a GUID. Re-POSTing the same id returns the existing row (200, idempotent).
  • tags — comma-separated string. Same non-countable conventions as event tags ({extra}, {merch}, *-) apply here too.
  • plusOnes — number of additional guests this attendee brings. The server tracks admittedPlusOnes separately as people walk in.
  • barcode — optional. Uniqueness is not enforced server-side — duplicates are accepted. If your workflow assumes uniqueness, use barcode/exists to pre-check before assigning.
  • image — base64-encoded photo for visual ID at check-in. Capped at 100 KB; oversize returns 400.

Atomic create-and-send

Add commsTemplateId (and optionally correlationId) to the create body and the API creates the attendee AND queues their templated email in a single round-trip — no race, no two-step coordination. This is the recommended pattern for one-shot welcome flows.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id":              "a4f1b2c3-9876-4321-abcd-1234567890ef",
    "eventId":         "<eventId>",
    "firstName":       "Joe",
    "lastName":        "Bloggs",
    "email":           "joe@example.com",
    "commsTemplateId": "<templateId>",
    "correlationId":   "welcome-001"
  }'

Allow-list gate: the call needs Comms API allow-list access on your account. Without it you get 403 comms_api_not_enabled and the send is rejected — the guest is NOT created in that case (it's atomic). Per-event / per-template trust checks also run on the comms validation step; see Authentication for the gating model.

Reading a single attendee

GET /api/guest/{guestId} returns the full record:

curl 'https://api.qflowhub.io/checkin/v1/api/guest/{guestId}' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

For "everyone at this event", prefer the bulk list endpoint — see Lookup & search.

Updating

PUT /api/guest accepts a partial update — only the fields you send are applied. Server-managed fields (admitted, discharged, blockedDate) are ignored.

curl -X PUT 'https://api.qflowhub.io/checkin/v1/api/guest' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id":             "<guestId>",
    "tags":           "VIP, Press",
    "additionalInfo": "Seat 14A — moved"
  }'

Tags on PUT: omit the tags field to leave existing tags untouched, send an empty string to clear them, or send a value to replace the whole set. (This differs from Events, where PUT ignores tag changes entirely.)

Deleting

DELETE /api/guest removes the attendee from your event. After the call they don't appear in list / lookup responses, can't be admitted, and can't be fetched via GET /api/guest/{id} — gone from the API's perspective. For compliance / GDPR-shaped removals (where data must be wiped beyond what the API surface guarantees), contact support.

curl -X DELETE 'https://api.qflowhub.io/checkin/v1/api/guest' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "id": "<guestId>" }'

Audit history

GET /api/guest/{guestId}/history returns every state change for that attendee — creates, updates, admissions, blocks. Useful for "who changed this and when" disputes.

curl 'https://api.qflowhub.io/checkin/v1/api/guest/{guestId}/history' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Records include the actor (createdBy), the change kind, and the before/after snapshot.

Where to next

Try it
GET/guest/{guestId} PUT/guest POST/guest DELETE/guest GET/guest/{guestId}/history
Guests API

Bulk operations

Insert, update, or upsert many attendees in one call — plus the atomic batch-create-and-send that bundles a single comms send across N new attendees.

When to use bulk

Bulk endpoints are for "I have a list of N attendees and want to do the same thing to all of them" — bulk imports from a CRM, nightly syncs, attendance sweeps. They're not faster than looping the single endpoints if N is small (under ~20) but the round-trip count savings compound quickly above that. There's also a hard server-side cap of 200 records per call to keep the work bounded — split larger batches client-side.

All bulk endpoints are scoped by {eventId} in the URL.

Creating many

POST /api/guests/{eventId} accepts an array of attendee bodies — same shape as the single create, just wrapped in [].

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '[
    { "id": "<guid-1>", "firstName": "Joe",  "lastName": "Bloggs", "email": "joe@example.com" },
    { "id": "<guid-2>", "firstName": "Jane", "lastName": "Doe",    "email": "jane@example.com" }
  ]'

Supply your own id per record (or omit it for a server-generated one). Unlike single create, bulk create does not de-duplicate — an id that already exists on the event fails validation, so this isn't a safe retry path. Use the upsert endpoint below for insert-or-update semantics. eventId is taken from the URL, not the body; if you set it in the body it's ignored.

Updating many

PUT /api/guests/{eventId} applies partial updates to a list of attendees identified by id. Same semantics as the single PUT — fields you omit are preserved.

curl -X PUT 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '[
    { "id": "<guid-1>", "tags": "VIP, Press" },
    { "id": "<guid-2>", "additionalInfo": "Late arrival" }
  ]'

If any record's id doesn't exist on the event, that row is silently skipped — there's no per-record failure response. Pre-validate ids client-side if that matters.

Upserting many

PUT /api/guests/{eventId}/upsert is the "I don't care if these exist yet" variant — inserts new rows where the id isn't taken, updates existing rows where it is. Ideal for nightly sync flows where the source-of-truth lives in your CRM and you don't want to track which ids you've already seen.

curl -X PUT 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/upsert' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '[
    { "id": "<guid-1>", "firstName": "Joe",  "lastName": "Bloggs", "tags": "VIP" },
    { "id": "<guid-3>", "firstName": "New",  "lastName": "Person", "email": "new@example.com" }
  ]'

For partial updates on existing rows, only the fields you supply are touched; the rest are preserved. For new rows, anything you omit defaults to empty.

Batch create-and-send

POST /api/guests/{eventId}/batch is the bulk equivalent of the atomic create-and-send pattern — creates N attendees AND queues one bundled email that contains all their tickets. Critically: this is for multi-ticket purchases where one recipient gets the full set of barcodes in a single email, NOT a fan-out where every attendee gets their own send.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/batch' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "templateId":     "<templateId>",
    "toEmail":        "purchaser@example.com",
    "toName":         "The Bloggs household",
    "correlationId":  "order-12345",
    "attendees": [
      { "id": "<guid-1>", "firstName": "Joe",  "lastName": "Bloggs", "barcode": "Q-001" },
      { "id": "<guid-2>", "firstName": "Jane", "lastName": "Doe",    "barcode": "Q-002" }
    ]
  }'

Capped at 25 attendees per batch. The single email goes to toEmail (not the individual attendee emails); the template can reference the bundled barcodes via merge tokens — see the Comms batch article for the template-side mechanics. Optional pdfMode / filenamePattern / batchId fields control PDF rendering and reconciliation.

Allow-list gate as the single atomic pattern. The transaction is all-or-nothing: if the comms send fails to queue, NO attendees are created.

Where to next

  • Lookup & search — find guests after a bulk import.
  • Comms batch — the receiving-side template mechanics for the batch-create-and-send.
Try it
PUT/guests/{eventId} POST/guests/{eventId} PUT/guests/{eventId}/upsert POST/guests/{eventId}/batch
Guests API

Lookup & search

List attendees by event, filter by admission state, or search by tag, ticket type, barcode, email, name, or free-text info.

Three read shapes

The Guests surface has three categories of read endpoints:

  • List — every attendee at an event. Paginated.
  • Filter — list narrowed by admission state (checked-in / checked-out / not-arrived).
  • Search — find specific attendees by attribute (tag, barcode, email, name, ticket type, info substring).

All three are scoped to a single event — you can't search across events in one call.

Listing everyone at an event

GET /api/guests/{eventId} returns every attendee. Supports OData paging (?$top=, ?$skip=, ?$filter=, ?$orderby=); default page size 50.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Records include every field on the attendee model — including image if set. Payloads can get large on events with photo-attached attendees; use ?$top= to cap the result set if you don't need everyone in one page.

Filtering by admission state

Three pre-built filters return subsets of the full list:

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/attendees' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'
  • /attendees — currently checked in (admitted but not yet discharged).
  • /discharged — checked in then checked out.
  • /nonattendees — on the list but not yet admitted.

More efficient than client-side filtering on the full list, since the filtering happens server-side. Suited to door-side dashboards.

Search by tag

GET /api/guests/{eventId}/bytag with a tag query parameter returns every attendee that has the tag.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/bytag?tag=VIP' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Tag matching is on the whole tag value and case-insensitive (vip matches VIP) — but not a substring, so a VIP Lounge tag won't match a search for VIP. For partial matching, list all and filter client-side.

Search by ticket type

GET /api/guests/{eventId}/byticket works the same as by-tag but only matches the {tickettype}{name} style tag convention used by ticketing integrations.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/byticket?ticketType=GA' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Useful for "give me all the General Admission tickets" without having to know the exact tag string.

Search by barcode

GET /api/guests/{eventId}/bybarcode does an exact-match lookup, with a prefix-match fallback if no exact match exists (handles "scanner read the first N digits" cases). Only attendees created in the last 6 months are searched.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/bybarcode?barcode=Q-2026-001' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

For "is this barcode already used on this event?" before assigning one, use the dedicated existence check below — it's more efficient than searching and discarding the result.

Search by email

GET /api/guests/{eventId}/byemail does an exact-match lookup on the email field.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/byemail?email=joe@example.com' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Matching is exact-value and case-insensitive (per the database collation) — Joe@Example.com matches joe@example.com. Duplicate emails on a single event are allowed (different people, same email).

Search by name

GET /api/guests/{eventId}/byname does a prefix match against the attendee's firstName and lastName.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/byname?name=Joe' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Prefix only (no substring match in the middle of a name), and case-insensitivejoe matches Joe.

Search by info substring

GET /api/guests/{eventId}/byinfo searches the free-text additionalInfo field with a substring match. Useful for "find anyone whose info contains the seat number 14".

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/byinfo?additionalInfo=Seat+14' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Slower than the indexed searches above — only use when the indexed shapes don't fit.

Barcode existence check

GET /api/guests/{eventId}/barcode/exists returns a bare boolean — true if any attendee on the event already has that barcode, false if it's free. More efficient than searching and discarding the result.

curl 'https://api.qflowhub.io/checkin/v1/api/guests/{eventId}/barcode/exists?barcode=Q-2026-001' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>'

Useful as a pre-flight before assigning barcodes from your own pool to make sure you don't collide with existing ones.

Where to next

Try it
GET/guests/{eventId} GET/guests/{eventId}/attendees GET/guests/{eventId}/discharged GET/guests/{eventId}/nonattendees GET/guests/{eventId}/bytag GET/guests/{eventId}/byticket GET/guests/{eventId}/bybarcode GET/guests/{eventId}/byemail GET/guests/{eventId}/byinfo GET/guests/{eventId}/byname GET/guests/{eventId}/barcode/exists
Guests API

Admission

Door-side check-in and check-out — idempotent, audit-logged, plus the undo path for accidental scans.

Concept

Admission has three endpoints — checkin, checkout, and undocheckin. All three operate on a single attendee, all three append to the guest's audit history. They drive the admitted, admittedPlusOnes, and discharged timestamps on the attendee record.

The expected door flow is:

  1. Scanner reads barcode → app calls byBarcode to resolve to a guestId.
  2. App calls POST /api/guest/checkin to admit.
  3. If anything goes wrong (wrong scan, return to the bar), call POST /api/guest/checkout or POST /api/guest/undocheckin.

Note on retries: these endpoints aren't idempotent — re-posting checkin overwrites the existing admitted timestamp and adds another history line. If you retry over an unreliable network, wrap calls in your own dedupe layer (track which guestIds you've already submitted in the current session and don't resubmit).

Check in

POST /api/guest/checkin marks the attendee as admitted and stamps admitted with the current server time. Returns the updated record.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest/checkin' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id":               "<guestId>",
    "admittedPlusOnes": 1
  }'

Notes:

  • admittedPlusOnes — how many of the attendee's plus-ones walked in with them. Defaults to 0 if omitted. Capped at the attendee's plusOnes allowance — over-admission returns 409 with error: "admitted_plus_ones_exceeded".
  • Not idempotent — a retry overwrites admitted with the new timestamp and adds a fresh history line. This is by design, so corrections at the door via the API are auditable (every overwrite is captured in history). Wrap retries in your own dedupe layer if you don't want the extra audit entries.
  • Validity window — if the attendee has validFrom / validTo set, check-in attempts outside that window return 403 with error: "not_yet_valid" or "validity_expired". The check uses the caller-supplied admitted timestamp (so you can back-fill audit entries without tripping the window), or server time if admitted is omitted.
  • isGroup is stored on the attendee record but not currently auto-admits plus-ones server-side — drive group admission client-side (set admittedPlusOnes to the household size).

Check out

POST /api/guest/checkout flips the discharged timestamp — typically used at events with a "leave and re-enter" door policy. The attendee can be checked back in later.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest/checkout' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "id": "<guestId>" }'

Returns 404 if the guest id doesn't exist on any event you have access to. There's no separate "must be admitted first" guard — calling checkout on a never-admitted guest stamps discharged regardless, which is probably not what you want; check admitted client-side first if it matters.

Undo check-in

POST /api/guest/undocheckin clears both admitted AND discharged — used for accidental scans where the attendee shouldn't be on the admitted list at all.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest/undocheckin' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "id": "<guestId>" }'

Reflects in the audit history — the action is recorded even though the visible state returns to "not admitted". Useful if you need to dispute counts later.

Where to next

  • Lookup & search — find the attendee to admit by barcode / email / name.
  • Blocking — when an attendee shouldn't be allowed in at all.
Try it
POST/guest/checkin POST/guest/checkout POST/guest/undocheckin
Guests API

Blocking

Flag an attendee as blocked so the door scanner rejects them — single toggle plus bulk-block by id or by barcode.

Concept

Blocking is reversible and leaves the attendee record in place: it stamps blockedDate and blockedReason on the record, and the check-in scanner app rejects blocked attendees at the door, showing the reason. Enforcement is at the scanner — the API checkin endpoint itself does not reject a blocked attendee, so blocking is a door control rather than an API-side gate. The block can be cleared with the same endpoint; nothing about the attendee's existence changes.

Three shapes:

  • Single toggle (POST /api/guest/block) — flip one attendee's block state. Sets or clears in one call depending on current state.
  • Bulk by id (POST /api/guests/block/{eventId}) — block N attendees by their ids in a single request.
  • Bulk by barcode (POST /api/guests/block/{eventId}/barcode) — block N attendees by barcode (useful when you only have scan data).

All three append to the attendee's audit history so the action is recoverable later.

Single block / unblock

POST /api/guest/block toggles state — if not blocked, it blocks; if already blocked, it clears. The reason is required when blocking; ignored when clearing.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest/block' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id":     "<guestId>",
    "reason": "Disputed payment — verify at door"
  }'

After the call, the attendee's blockedDate and blockedReason are populated, and the door scanner will reject them with the reason on the next scan.

To unblock, call again with the same id (reason is ignored on the unblock leg):

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guest/block' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{ "id": "<guestId>" }'

If you want to be explicit about state rather than relying on the toggle behaviour, call GET /api/guest/{id} first and check blockedDate.

Bulk block by id

POST /api/guests/block/{eventId} blocks every attendee whose id is in the supplied array. Same reason applies to all.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guests/block/{eventId}' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "reason": "Group cancelled — refund pending",
    "guestIds": [
      "<guestId-1>",
      "<guestId-2>",
      "<guestId-3>"
    ]
  }'

Capped at 200 ids per call. Ids that don't exist on the event are silently skipped — no partial error.

Note: re-blocking overwrites. Already-blocked ids in the array get their blockedReason and blockedDate overwritten with the new call's values — the original block reason is replaced. If you need to preserve original reasons, filter the list client-side first (read the attendees and skip ones with an existing blockedDate).

This endpoint only blocks — there's no bulk unblock by id. Use the single toggle endpoint to unblock.

Bulk block by barcode

POST /api/guests/block/{eventId}/barcode is the same shape but takes barcodes instead of ids — useful when you have scanner data and don't want to round-trip through a lookup first.

curl -X POST 'https://api.qflowhub.io/checkin/v1/api/guests/block/{eventId}/barcode' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Ocp-Apim-Subscription-Key: <subscription_key>' \
  -H 'Content-Type: application/json' \
  -d '{
    "reason": "Stolen tickets — flagged by venue security",
    "barcodes": [
      "Q-2026-001",
      "Q-2026-002"
    ]
  }'

Same 200-per-call cap. Barcodes that don't match any attendee on the event are silently skipped.

Where to next

  • Admission — admit attendees who aren't blocked.
  • Guests — check blockedDate / blockedReason on the attendee record.
Try it
POST/guests/block/{eventId} POST/guests/block/{eventId}/barcode POST/guest/block
Guests API

Check-in webhook

Get notified the moment an attendee is admitted to your event. Fires from API, dashboard, and scanner-app check-ins. Configured once per account.

Whenever an attendee is admitted to one of your events — via the check-in scanner app, the dashboard, or POST /checkin/v1/api/guest/checkin — Qflow POSTs a payload to your configured webhook URL.

This is distinct from the Comms API webhooks (which report email lifecycle events). One account can configure both.

Configure the URL

The check-in webhook URL is set per-account in the Qflow dashboard under your account / settings → API. Sign in at qflowhub.io and add the receiving URL there. Same URL receives all check-in events for all events under your account.

Note

White-label / enterprise fallback. If a sub-account hasn't set its own check-in webhook, Qflow falls back to the enterprise webhook configured on the parent account — so a sub-account's check-ins still reach the parent's URL until (or unless) the sub-account configures its own.

When it fires

  • A guest is admitted via the scanner app on a phone or kiosk
  • A user clicks "Check in" on the dashboard guest list
  • An integration calls POST /checkin/v1/api/guest/checkin (or batched equivalents)
  • Plus-ones are admitted alongside the main attendee

Payload

{
  "Id":             "<attendee guid>",
  "FirstName":      "Joe",
  "LastName":       "Bloggs",
  "Email":          "joe@example.com",
  "Barcode":        "4GA6965Q1G5EZ0FB",
  "Tags":           "VIP, {tickettype}Main Entry, {session}Keynote",
  "AdditionalInfo": "Card #1234",
  "Admitted":       "2026-04-30T14:30:12Z",
  "CreatedBy":      "API (acme_resowner)",
  "User":           "joe.bloggs@yourcompany.com",
  "EventId":        "<event guid>"
}

Field notes:

Field Notes
Id The attendee's GUID — same as the id you supply when creating via the API
Admitted UTC timestamp of the check-in event
CreatedBy How the attendee originally got into Qflow (API client name, dashboard user, import)
User The team member or staff account who performed the check-in (not the attendee)
Tags Comma-separated tags; prefixed tags include their {...} markers — see Tags

Receiving the webhook

Each request carries these headers:

  • X-Qflow-Webhook: true — identifies the call as a Qflow check-in webhook; filter on it if the endpoint is shared with other traffic.
  • User-Agent: Qflow-Webhook/1.0
  • Content-Type: application/json

A minimal receiver:

app.post('/qflow/checkin', express.json(), (req, res) => {
  const { Id, FirstName, LastName, Email, EventId, Admitted } = req.body;
  console.log(`${FirstName} ${LastName} (${Email}) admitted to ${EventId} at ${Admitted}`);
  // ... your logic ...
  res.sendStatus(200);
});

Return 200 OK on receipt. Non-2xx responses may trigger retries (subject to operational settings).

Note

This webhook is the legacy check-in notification channel and is not currently HMAC-signed. If signature verification is critical for your use case, contact support@qflowhub.io.

Common uses

  • CRM sync — push Admitted events into your CRM as engagement signals
  • Real-time analytics — dashboards showing arrivals as they happen
  • Notifications to staff — alert a host when a VIP is checked in
  • Cross-system mirror — mark the same person attended in another system (badge printer, partner platform)

See also

  • Comms API webhooks — separate channel for email lifecycle events (sent / delivered / bounced / etc.)
  • Tags — understanding the {tickettype}, {session}, etc. markers in the Tags field
Guests API

API Reference

Interactive reference for the Guests surface — attendee CRUD, bulk reads, atomic create-and-send, and door-side admission.