Overview

The Events app stores event data in two HubSpot custom CRM objects: events and event_registrations. The worksby.design API provides endpoints for listing events, managing registrations, and checking attendees in. Events themselves are created and edited directly in HubSpot CRM (not via this API) — see Managing events below.

Base URL: https://api.worksby.design/apps/events/

All responses are JSON. Successful responses include "success": true. Errors include "error": "<message>" and an appropriate HTTP status code.

For AI agents: the common read/registration flow is fetch-eventsfetch-event-detailscreate-registration (each call requires portalId). To seed events and sample registrations on a portal, write the events and event_registrations custom objects directly via the HubSpot CRM API — see Creating and managing events.

Authentication

All endpoints except GET /license-status require the portal to have the Events app installed and a valid active license. Authentication is handled automatically — no API key is passed by the caller.

Every request must include portalId — the numeric HubSpot portal ID. For GET requests pass it as a query parameter. For POST requests include it in the JSON body.

The server validates that the portal has completed the OAuth install flow and holds a stored access token. If not, the endpoint returns 403 with { "error": "Portal <id> not authorised for events" }. If the license has expired it returns 403 with { "code": "LICENSE_INACTIVE" }.

Custom Object — events

Created automatically on first install. The name property is unique and acts as the primary identifier across all API calls and navigation links. Object type name: events. The numeric objectTypeId is portal-specific and resolved by the backend at runtime — callers never need it.

events — all properties

PropertyTypeDescription
namestring · uniqueEvent name. Must be unique per portal. Used as the primary event identifier for URL routing and in fetch-event-details name lookups. Cannot be duplicated.
url_slugstringLegacy URL identifier. Also accepted by fetch-event-details as a lookup key (tried before name). By convention equals a slugified version of name.
descriptionstring · textareaFull event description. Shown on the event detail page.
meta_descriptionstringSEO meta description. Keep under 160 characters. Maps to the HubSpot dynamic page meta description field.
highlightsstring · textareaShort bullet-style summary of event highlights. Shown as a callout on event detail pages.
start_datetimedatetimeEvent start date and time. ISO 8601 format (2026-06-15T09:00:00.000Z). Used as the sort key in fetch-events and the upcoming-only filter.
end_datetimedatetimeEvent end date and time. ISO 8601 format.
event_typestringFree-text event category. Common values: conference, webinar, workshop, meetup.
event_status enumeration Controls visibility. Only published events are returned by fetch-events.
draft published cancelled
event_tagsstringFree-text tags for filtering. Semicolon-separated by convention (e.g. training;certification;online).
image_featuredstring · URLFull URL to the hero/featured image. Also maps to the HubSpot dynamic page featured image field.
image_thumbnailstring · URLFull URL to the event card thumbnail image.
video_featuredstring · URLURL to a featured video (embed URL or direct link). Optional.
is_online boolean Whether the event is online-only.
truefalse
location_namestringVenue name or platform name for online events (e.g. Convention Center Amsterdam, Online via Zoom).
street_addressstringStreet address of the venue. Leave blank for online events.
citystringCity. Leave blank for online events.
postal_codestringPostal/ZIP code.
countrystringCountry name in full (e.g. Netherlands, Belgium).
location_map_typestringMap provider hint (e.g. google). Optional.
location_map_urlstring · URLEmbed URL for an interactive map.
location_image_mapstring · URLURL to a static map image.
price_amountnumberTicket price. Use 0 for free events.
price_currencystringISO 4217 currency code (e.g. EUR, USD).
payment_linkstring · URLExternal payment or booking URL (e.g. Stripe, Eventbrite). Optional.
ticket_typesstring · textareaAvailable ticket type names, one per line or semicolon-separated (e.g. standard;vip;student). Used to populate the ticket type selector on the registration form. The ticket_type field on a registration should match one of these values.
capacity_totalnumberMaximum number of attendees. Set to 0 for unlimited. create-registration blocks new registrations when capacity_current >= capacity_total (and capacity_total > 0).
capacity_currentnumberCurrent registration count. Incremented automatically by create-registration. Starts at 0.
featuresstringSemicolon-separated list of feature/amenity codes shown as icons on event pages. Common values: wifi, parking, food_drinks, networking, certificate, recording, accessible.
contact_emailstringOrganiser contact email address. Shown on the event detail page.
contact_phonestringOrganiser contact phone number.
external_urlstring · URLLink to an external event page or additional information. Optional.
registration_modestringHow registrations are collected. Common value: form (inline registration form). Optional — used by the front-end to decide which registration UI to show.
requires_membership boolean Whether the event is restricted to members only.
truefalse
meeting_platformstringOnline meeting platform name (e.g. Zoom, Microsoft Teams). Shown when is_online is true.
online_meeting_urlstring · URLDirect join URL for the online meeting. Typically shared only after registration.
associated_projectsstringInternal field — links the event to a HubSpot Projects record by numeric ID. Events with a value here are hidden when hideProjectEvents: true is passed to fetch-events.
training_typestringOptional classification for training events (e.g. certification, induction). Used by portal-specific filtering logic.

