> ## Documentation Index
> Fetch the complete documentation index at: https://developers.perkstar.co.uk/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook event catalogue

> Every Perkstar outbound webhook event, with exact data fields and representative payloads.

This is the complete public outbound-event catalogue. Event names are stable,
dotted, lowercase strings. New optional fields may be added without a version
change; existing fields are not repurposed.

Every example below shows the event-specific `data` object. Perkstar wraps it
in the common envelope documented in [Receive webhooks](/guides/webhooks).

## Field conventions

* IDs are opaque strings. Store and compare them exactly.
* `occurredAt`, `scheduledStart`, `scheduledEnd`, `attendedAt`, and
  `resolvedAt` are ISO 8601 UTC timestamps.
* `created` in the outer envelope is Unix seconds.
* Money fields ending in `Pence` are integer minor units.
* Nullable fields are sent as JSON `null`; optional fields may be absent.
* `testMode: true` identifies a transaction generated with test credentials.

## Customer lifecycle

| Event                 | Sent when                     | Required `data` fields                           | Optional `data` fields |
| --------------------- | ----------------------------- | ------------------------------------------------ | ---------------------- |
| `customer.enrolled`   | A customer first joins a card | `enrollmentId`, `cardId`, `source`, `occurredAt` | `customerId`           |
| `customer.unenrolled` | Access to a card ends         | `enrollmentId`, `reason`, `occurredAt`           | `cardId`               |
| `customer.anonymized` | Personal data is anonymised   | `customerId`, `customerNumber`, `occurredAt`     | `source`               |

