openapi: 3.0.3
info:
  title: Handling Webhook Events API
  description: >
    Webhook API specification for receiving real-time notifications from The
    Bookings Group system.

    Distributors must implement this endpoint to receive notifications about
    booking updates

    and inbound messages from operators via their Table Management System (TMS).


    ## Event Types


    | `eventType`        |
    Description                                              | Populated fields
    |

    |--------------------|----------------------------------------------------------|-----------------|

    | `booking.updated`  | A booking was created, updated, or
    cancelled             | `bookings`       |

    | `message.received` | One or more operator messages were sent for a
    booking    | `messages`       |


    ## Authentication


    Every request includes an `X-API-Key` header containing an HMAC-SHA256 hex
    digest of the

    raw request body, signed with your API key. Verify this signature before
    processing the payload:


    ```

    expected = HMAC-SHA256(key=YOUR_API_KEY, message=raw_request_body).hex()

    assert request.headers["X-API-Key"] == expected

    ```


    ## Response Codes


    Return `204 No Content` on success. Any non-2xx response causes the delivery
    to be considered

    failed. Respond within 30 seconds to avoid a timeout.
  version: 1.5.0
  contact:
    name: Bookable
    url: https://www.bookabletech.com
    email: hello@bookabletech.com
  license:
    name: Proprietary
servers:
  - url: https://your-distributor-domain.com
    description: Distributor webhook endpoint