Custom Object — event_registrations

One record per attendee per event. Created by create-registration. Each registration is associated to its event and (optionally) to a HubSpot contact.

event_registrations — all properties

PropertyTypeDescription
attendee_namestringFull name (firstName + ' ' + lastName). Set automatically by create-registration. Can be edited via update-registration.
first_namestringGiven name.
last_namestringFamily name.
emailstringEmail address. Used to match or create a HubSpot contact during registration. Editable via update-registration.
phonestringPhone number. Editable via update-registration.
companystringCompany name. Editable via update-registration.
job_titlestringJob title. Editable via update-registration.
registration_datedatetimeISO 8601 timestamp of when the registration was created. Set automatically.
registration_status enumeration Registration lifecycle status. Physical attendance is tracked separately in attendance_status — a checked-in attendee stays confirmed.
pending confirmed waitlisted cancelled
attendance_status enumeration Physical attendance, tracked separately from the registration lifecycle. Written by check-in-by-qr (sets checked_in). New registrations default to not_arrived.
not_arrived checked_in no_show
ticket_typestringTicket type chosen at registration. Defaults to standard if not specified. Should match a value from the event's ticket_types property.
qr_codestringUnique QR check-in code generated at registration time. Format: EVT-XXXXXXXXXXXX (16 chars, uppercase alphanumeric). Used by check-in-by-qr.
check_in_datetimedatetimeISO 8601 timestamp of when the attendee was checked in. Set by check-in-by-qr when action: 'checkin' (alongside attendance_status: checked_in).
payment_status enumeration Payment state. check-in-by-qr grants access when status is paid, free, or complimentary. All five are declared schema options.
pending paid free complimentary refunded
amount_paidnumberAmount paid in the event's currency. Optional — use for paid registrations.
special_requirementsstring · textareaDietary, accessibility, or other special requirements. Shown on the event attendee list.
purchaser_emailstringEmail of the person who registered/paid. Equals the attendee's email for self-registration; differs for group registrations (one purchaser, several attendees). Mirrors the Purchaser association so emails can route to the buyer without resolving associations.
purchaser_namestringName of the purchaser. See purchaser_email.

Associations

Five association labels are created automatically on install. Label names are stable contracts — the backend resolves portal-specific numeric type IDs at runtime by matching on the label name. Do not rename these labels in HubSpot.

FromToLabelInverse label
eventsevent_registrationsEvent RegistrationsEvent
contactsevent_registrationsRegistered AttendeeEvent Registration
contactsevent_registrationsPurchaserPurchased Registration
contactseventsRegisteredRegistered Contact
projectseventsProject EventProject
create-registration creates the event→registration, Registered Attendee, Purchaser, and contact→event links automatically. The attendee and purchaser links point to the same contact for self-registration, or to different contacts for group registrations. (Project Event links events to HubSpot Projects and is managed separately.)

