Skip to content

Webhooks

The webhooks for events allow you to synchronise mutations in our system with yours, e.g. to track attendance, for table planning, etcetera.

Mechanism

When we enable the webhooks for you, you will be provided with an identifier for your channel, as well as a webhook secret. Both of these MUST be kept secret, as they are sensitive credentials.

With these two values, you can verify that the incoming request did indeed come from our systems.

Webhooks have a header X-Signature, which contains a string that is generated by our systems, using your credentials. Bodies will always be JSON. Generally speaking, a webhook looks as follows:

HTTP
POST /webhook-endpoint
Content-Type: application/json
X-Signature: abcdef

{
  "id": "evt_qwerty",
  "event": "event.type",
  "data": {
    "...": "..."
  }
}

Verifying the incoming webhook

SDK availability

We can provide SDKs for PHP and JavaScript upon request, which will make it very quick to implement this.

Any code in this section is pseudocode. You will need adapt this pseudocode to the programming language of your choice.

The signature header consists of 2 parts: a timestamp (as nanoseconds since UNIX epoch) and a hash (a string). These 2 parts are joined by a slash (/). The timestamp indicates when the webhook was generated on our systems; you may use this to make sure the webhook was executed in a timely fashion.

JavaScript
const identifier = env['PLATFORM_IDENTIFIER']
const secret = env['WEBHOOK_SECRET']

// This MUST be the raw string from the body.
const body = request.body
const header = request.headers['X-Signature']

// Split the header, as per our spec
const parts = header.split('/')

// Must be exactly 2 non-empty parts to it.
if (parts.length !== 2 || parts[0] is empty || parts[1] is empty) {
    fail 'Invalid Header'
}

const timestamp = parts[0] as number
const signature = parts[1] as string

// Optional: you can check for a discrepancy between the timestamp and current time.
if (timestamp === 0 || timestamp < time.now.minusMinutes(5).nanoseconds) {
    fail 'Timestamp is out of range'
}

// Generate the message to verify.
// You MUST use the body exactly as we sent it in order to verify.
const message = identifier + '/' + timestamp + '/' + body
const expectedSignature = HMAC('SHA256', message, secret)

if (signature !== expectedSignature) {
    fail 'Invalid signature'
}

// Proceed to parse the body as you see fit.

Request Body

Attribute Type Description
id string A unique identifier for this particular webhook. May be used to ensure every webhook is handled only once.
event string The type of this webhook. See the individual webhooks below for details.
data object The data for this particular webhook. See the individual webhooks below for details.

Data Types

Data is represented in consistent structures across all webhooks. At their core, each data structure will have a type and an id field. type is a lowercase, dash-separated plural data type; e.g. users rather than user, or event-attendees as opposed to event-attendee. The id will typically be a unique, generated ID from our platform. The exception is with users, where only an external ID is returned.

Users

JSON
{
    "type": "users",
    "id": String
}
  • id will be the ID as provided by your SSO; even if it is numerical, we store it as a string

Events

JSON
{
  "type": "events",
  "id": Number,
  "externalId": String|null,
  "title": String,
  "url": String|Null,
  "isVip": Boolean,
  "isPaid": Boolean,
}
  • externalId is the external ID if it was set during cross-posting
  • url is the external URL set by your admins when creating the event on our platform.
  • isVip indicates whether the event is a VIP-only event
  • isPaid indicates whether the event is a paid event
  • isTicketed indicates whether the event is an on-platform, ticketed event

Event Attendees

JSON
{
  "type": "event-attendees",
  "id": Number,
  "isCheckedIn": Boolean,
  "user": User,
  "event": Event
}
  • user is a nested user data structure
  • event is a nested event data structure
  • isCheckedIn indicates whether the attendee has been checked in

Event Ticket Types

JSON
{
  "type": "event-ticket-types",
  "id": Number,
  "event": Event,
  "externalId": String,
  "title": String,
  "price": Number,
  "quantity": Number,
  "purchaseLimit": Number,
  "isVip": Boolean
}
  • event is a nested event data structure
  • externalId is the external ID if it was set during cross-posting
  • price is the price of this ticket type in the smallest denomination possible (cents, pence)
  • quantity is total inventory available of this ticket type
  • purchaseLimit is the maximum number of tickets that may be purchased of this type per customer
  • isVip indicates whether this ticket type is limited to VIP members only

Event Tickets

JSON
{
  "type": "event-tickets",
  "id": Number,
  "isCheckedIn": Boolean,
  "isPrimaryGuest": Boolean,
  "givenName": String|null,
  "familyName":String|null,
  "user": User,
  "event": Event,
}
  • user is a nested user data structure, indicating the user who purchased this ticket.
  • event is a nested event data structure
  • givenName is the given name for this ticket as provided during the order process
  • familyName is the family name for this ticket as provided during the order process
  • isPrimaryGuest indicates whether this ticket is for the primary guest, e.g. the user who placed the order.
  • isCheckedIn indicates whether the guest registered on this ticket has been checked in

Webhooks

Note

All webhooks below only detail the structure of the data field in the full request body, not the entire body.

As an example, here's an example of event.attendees.created:

JSON
{
  "id": "evt_...",
  "event": "event.attendees.created",
  "data": {
    "id": 1,
    "isCheckedIn": false,
    "user": {
      "type": "users",
      "id": "your_sso_id"
    },
    "event": {
      "type": "events",
      "id": 1,
      "url": null,
      "isVip": true
    }
  }
}

Event Mutations

There are 3 webhooks for event mutations:

  • events.created When an event is created
  • events.updated When an event is updated
  • events.deleted When an event is deleted

The data field is of type events.

Event Ticket Type Mutations (ticketed events only)

There are 3 webhooks for event mutations:

  • ticket-types.created When a ticket type is created
  • ticket-types.updated When a ticket type is updated
  • ticket-types.deleted When a ticket type is deleted

The data field is of type event-ticket-types.

Event Attendee Mutations

There are 4 webhooks for attendee mutations:

  • event.attendees.created When someone RSVP's to an event
  • event.attendees.deleted When someone retracts their RSVP for an event
  • event.attendees.checked-in An attendee has been checked in
  • event.attendees.checked-out An attendee has been checked out

The data field is of type event-attendees.

Note

These webhooks also fire for ticketed events, but only for the member who booked. For ticketed events, we recommend listening to the event.tickets.* webhooks.

Event Ticket Mutations

There are 4 webhooks for ticket mutations:

  • event.tickets.created When an order is placed for a ticketed event (e.g. a member RSVPing, optionally with guests)
  • event.tickets.deleted Occurs only when an order is cancelled or refunded
  • event.tickets.checked-in An attendee or guest has been checked in
  • event.tickets.checked-out An attendee or guest has been checked out

The data field is of type event-tickets.

Note

These webhooks only fire for ticketed events.