paths:
  /:
    post:
      summary: Receive booking and messaging notifications
      description: >
        Endpoint that distributors must implement to receive notifications.

        The Bookings Group system sends HTTP POST requests to this endpoint
        when:

        - A booking is updated (`booking.updated`)

        - An operator sends a message via their TMS (`message.received`)


        Use `eventType` to determine which top-level field (`bookings` or
        `messages`) is populated.
      operationId: receiveBookingNotification
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BookingNotification'
            examples:
              bookingUpdated:
                $ref: '#/components/examples/BookingUpdatedNotification'
              messageReceived:
                $ref: '#/components/examples/MessageReceivedNotification'
      responses:
        '204':
          description: Notification received and processed successfully
        '400':
          description: Bad request - invalid payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: validation_error
                message: Required field 'booking.id' is missing
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - apiKey: []
components:
  schemas:
    BookingNotification:
      type: object
      description: |
        Top-level webhook payload. `eventType` acts as a discriminator:
        - `booking.updated` → `bookings` is populated, `messages` is null
        - `message.received` → `messages` is populated, `bookings` is null
      required:
        - eventType
        - timestamp
      properties:
        eventType:
          type: string
          enum:
            - booking.updated
            - message.received
          description: Type of event that triggered the notification
          example: booking.updated
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the event was dispatched
          example: '2025-06-17T10:30:00Z'
        bookings:
          type: array
          items:
            $ref: '#/components/schemas/Booking'
          nullable: true
          description: >-
            Booking data. Present when `eventType` is `booking.updated`, null
            otherwise.
        messages:
          type: array
          items:
            $ref: '#/components/schemas/MessagePayload'
          nullable: true
          description: >
            Message data. Present when `eventType` is `message.received`, null
            otherwise.

            All messages in a single notification belong to the same booking.
    Booking:
      type: object
      required:
        - id
        - compositeId
        - date
        - time
        - partySize
        - status
        - reference
        - firstName
        - lastName
        - email
        - duration
        - createdDate
        - lastUpdate
      properties:
        id:
          type: string
          description: A globally unique identifier for the reservation
          example: 29|X9|275cc44dd2e2496fba44857c9257443a|B
        compositeId:
          type: string
          description: A globally unique identifier for the venue/product
          example: >-
            29|X9|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b
        date:
          type: string
          format: date
          description: The date of the reservation (YYYY-MM-DD)
          example: '2025-06-25'
        time:
          type: string
          format: time
          description: The time of the reservation (HH:MM:SS)
          example: '19:30:00'
        partySize:
          type: integer
          minimum: 1
          maximum: 50
          description: Number of guests for the reservation
          example: 4
        status:
          allOf:
            - $ref: '#/components/schemas/BookingStatusEnum'
          description: Current status of the reservation
        reference:
          type: string
          maxLength: 100
          description: >-
            Reference code for the reservation as originally provided by the
            distributor
          example: REF-20250617-001
        firstName:
          type: string
          maxLength: 100
          description: Guest's first name
          example: John
        lastName:
          type: string
          maxLength: 100
          description: Guest's last name
          example: Doe
        email:
          type: string
          format: email
          maxLength: 255
          description: Email address of the guest
          example: john.doe@example.com
        phone:
          type: string
          nullable: true
          maxLength: 50
          description: Phone number of the guest
          example: '+1234567890'
        company:
          type: string
          maxLength: 200
          nullable: true
          description: Company name if the reservation is business-related
          example: Acme Corp
        location:
          allOf:
            - $ref: '#/components/schemas/LocationDto'
          nullable: true
          description: Location details for the reservation
        duration:
          type: integer
          minimum: 15
          maximum: 1440
          description: Duration of the reservation in minutes
          example: 120
        productName:
          type: string
          maxLength: 100
          nullable: true
          description: Name of product or service reserved
          example: dinner
        productType:
          type: string
          maxLength: 100
          nullable: true
          deprecated: true
          description: >-
            Type of product or service reserved. Deprecated - use productName
            instead.
          example: dinner
        notes:
          type: string
          maxLength: 1000
          nullable: true
          description: Additional notes for the reservation
          example: Window table preferred
        source:
          type: string
          description: The distributor source that made the booking
          example: MyDistributor
        createdDate:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the reservation was created
          example: '2025-06-17T09:15:00Z'
        lastUpdate:
          type: string
          format: date-time
          description: ISO 8601 timestamp of the last reservation update
          example: '2025-06-17T10:30:00Z'
        preorders:
          type: array
          nullable: true
          description: List of preordered packages for the booking
          items:
            type: object
            properties:
              packageId:
                type: string
                description: Unique identifier for the preordered package
              name:
                type: string
                description: Package name
                example: '*Pan roasted Padron peppers (V)'
              description:
                type: string
                description: Detailed package description
                example: >-
                  5 items per guest: Salmon & celeriac on rye, Buttermilk fries
                  chicken...
              type:
                type: string
                description: Main package type
                example: food
              quantity:
                type: integer
                description: Quantity of the preordered package
              price:
                type: number
                format: float
                description: Package price
                example: 5.57
            required:
              - packageId
              - quantity
        preorderMenus:
          type: array
          nullable: true
          description: List of preordered menus for the booking
          items:
            type: object
            properties:
              menuId:
                type: string
                description: Unique identifier for the preordered menu
            required:
              - menuId
    LocationDto:
      type: object
      properties:
        address:
          type: string
          maxLength: 200
          description: Street address
          example: 123 Main Street
        city:
          type: string
          maxLength: 100
          description: City name
          example: New York
        state:
          type: string
          maxLength: 100
          description: State or province
          example: NY
        country:
          type: string
          maxLength: 100
          description: Country name
          example: US
        postalCode:
          type: string
          maxLength: 20
          description: Postal or ZIP code
          example: '10001'
    BookingStatusEnum:
      type: string
      enum:
        - pending
        - in_progress
        - confirmed
        - cancelled
        - deleted
        - lost
      description: >-
        Current status of the reservation. Mirrors the Bookings API booking
        statuses (`Pending`, `InProgress`, `Confirmed`, `Cancelled`, `Deleted`,
        `Lost`).
    MessagePayload:
      type: object
      description: A single message sent by an operator via their TMS (e.g. Collins)
      required:
        - bookingId
        - body
        - sentAt
      properties:
        bookingId:
          type: string
          description: >-
            The globally unique booking identifier. Matches the `id` field in
            `booking.updated` events.
          example: 29|X9|275cc44dd2e2496fba44857c9257443a|B
        body:
          type: string
          description: Message body text
          example: Hi, just confirming your table for 4 at 19:30 — see you then!
        senderName:
          type: string
          nullable: true
          description: Display name of the operator or staff member who sent the message
          example: The Ivy Team
        sentAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the message was sent by the operator
          example: '2025-06-17T14:22:00Z'
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Error code or short description
          example: validation_error
        message:
          type: string
          description: Detailed error message
          example: Required field 'booking.id' is missing
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: >
        HMAC-SHA256 hex digest of the raw request body, signed with the
        distributor's API key.

        Verify this on every request to confirm the payload originated from The
        Bookings Group.
  examples:
    BookingUpdatedNotification:
      summary: Booking updated
      description: Sent when a booking's details or status change
      value:
        eventType: booking.updated
        timestamp: '2025-06-17T11:45:00Z'
        bookings:
          - id: 29|X9|275cc44dd2e2496fba44857c9257443a|B
            compositeId: >-
              29|X9|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b
            date: '2025-06-25'
            time: '21:30:00'
            partySize: 6
            status: confirmed
            reference: REF-20250617-001
            firstName: John
            lastName: Doe
            email: john.doe@example.com
            phone: '+1234567890'
            company: null
            location: null
            duration: 120
            productName: dinner
            productType: dinner
            notes: Anniversary dinner — window table if possible
            source: MyDistributor
            createdDate: '2025-06-17T09:15:00Z'
            lastUpdate: '2025-06-17T11:45:00Z'
            preorders: []
            preorderMenus: []
        messages: null
    MessageReceivedNotification:
      summary: Message received from venue
      description: Sent when a venue sends one or more messages in response to a booking
      value:
        eventType: message.received
        timestamp: '2025-06-17T14:25:00Z'
        bookings: null
        messages:
          - bookingId: 29|X9|275cc44dd2e2496fba44857c9257443a|B
            body: Hi, just confirming your table for 6 at 21:30 — see you then!
            senderName: The Ivy Team
            sentAt: '2025-06-17T14:22:00Z'
          - bookingId: 29|X9|275cc44dd2e2496fba44857c9257443a|B
            body: Your table is ready. Please arrive 10 minutes early.
            senderName: The Ivy Team
            sentAt: '2025-06-17T14:23:00Z'