Endpoints

GET /apps/events/license-status

Check whether a portal has an active Events license. Does not require OAuth install — safe to call before the install flow.

Query parameters

ParameterTypeDescription
portalIdrequiredstringHubSpot portal ID.

Response

{ "licensed": true }
POST /apps/events/fetch-events

Return a list of published events, sorted by start_datetime ascending. By default returns only upcoming events (start date in the future).

Request body

FieldTypeDescription
portalIdrequiredstringHubSpot portal ID.
upcomingOnlybooleanFilter to events whose start_datetime is in the future. Default: true. Pass false to include past events.
hideProjectEventsbooleanIf true, exclude events that have a value in associated_projects (used to hide internal project-linked events from public listings). Default: false.

Response

{
  "success": true,
  "total": 3,
  "events": [
    {
      "id": "12345678",
      "name": "Monthly Member Webinar",
      "url_slug": "monthly-member-webinar",
      "start_datetime": "2026-06-15T14:00:00.000Z",
      "end_datetime": "2026-06-15T16:00:00.000Z",
      "event_type": "webinar",
      "event_status": "published",
      "event_tags": "online;members",
      "is_online": "true",
      "location_name": "Online via Microsoft Teams",
      "city": null,
      "country": null,
      "price_amount": "0",
      "price_currency": "EUR",
      "capacity_total": "500",
      "capacity_current": "12",
      "image_featured": "https://images.unsplash.com/...",
      "image_thumbnail": "https://images.unsplash.com/..."
    }
  ]
}
Each event in the array contains all events object properties. Use id when you need to reference this event in other HubSpot CRM API calls; use name when calling fetch-event-details.
POST /apps/events/fetch-event-details

Fetch full details for a single event including all registered attendees. Looks up the event by url_slug, then name (exact match), then name (partial match) — the first match wins.

Request body

FieldTypeDescription
portalIdrequiredstringHubSpot portal ID.
eventIdrequiredstringEvent identifier — can be the url_slug, the exact name, or a partial name. The backend tries each lookup in order until a match is found.
isNameLookupbooleanPass true when eventId is a decoded event name from a URL path segment. This skips the url_slug lookup and saves one HubSpot API call. Default: false.
contactIdstringOptional HubSpot contact ID. Reserved for future use — currently unused by the backend but can be passed without error.

Response

{
  "success": true,
  "event": {
    "id": "12345678",
    "name": "Monthly Member Webinar",
    "description": "Our regular online gathering...",
    "start_datetime": "2026-06-15T14:00:00.000Z",
    "capacity_total": "500",
    "capacity_current": "12",
    ...all event properties...
  },
  "registrations": [
    {
      "id": "98765432",
      "attendee_name": "Sarah Johnson",
      "email": "sarah.johnson@example.com",
      "registration_status": "confirmed",
      "payment_status": "paid",
      "qr_code": "EVT-ABC123DEF456",
      "check_in_datetime": null,
      ...all registration properties...
    }
  ]
}
POST /apps/events/fetch-user-registrations

Find all event IDs that a specific contact is registered for. Matches by contact email address via the event_registrations object.

Request body

FieldTypeDescription
portalIdrequiredstringHubSpot portal ID.
contactIdrequiredstringHubSpot contact ID. The backend fetches the contact's email and uses it to search registrations.

Response

{
  "success": true,
  "eventIds": ["12345678", "87654321"]
}
Returns an array of event IDs (not names or slugs). Cross-reference these with the id field returned by fetch-events to determine which events a contact has already registered for.
POST /apps/events/create-registration

Register an attendee for an event. Creates the event_registrations record, creates or updates the HubSpot contact, and creates all three association links (event → registration, contact → registration, contact → event). Increments capacity_current on the event.

Request body

