Webhook Documentation
When a subscribed event occurs, a POST request is sent to your endpoint URL within a few minutes.
The request body is a JSON object with an event_type field that identifies what happened,
so a single URL can receive all event types and route them internally.
Compatibility
New fields may be added to any payload object, and new event types may be introduced, at any time without notice. Your implementation should ignore fields and event types it does not recognize, and should not depend on the order of keys or the number of fields in an object.
Existing fields are stable: their names, types, and formats will not change.
Request format
Every delivery sends the following HTTP headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Webhook-Event | The event type string, e.g. ticket.purchased |
X-Webhook-Signature | sha256=<HMAC-SHA256 hex digest> |
Verifying the signature
Each subscription has a signing secret shown on its edit page.
Compute HMAC-SHA256 of the raw request body using that secret and compare it to
the value in X-Webhook-Signature (after stripping the sha256= prefix).
Always use a constant-time comparison to prevent timing attacks.
PHP
$secret = 'your_signing_secret';
$payload = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expect = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (! hash_equals($expect, $sig)) {
http_response_code(401);
exit;
}
Node.js
const crypto = require('crypto');
const secret = 'your_signing_secret';
const sig = req.headers['x-webhook-signature'];
const digest = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(digest),
Buffer.from(sig))) {
return res.status(401).end();
}
Envelope
Every payload shares the same top-level envelope. The data object varies by event type, but always includes an event object.
{
"event_type": "ticket.purchased", // string — one of the event types below
"event_id": 42, // integer — the event this subscription belongs to
"webhook_id": "550e8400-...", // string — UUID v4, unique per delivery attempt
"created_at": "2026-06-23T10:30:00-06:00", // ISO-8601 with the event timezone offset
"data": { ... }
}
Event types
| Type | Trigger | data shape |
|---|---|---|
ticket.purchased |
A ticket order was completed and payment collected. | order + attendee |
ticket.exchanged |
A ticket was exchanged for a different event time or type. | order + attendee |
ticket.transferred |
A ticket was transferred to a new holder. | order + attendee |
ticket.canceled |
A ticket was canceled. | order + attendee |
ticket.refunded |
A refund was issued for a ticket order. | order + attendee |
attendee.checked_in |
A ticket holder was scanned in at the gate. | order + attendee |
registration.created |
A new registration was confirmed. | order + registration |
registration.updated |
An existing registration was modified. | order + registration |
registration.canceled |
A registration was canceled. Also triggers alongside registration.refunded when a refund includes a cancellation. | order + registration |
registration.refunded |
A refund was issued for a registration. | order + registration |
registration.checked_in |
A participant was checked in at the event. | order + registration |
registration.transferred |
A registration was transferred to a different category. | order + registration |
registration.deferred |
A registration was deferred to a future year. | order + registration |
waiver.signed |
A participant signed a waiver, either during registration checkout or per-attendee for a ticket order after purchase. | registration + waiver |
product.purchased |
A merchandise order was completed and payment collected. | order + product |
product.fulfilled |
A purchased product was marked as fulfilled (picked up or shipped). | order + product |
donation.made |
A donation to a charity was completed as part of an order. | order + donation |
sponsorship.purchased |
A sponsorship was purchased for an event. | order + sponsorship |
Payload reference
The ticket and registration words are fixed for API consistency, event customizations to display text are ignored here.
Ticket events (ticket.* and attendee.checked_in) include an order and an attendee object.
Registration events (registration.*) include an order and a registration object.
The order object is the same shape in both cases.
The waiver.signed event includes a registration and a waiver object (no order).
product.purchased and product.fulfilled include an order and a product object.
donation.made includes an order and a donation object.
sponsorship.purchased includes an order and a sponsorship object.
Every event type also includes an event object.
Event object
| Field | Type | Notes |
|---|---|---|
id | integer | Same as the envelope event_id |
name | string | Event name |
year | string | Event year, e.g. 2026; combine with name to identify a specific year of a recurring event |
start_date | ISO-8601|null | Start date and time of the event |
end_date | ISO-8601|null | End date and time of the event; the same as start_date for single-day events |
Order object
| Field | Type | Notes |
|---|---|---|
id | integer | Internal order ID |
finish_date | ISO-8601 string | When payment was collected and the order completed |
billing_name | string | |
billing_email | string | |
billing_address | string | |
billing_city | string | |
billing_state | string | Two-letter state code |
billing_province | string|null | Non-US province, if applicable |
billing_zip | string | |
billing_country | string | ISO 3166-1 alpha-2 country code, e.g. US |
order_type | string | Human-readable order type name |
payment_type | string | e.g. Credit Card, Free |
ccnumber | string | Masked card number, e.g. xxxx-xxxx-xxxx-4242 |
transaction_id | string|null | Processor transaction reference |
ticket_total | decimal string | Sum of ticket prices |
registration_total | decimal string | Sum of registration prices (ticket events only) |
merchandise_total | decimal string | Ticket events only |
tax_total | decimal string | |
marketplace_tax_total | decimal string | |
discount_total | decimal string | Promo codes and referral discounts |
fee_total | decimal string | Processing fees |
bonus_fee_total | decimal string | Event-specific bonus fees (subset of fee_total) |
gift_card_payment | decimal string | Amount paid with a gift card |
order_total | decimal string | Grand total charged |
Promo object
Included as promo on tickets, registrations, and products. null when no promo code was used.
| Field | Type | Notes |
|---|---|---|
code | string | The promo code entered |
discount | decimal string | Amount discounted from this item by the promo code |
Custom answer object
Included in the answers array on tickets and registrations, one entry per answer to the event's custom questions.
Checkbox questions produce one entry per selected option, all sharing the same question_id.
Questions that were not answered are omitted.
| Field | Type | Notes |
|---|---|---|
question_id | integer | Unique question ID; stable even if the question text is edited |
question | string | Question text |
answer | string|null | Selected option text for multiple-choice questions, otherwise the entered text; null for file uploads |
additional_text | string|null | Extra text entered with a selected option, when the option allows it |
Ticket event payload
Used by: ticket.purchased, ticket.exchanged, ticket.transferred, ticket.canceled, ticket.refunded, attendee.checked_in
{
"event_type": "ticket.purchased",
"event_id": 42,
"webhook_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-06-23T10:30:00-06:00",
"data": {
"event": {
"id": 42,
"name": "Summer Fun Run",
"year": "2026",
"start_date": "2026-07-04T08:00:00-06:00",
"end_date": "2026-07-04T08:00:00-06:00"
},
"order": {
"id": 456,
"finish_date": "2026-06-23T10:30:00-06:00",
"billing_name": "Jane Smith",
"billing_email": "jane@example.com",
"order_type": "Ticket Order",
"payment_type": "Credit Card",
"ccnumber": "xxxx-xxxx-xxxx-4242",
"ticket_total": "75.00",
"fee_total": "6.50",
"order_total": "81.50",
...
},
"attendee": {
"id": 789,
"order_id": 456,
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "5551234567",
"status": "Active",
"billing_name": "Jane Smith",
"billing_email": "jane@example.com",
"order_date": "2026-06-23T10:30:00-06:00",
"tickets": [
{
"id": 101,
"order_id": 456,
"event_time_name": "5K Run",
"event_time_start": "2026-07-04T08:00:00-06:00",
"section_name": "General Admission",
"row_name": null,
"seat_name": null,
"price_label": "Adult",
"price": "35.00",
"tax": "0.00",
"fee": "3.25",
"sms_marketing_opt_in": true,
"promo": {
"code": "SUMMER10",
"discount": "3.50"
},
"answers": [
{
"question_id": 15,
"question": "T-shirt size",
"answer": "Medium",
"additional_text": null
}
]
}
]
}
}
}
Attendee fields
| Field | Type | Notes |
|---|---|---|
id | integer | Ticket group ID — uniquely identifies this attendee record |
order_id | integer | The order this attendee belongs to |
name | string | Attendee name as entered during checkout |
email | string | Attendee email |
phone | string|null | |
status | string | Ticket group status name |
billing_name | string | Name on the order (may differ from attendee name) |
billing_email | string | Email on the order |
order_date | ISO-8601 string | When the order was completed |
tickets | array | One entry per ticket in this group — see below |
Ticket fields (within attendee.tickets[])
| Field | Type | Notes |
|---|---|---|
id | integer | Unique ticket ID — use this to identify an individual ticket |
order_id | integer | The parent order — ties this ticket to its group and billing record |
event_time_name | string | Name of the event time group, e.g. 5K Run |
event_time_start | ISO-8601|null | Start time of the event time; null for reserved-seat tickets with a custom reservation window |
section_name | string|null | Venue section, if assigned |
row_name | string|null | Seat row, if assigned |
seat_name | string|null | Specific seat, if assigned |
price_label | string|null | Ticket tier label, e.g. Adult, VIP |
price | decimal string | Face value of the ticket |
tax | decimal string | |
fee | decimal string | Processing fee for this ticket |
sms_marketing_opt_in | boolean | Whether the ticket holder opted in to SMS marketing (text alerts) for this ticket; false if no opt-in was recorded |
promo | object|null | Promo code applied to this ticket — see Promo object |
answers | array | Custom question answers — see Custom answer object. Answers to once-per-order questions appear on only one ticket in the order. |
Registration event payload
Used by: registration.created, registration.updated, registration.canceled, registration.refunded, registration.checked_in, registration.transferred, registration.deferred
{
"event_type": "registration.created",
"event_id": 42,
"webhook_id": "550e8400-e29b-41d4-a716-446655440001",
"created_at": "2026-06-23T11:00:00-06:00",
"data": {
"event": {
"id": 42,
"name": "Summer Fun Run",
"year": "2026",
"start_date": "2026-07-04T08:00:00-06:00",
"end_date": "2026-07-04T08:00:00-06:00"
},
"order": {
"id": 457,
"finish_date": "2026-06-23T11:00:00-06:00",
"billing_name": "John Doe",
"billing_email": "john@example.com",
"order_type": "Registration",
"payment_type": "Credit Card",
"ccnumber": "xxxx-xxxx-xxxx-1234",
"registration_total": "45.00",
"fee_total": "4.00",
"order_total": "49.00",
...
},
"registration": {
"id": 123,
"order_id": 457,
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "5559876543",
"sms_marketing_opt_in": true,
"address": "123 Main St",
"city": "Boulder",
"state": "Colorado",
"province": null,
"zip": "80301",
"country_code": "US",
"gender": "M",
"birthdate": "1988-04-15",
"bib_number": "1042",
"category": "5K Adult",
"registration_date": "2026-06-23T11:00:00-06:00",
"checked_in_datetime": null,
"promo": null,
"answers": [
{
"question_id": 21,
"question": "How did you hear about us?",
"answer": "Other",
"additional_text": "Running club newsletter"
},
{
"question_id": 22,
"question": "Emergency contact phone",
"answer": "5551112222",
"additional_text": null
}
]
}
}
}
Registration fields
| Field | Type | Notes |
|---|---|---|
id | integer | Unique registration ID |
order_id | integer | The order this registration belongs to |
first_name | string | |
last_name | string | |
email | string | |
phone | string|null | |
sms_marketing_opt_in | boolean | true only when the participant entered a mobile phone number and explicitly opted in to marketing text messages |
address | string|null | |
city | string|null | |
state | string|null | Full state name |
province | string|null | Non-US province |
zip | string|null | |
country_code | string | ISO 3166-1 alpha-2 country code, e.g. US |
gender | string|null | M or F |
birthdate | date|null | YYYY-MM-DD |
bib_number | string|null | Assigned race bib; null until assigned |
category | string|null | Registration category/division name |
registration_date | ISO-8601 string | When the registration was created |
checked_in_datetime | ISO-8601|null | Set when the participant checks in on race day; null otherwise |
promo | object|null | Promo code applied to this registration — see Promo object |
answers | array | Custom question answers — see Custom answer object |
Waiver event payload
Used by: waiver.signed
This event does not include an order object. It fires once per waiver per registration at the moment of signing.
{
"event_type": "waiver.signed",
"event_id": 42,
"webhook_id": "550e8400-e29b-41d4-a716-446655440002",
"created_at": "2026-06-23T11:00:00-06:00",
"data": {
"event": {
"id": 42,
"name": "Summer Fun Run",
"year": "2026",
"start_date": "2026-07-04T08:00:00-06:00",
"end_date": "2026-07-04T08:00:00-06:00"
},
"registration": {
"id": 123,
"order_id": 457,
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"registration_date": "2026-06-23T11:00:00-06:00"
},
"waiver": {
"waiver_id": 7,
"waiver_version_id": 12,
"waiver_name": "General Liability Waiver",
"signed_at": "2026-06-23T11:00:05-06:00"
}
}
}
Waiver fields
| Field | Type | Notes |
|---|---|---|
waiver_id | integer | ID of the waiver template |
waiver_version_id | integer | ID of the specific version that was signed; versions are created when the waiver text is edited |
waiver_name | string | Name of the waiver |
signed_at | ISO-8601 string | Timestamp when the signature was recorded |
Product event payload
Used by: product.purchased, product.fulfilled
{
"event_type": "product.purchased",
"event_id": 42,
"webhook_id": "550e8400-e29b-41d4-a716-446655440003",
"created_at": "2026-06-23T11:00:00-06:00",
"data": {
"event": {
"id": 42,
"name": "Summer Fun Run",
"year": "2026",
"start_date": "2026-07-04T08:00:00-06:00",
"end_date": "2026-07-04T08:00:00-06:00"
},
"order": {
"id": 458,
"finish_date": "2026-06-23T11:00:00-06:00",
"billing_name": "Jane Smith",
"billing_email": "jane@example.com",
"order_type": "Merchandise Order",
"payment_type": "Credit Card",
"ccnumber": "xxxx-xxxx-xxxx-4242",
"merchandise_total": "25.00",
"fee_total": "2.00",
"order_total": "27.00",
...
},
"product": {
"id": 321,
"order_id": 458,
"product_name": "Event T-Shirt",
"size": "Large",
"quantity": 1,
"price": "25.00",
"shipping": "0.00",
"tax": "0.00",
"fee": "2.00",
"total": "27.00",
"fulfillment_status": "Unfulfilled",
"promo": null
}
}
}
Product fields
| Field | Type | Notes |
|---|---|---|
id | integer | Unique product purchase ID |
order_id | integer | The order this product purchase belongs to |
product_name | string | |
size | string|null | Selected size, if the product has sizes |
quantity | integer | |
price | decimal string | Unit price |
shipping | decimal string | |
tax | decimal string | |
fee | decimal string | Processing fee |
total | decimal string | Price, shipping, tax, and fee combined |
fulfillment_status | string | Unfulfilled, Fulfilled, or Cancelled |
promo | object|null | Promo code applied to this product — see Promo object |
Donation event payload
Used by: donation.made
{
"event_type": "donation.made",
"event_id": 42,
"webhook_id": "550e8400-e29b-41d4-a716-446655440004",
"created_at": "2026-06-23T11:00:00-06:00",
"data": {
"event": {
"id": 42,
"name": "Summer Fun Run",
"year": "2026",
"start_date": "2026-07-04T08:00:00-06:00",
"end_date": "2026-07-04T08:00:00-06:00"
},
"order": {
"id": 459,
"finish_date": "2026-06-23T11:00:00-06:00",
"billing_name": "Jane Smith",
"billing_email": "jane@example.com",
"order_type": "Ticket Order",
"payment_type": "Credit Card",
"ccnumber": "xxxx-xxxx-xxxx-4242",
"order_total": "35.00",
...
},
"donation": {
"id": 654,
"order_id": 459,
"charity_name": "Local Food Bank",
"donor_name": "Jane Smith",
"amount": "25.00",
"fee": "2.00",
"total": "27.00",
"comment": "Keep up the great work!"
}
}
}
Donation fields
| Field | Type | Notes |
|---|---|---|
id | integer | Unique donation ID |
order_id | integer | The order this donation belongs to |
charity_name | string | |
donor_name | string|null | |
amount | decimal string | Donation amount before fee |
fee | decimal string | |
total | decimal string | Amount plus fee, if the donor covered the fee |
comment | string|null | Optional message left by the donor |
Sponsorship event payload
Used by: sponsorship.purchased
{
"event_type": "sponsorship.purchased",
"event_id": 42,
"webhook_id": "550e8400-e29b-41d4-a716-446655440005",
"created_at": "2026-06-23T11:00:00-06:00",
"data": {
"event": {
"id": 42,
"name": "Summer Fun Run",
"year": "2026",
"start_date": "2026-07-04T08:00:00-06:00",
"end_date": "2026-07-04T08:00:00-06:00"
},
"order": {
"id": 460,
"finish_date": "2026-06-23T11:00:00-06:00",
"billing_name": "Acme Co.",
"billing_email": "sponsor@acme.example.com",
"order_type": "Sponsorship",
"payment_type": "Credit Card",
"ccnumber": "xxxx-xxxx-xxxx-4242",
"order_total": "500.00",
...
},
"sponsorship": {
"sponsor_id": 88,
"order_id": 460,
"sponsor_name": "Acme Co.",
"category_name": "Gold Sponsor",
"price": "475.00",
"tax": "0.00",
"marketplace_tax": "0.00",
"fee": "25.00"
}
}
}
Sponsorship fields
| Field | Type | Notes |
|---|---|---|
sponsor_id | integer | Unique sponsor ID |
order_id | integer | The order this sponsorship belongs to |
sponsor_name | string | |
category_name | string | Sponsorship tier/category name |
price | decimal string | |
tax | decimal string | |
marketplace_tax | decimal string | |
fee | decimal string | Processing fee |