<AccordionGroup>
  <Accordion title="customer.enrolled">
    ```json theme={null}
    {
      "enrollmentId": "enr_123",
      "customerId": "cus_123",
      "cardId": "card_123",
      "source": "marketplace",
      "occurredAt": "2026-07-30T10:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="customer.unenrolled">
    ```json theme={null}
    {
      "enrollmentId": "enr_123",
      "cardId": "card_123",
      "reason": "membership_cancelled",
      "occurredAt": "2026-07-30T10:10:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="customer.anonymized">
    ```json theme={null}
    {
      "customerId": "cus_123",
      "customerNumber": 1042,
      "source": "api",
      "occurredAt": "2026-07-30T10:15:00.000Z"
    }
    ```
  </Accordion>
</AccordionGroup>

## Loyalty activity

| Event                 | Sent when                                          | Required `data` fields                                        | Optional `data` fields                                                                                                                                                     |
| --------------------- | -------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transaction.created` | A loyalty-ledger row is committed                  | `enrollmentId`, `type`, `delta`, `occurredAt`                 | `transactionId`, `cardId`, `customerId`, `amountPence`, `balanceAfter`, `reversesTransactionId`, `reason`, `source`, `testMode`, `externalTransactionId`                   |
| `coupon.redeemed`     | A coupon card is redeemed                          | `enrollmentId`, `cardId`, `customerId`, `occurredAt`          | None                                                                                                                                                                       |
| `reward.redeemed`     | A reward, offer, voucher, or milestone is redeemed | `enrollmentId`, `cardId`, `cost`, `occurredAt`                | `transactionId`, `milestoneId`, `stampRewardId`, `rewardTierId`, `offerId`, `offerTitle`, `discountAmountPence`, `discountKind`, `basketAmountPence`, `source`, `testMode` |
| `tier.changed`        | A status, cashback, or discount tier changes       | `enrollmentId`, `cardId`, `newTierName`, `kind`, `occurredAt` | `source`                                                                                                                                                                   |

`transaction.created.type` is normally `STAMP`, `REDEEM`, or `ADJUST`.
`tier.changed.kind` is `status`, `cashback`, or `discount`.

<AccordionGroup>
  <Accordion title="transaction.created">
    ```json theme={null}
    {
      "transactionId": "txn_123",
      "enrollmentId": "enr_123",
      "customerId": "cus_123",
      "cardId": "card_123",
      "type": "STAMP",
      "delta": 1,
      "amountPence": 1250,
      "balanceAfter": 6,
      "externalTransactionId": "odoo:order:94721",
      "source": "api",
      "testMode": false,
      "occurredAt": "2026-07-30T11:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="coupon.redeemed">
    ```json theme={null}
    {
      "enrollmentId": "enr_123",
      "cardId": "card_coupon_123",
      "customerId": "cus_123",
      "occurredAt": "2026-07-30T11:05:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="reward.redeemed">
    ```json theme={null}
    {
      "transactionId": "txn_124",
      "enrollmentId": "enr_123",
      "cardId": "card_123",
      "cost": 10,
      "rewardTierId": "tier_123",
      "discountAmountPence": 500,
      "discountKind": "FIXED",
      "basketAmountPence": 2500,
      "source": "api",
      "testMode": false,
      "occurredAt": "2026-07-30T11:10:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="tier.changed">
    ```json theme={null}
    {
      "enrollmentId": "enr_123",
      "cardId": "card_123",
      "newTierName": "Gold",
      "kind": "cashback",
      "source": "api",
      "occurredAt": "2026-07-30T11:15:00.000Z"
    }
    ```
  </Accordion>
</AccordionGroup>

## Tickets, gifts, and multipasses

| Event                 | Sent when                                          | Required `data` fields                                                                                             | Optional `data` fields        |
| --------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------- |
| `ticket.purchased`    | A paid ticket fulfilment completes                 | `enrollmentId`, `cardId`, `pricePence`, `sessionId`, `firingId`, `occurredAt`                                      | None                          |
| `ticket.cancelled`    | A ticket event is cancelled                        | `cardId`, `cardName`, `cancellationId`, `attendeeCount`, `reason`, `willRefund`, `occurredAt`                      | None                          |
| `ticket.refunded`     | A ticket payment is refunded or lost to chargeback | `enrollmentId`, `cardId`, `paymentIntentId`, `occurredAt`                                                          | `refundAmountPence`, `reason` |
| `gift.purchased`      | A paid gift fulfilment completes                   | `giftPurchaseId`, `enrollmentId`, `cardId`, `amountPence`, `purchaserEmail`, `recipientEmail`, `occurredAt`        | None                          |
| `gift.redeemed`       | Value is spent from a gift card                    | `enrollmentId`, `amountPence`, `remainingPence`, `occurredAt`                                                      | None                          |
| `multipass.purchased` | A multipass is purchased or topped up              | `enrollmentId`, `cardId`, `customerId`, `visitCount`, `bundleName`, `topUp`, `sessionId`, `firingId`, `occurredAt` | None                          |

<AccordionGroup>
  <Accordion title="ticket.purchased">
    ```json theme={null}
    {
      "enrollmentId": "enr_ticket_123",
      "cardId": "card_ticket_123",
      "pricePence": 2500,
      "sessionId": "checkout_session_123",
      "firingId": "fulfilment_123",
      "occurredAt": "2026-07-30T12:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="ticket.cancelled">
    ```json theme={null}
    {
      "cardId": "card_ticket_123",
      "cardName": "Summer tasting",
      "cancellationId": "cancel_123",
      "attendeeCount": 18,
      "reason": "Venue unavailable",
      "willRefund": true,
      "occurredAt": "2026-07-30T12:05:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="ticket.refunded">
    ```json theme={null}
    {
      "enrollmentId": "enr_ticket_123",
      "cardId": "card_ticket_123",
      "refundAmountPence": 2500,
      "paymentIntentId": "payment_123",
      "occurredAt": "2026-07-30T12:10:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="gift.purchased">
    ```json theme={null}
    {
      "giftPurchaseId": "gift_123",
      "enrollmentId": "enr_gift_123",
      "cardId": "card_gift_123",
      "amountPence": 5000,
      "purchaserEmail": "buyer@example.com",
      "recipientEmail": "recipient@example.com",
      "occurredAt": "2026-07-30T12:15:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="gift.redeemed">
    ```json theme={null}
    {
      "enrollmentId": "enr_gift_123",
      "amountPence": 1250,
      "remainingPence": 3750,
      "occurredAt": "2026-07-30T12:20:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="multipass.purchased">
    ```json theme={null}
    {
      "enrollmentId": "enr_multi_123",
      "cardId": "card_multi_123",
      "customerId": "cus_123",
      "visitCount": 10,
      "bundleName": "Ten visits",
      "topUp": false,
      "sessionId": "checkout_session_456",
      "firingId": "fulfilment_456",
      "occurredAt": "2026-07-30T12:25:00.000Z"
    }
    ```
  </Accordion>
</AccordionGroup>

## Memberships

| Event                  | Sent when                                                      | Required `data` fields                                             | Conditional fields                                                                        |
| ---------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `membership.purchased` | A recurring or one-off membership first activates              | `enrollmentId`, `cardId`, `tierId`, `occurredAt`                   | Recurring purchases include `subscriptionId`; one-off purchases include `billingInterval` |
| `membership.renewed`   | A recurring membership advances to a new billing period        | `enrollmentId`, `cardId`, `tierId`, `subscriptionId`, `occurredAt` | None                                                                                      |
| `membership.cancelled` | A recurring membership becomes cancelled or incomplete-expired | `enrollmentId`, `cardId`, `subscriptionId`, `occurredAt`           | None                                                                                      |

<AccordionGroup>
  <Accordion title="membership.purchased">
    ```json theme={null}
    {
      "enrollmentId": "enr_member_123",
      "cardId": "card_member_123",
      "tierId": "tier_123",
      "subscriptionId": "subscription_123",
      "occurredAt": "2026-07-30T13:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="membership.renewed">
    ```json theme={null}
    {
      "enrollmentId": "enr_member_123",
      "cardId": "card_member_123",
      "tierId": "tier_123",
      "subscriptionId": "subscription_123",
      "occurredAt": "2026-08-30T13:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="membership.cancelled">
    ```json theme={null}
    {
      "enrollmentId": "enr_member_123",
      "cardId": "card_member_123",
      "subscriptionId": "subscription_123",
      "occurredAt": "2026-09-12T13:00:00.000Z"
    }
    ```
  </Accordion>
</AccordionGroup>

## Engagement

| Event                | Sent when                                               | Required `data` fields                                               | Optional `data` fields |
| -------------------- | ------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------- |
| `feedback.submitted` | A customer submits card feedback                        | `enrollmentId`, `cardId`, `rating`, `hasComment`, `occurredAt`       | None                   |
| `automation.fired`   | An automated-message firing is recorded                 | `automationId`, `customerId`, `occurredAt`                           | None                   |
| `broadcast.sent`     | A wallet broadcast reaches terminal delivery processing | `broadcastId`, `cardId`, `targetCount`, `failureCount`, `occurredAt` | None                   |

<AccordionGroup>
  <Accordion title="feedback.submitted">
    ```json theme={null}
    {
      "enrollmentId": "enr_123",
      "cardId": "card_123",
      "rating": 5,
      "hasComment": true,
      "occurredAt": "2026-07-30T14:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="automation.fired">
    ```json theme={null}
    {
      "automationId": "automation_123",
      "customerId": "cus_123",
      "occurredAt": "2026-07-30T14:05:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="broadcast.sent">
    ```json theme={null}
    {
      "broadcastId": "broadcast_123",
      "cardId": "card_123",
      "targetCount": 250,
      "failureCount": 3,
      "occurredAt": "2026-07-30T14:10:00.000Z"
    }
    ```
  </Accordion>
</AccordionGroup>

## Bookings

All booking events use the same schema. Only the event name and resulting
`status` differ.

| Event               | Resulting `status` |
| ------------------- | ------------------ |
| `booking.created`   | `PENDING`          |
| `booking.confirmed` | `CONFIRMED`        |
| `booking.attended`  | `ATTENDED`         |
| `booking.no_show`   | `NO_SHOW`          |
| `booking.cancelled` | `CANCELLED`        |

Required fields are `id`, `provider`, `providerBookingId`, `status`,
`scheduledStart`, `loyaltyCredited`, `source`, and `occurredAt`. The fields
`customerId`, `cardId`, `scheduledEnd`, `attendedAt`, and `resolvedAt` are
nullable. `serviceLabel` and `staffLabel` contain the provider labels saved on
the booking.

<AccordionGroup>
  <Accordion title="booking.created">
    ```json theme={null}
    {
      "id": "booking_123",
      "provider": "ACUITY",
      "providerBookingId": "987654",
      "status": "PENDING",
      "customerId": "cus_123",
      "cardId": "card_123",
      "serviceLabel": "Consultation",
      "staffLabel": "Alex",
      "scheduledStart": "2026-08-02T09:00:00.000Z",
      "scheduledEnd": "2026-08-02T09:30:00.000Z",
      "attendedAt": null,
      "resolvedAt": null,
      "loyaltyCredited": false,
      "source": "acuity_webhook",
      "occurredAt": "2026-07-30T15:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="booking.confirmed">
    ```json theme={null}
    {
      "id": "booking_123",
      "provider": "ACUITY",
      "providerBookingId": "987654",
      "status": "CONFIRMED",
      "customerId": "cus_123",
      "cardId": "card_123",
      "serviceLabel": "Consultation",
      "staffLabel": "Alex",
      "scheduledStart": "2026-08-02T09:00:00.000Z",
      "scheduledEnd": "2026-08-02T09:30:00.000Z",
      "attendedAt": null,
      "resolvedAt": null,
      "loyaltyCredited": false,
      "source": "acuity_webhook",
      "occurredAt": "2026-07-30T15:05:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="booking.attended">
    ```json theme={null}
    {
      "id": "booking_123",
      "provider": "ACUITY",
      "providerBookingId": "987654",
      "status": "ATTENDED",
      "customerId": "cus_123",
      "cardId": "card_123",
      "serviceLabel": "Consultation",
      "staffLabel": "Alex",
      "scheduledStart": "2026-08-02T09:00:00.000Z",
      "scheduledEnd": "2026-08-02T09:30:00.000Z",
      "attendedAt": "2026-08-02T09:31:00.000Z",
      "resolvedAt": "2026-08-02T09:31:00.000Z",
      "loyaltyCredited": true,
      "source": "acuity_webhook",
      "occurredAt": "2026-08-02T09:31:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="booking.no_show">
    ```json theme={null}
    {
      "id": "booking_123",
      "provider": "ACUITY",
      "providerBookingId": "987654",
      "status": "NO_SHOW",
      "customerId": "cus_123",
      "cardId": "card_123",
      "serviceLabel": "Consultation",
      "staffLabel": "Alex",
      "scheduledStart": "2026-08-02T09:00:00.000Z",
      "scheduledEnd": "2026-08-02T09:30:00.000Z",
      "attendedAt": null,
      "resolvedAt": "2026-08-02T10:00:00.000Z",
      "loyaltyCredited": false,
      "source": "operator",
      "occurredAt": "2026-08-02T10:00:00.000Z"
    }
    ```
  </Accordion>

  <Accordion title="booking.cancelled">
    ```json theme={null}
    {
      "id": "booking_123",
      "provider": "ACUITY",
      "providerBookingId": "987654",
      "status": "CANCELLED",
      "customerId": "cus_123",
      "cardId": "card_123",
      "serviceLabel": "Consultation",
      "staffLabel": "Alex",
      "scheduledStart": "2026-08-02T09:00:00.000Z",
      "scheduledEnd": "2026-08-02T09:30:00.000Z",
      "attendedAt": null,
      "resolvedAt": "2026-07-31T17:00:00.000Z",
      "loyaltyCredited": false,
      "source": "acuity_webhook",
      "occurredAt": "2026-07-31T17:00:00.000Z"
    }
    ```
  </Accordion>
</AccordionGroup>

## Diagnostics

| Event          | Sent when                                            | Required `data` fields                |
| -------------- | ---------------------------------------------------- | ------------------------------------- |
| `webhook.test` | An operator sends a test from one webhook's settings | `message`, `webhookId`, `triggeredAt` |

The targeted test ignores the endpoint's subscription filter, but the webhook
must be active.

```json theme={null}
{
  "message": "This is a test event from Perkstar.",
  "webhookId": "webhook_123",
  "triggeredAt": "2026-07-30T16:00:00.000Z"
}
```


## Related topics

- [Receive a perkstar outbound event](/api-reference/outbound-webhooks/receive-a-perkstar-outbound-event.md)
- [Receive webhooks](/guides/webhooks.md)
- [Run the API in Postman](/tools/postman.md)
- [API availability](/support/api-availability.md)
- [Build a POS integration](/guides/pos.md)