FieldTypeDescription
portalIdrequiredstringHubSpot portal ID.
eventIdrequiredstringEvent identifier — numeric ID from fetch-events, or a url_slug string. Non-numeric values trigger a slug lookup first.
formDatarequiredobjectAttendee details. See sub-fields below.
contactIdstringOptional HubSpot contact ID. If provided, the contact is updated with the form data rather than searched by email. If omitted, the backend searches by email and creates the contact if not found.

formData fields

FieldTypeDescription
firstNamerequiredstringAttendee first name.
lastNamerequiredstringAttendee last name.
emailrequiredstringAttendee email address. Used to find or create the HubSpot contact.
phonestringPhone number.
companystringCompany name.
jobTitlestringJob title.
ticketTypestringTicket type. Defaults to standard if not provided. Should match a value from the event's ticket_types field.
specialRequirementsstringDietary, accessibility, or other special requirements.

Response

{
  "success": true,
  "registration": {
    "id": "98765432",
    "qr_code": "EVT-ABC123DEF456",
    "email": "sarah.johnson@example.com"
  }
}
Capacity check: if capacity_total > 0 and capacity_current >= capacity_total, the registration is rejected with 400 { "error": "Event is at full capacity" }. Check capacity before attempting to register.
Group registration: include an optional attendees array (each item carries the same fields as formData) to register several people under one purchaser in a single call. Each registration stores purchaser_email/purchaser_name and gets both a Registered Attendee link and (for the purchaser contact) a Purchaser link. Omit attendees for ordinary self-registration — the single formData attendee is also the purchaser.
POST /apps/events/update-registration

Update a single editable property on an existing registration. Used for inline attendee detail corrections.

Request body

FieldTypeDescription
portalIdrequiredstringHubSpot portal ID.
registrationIdrequiredstringNumeric ID of the event_registrations record.
propertyNamerequiredstring Property to update. Only these five values are accepted:
attendee_name job_title company email phone
propertyValuestringNew value. Pass an empty string to clear the field.

Response

{ "success": true }
POST /apps/events/check-in-by-qr

Look up a registration by QR code and optionally check the attendee in. Returns an access decision that drives the check-in UI: grant entry, deny, flag as already checked in, or report an error.

Request body

FieldTypeDescription
portalIdrequiredstringHubSpot portal ID.
qrCoderequiredstringQR code value from the registration. Format: EVT-XXXXXXXXXXXX.
actionstringPass "checkin" to set attendance_status: checked_in and record check_in_datetime (only on a GRANT). Omit (or any other value) for a read-only verification that doesn't mutate the record.

Access decisions

DecisionMeaningWhen
ALREADY_INAlready checked inattendance_status is checked_in (or check_in_datetime is set) — evaluated first
GRANTEntry permittedNot yet checked in, registration_status is confirmed, AND payment_status is paid, free, or complimentary
DENYEntry refusedNot yet checked in, but registration isn't confirmed (e.g. pending/waitlisted/cancelled) or payment isn't settled
ERRORQR code not foundNo registration matches the provided QR code (404 response)

Response

{
  "success": true,
  "accessDecision": "GRANT",
  "checkedIn": true,
  "registration": {
    "id": "98765432",
    "attendeeName": "Sarah Johnson",
    "firstName": "Sarah",
    "lastName": "Johnson",
    "email": "sarah.johnson@example.com",
    "company": "Acme Corp",
    "jobTitle": "Product Manager",
    "eventTicketType": "standard",
    "registrationStatus": "confirmed",
    "paymentStatus": "paid",
    "attendanceStatus": "checked_in",
    "checkInDatetime": "2026-06-15T09:32:00.000Z",
    "specialRequirements": null
  },
  "event": {
    "name": "Annual Member Conference",
    "startDatetime": "2026-06-15T09:00:00.000Z"
  }
}
Note: checkedIn is true only when action: "checkin" was passed and the decision was GRANT. On DENY or ALREADY_IN, the record is not modified regardless of the action.

Creating and Managing Events

Events are created and edited directly via the HubSpot CRM API — this worksby.design API does not expose a create/update event endpoint. Use the standard HubSpot /crm/v3/objects/{objectTypeId} endpoints with the events custom object.

Resolving the objectTypeId

HubSpot assigns a different numeric objectTypeId to the events custom object on each portal. To resolve it, fetch all schemas and find the one whose name field equals "events":

GET https://api.hubapi.com/crm/v3/schemas
Authorization: Bearer {access_token}

// In the response, find:
results.find(s => s.name === 'events').objectTypeId
// → e.g. "2-12345678"

Creating an event

Once you have the objectTypeId, create an event with a POST to /crm/v3/objects/{objectTypeId}:

POST https://api.hubapi.com/crm/v3/objects/{objectTypeId}
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "properties": {
    "name": "Monthly Member Webinar",
    "url_slug": "monthly-member-webinar",
    "description": "Our regular online gathering for members.",
    "meta_description": "Free monthly online webinar — live panel and open Q&A.",
    "highlights": "Live panel, member updates, exclusive content.",
    "event_type": "webinar",
    "event_status": "published",
    "start_datetime": "2026-07-15T14:00:00.000Z",
    "end_datetime": "2026-07-15T16:00:00.000Z",
    "is_online": "true",
    "location_name": "Online via Microsoft Teams",
    "price_amount": "0",
    "price_currency": "EUR",
    "capacity_total": "500",
    "capacity_current": "0",
    "features": "recording",
    "registration_mode": "form",
    "image_featured": "https://images.unsplash.com/photo-...",
    "image_thumbnail": "https://images.unsplash.com/photo-..."
  }
}

Required properties for a visible event

At minimum, set these properties for an event to appear in fetch-events results:

  • name — must be unique; this is the event identifier
  • event_status: "published" — draft and cancelled events are excluded
  • start_datetime — must be in the future (unless upcomingOnly: false)

Updating an event

PATCH https://api.hubapi.com/crm/v3/objects/{objectTypeId}/{eventId}
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "properties": {
    "event_status": "cancelled"
  }
}
Name uniqueness: the name property has a unique constraint. Attempting to create two events with the same name on the same portal will fail with a HubSpot 409 conflict error. If you need test events, use distinct names.

Writing registrations directly (sample data)

To seed demo attendees, create event_registrations records directly via the CRM API rather than calling create-registration (which also matches contacts, enforces capacity, and may send email). Resolve the event_registrations objectTypeId the same way as for events.

POST https://api.hubapi.com/crm/v3/objects/{registrationsObjectTypeId}
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "properties": {
    "attendee_name": "Sarah Johnson",
    "first_name": "Sarah",
    "last_name": "Johnson",
    "email": "sarah.johnson@example.com",
    "registration_date": "2026-06-01T10:00:00.000Z",
    "registration_status": "confirmed",
    "attendance_status": "not_arrived",
    "payment_status": "free",
    "ticket_type": "standard",
    "qr_code": "EVT-ABC123DEF456",
    "purchaser_email": "sarah.johnson@example.com",
    "purchaser_name": "Sarah Johnson"
  }
}

Then link the record. Resolve each numeric association type ID by label via GET /crm/v4/associations/{from}/{to}/labels, then create with batch/create. Use the field name from (not _from) or the link is silently dropped:

  • events → event_registrations · label Event Registrations
  • contacts → event_registrations · label Registered Attendee (the attendee)
  • contacts → event_registrations · label Purchaser (the buyer — same contact as the attendee for self-registration)
  • contacts → events · label Registered
QR codes: use a unique value per registration shaped as EVT- + 12 uppercase alphanumerics. Capacity: the app derives the live attendee count from non-cancelled registrations, so you needn't keep capacity_current exact — but set capacity_total on the event for display and the full-capacity check.