Event Group Tickets ("Buy Together") API

Author: Josh S. Sakweli, Backend Lead Team Last Updated: 2026-10-07 Version: v1.0

Base URL: http://localhost:8765/api/v1 (local) — https://dev.api.nexgate.co/api/v1 (staging)

v1.0 — tested end to end locally on 2026-10-07: starting and filling a group, two buyers racing for the last ticket, moving tickets between groups, a group expiring with refunds, cancelling a group booking, an organizer payout and an event cancel. The JSON samples below are real local responses.

Short Description: An organizer can let people buy a paid ticket type together at a cheaper group price. One buyer starts a group (for example "3 tickets at 15,000 instead of 20,000"), friends join with a code, and when the group is full everyone gets their tickets. If the group does not fill in time, everyone is refunded automatically.

Hints:


What is new — all endpoints at a glance

Group tickets are new; nothing existing was reshaped. Existing endpoints only gained fields.

Endpoint New / changed What for
PATCH /e-events/tickets/{ticketId}/group-buying New Organizer turns group buying on/off for a ticket type
GET /e-events/tickets/{eventId}/{ticketId} and the ticket list Changed — new groupBuying object Organizer sees available / held in groups / paid / open groups / sold
POST /e-events/checkout Changed — new optional group object Start a group (groupName) or join one (groupId)
POST /e-events/checkout/{sessionId}/payment Same Pays; fills the group if it was the last place
GET /e-events/ticket-groups/ticket/{ticketTypeId}/open New Open groups for a ticket
GET /e-events/ticket-groups/code/{groupCode} New Look up a shared code
GET /e-events/ticket-groups/{groupId} New One group
GET /e-events/ticket-groups/my New My groups
All four group reads above Changed — new participants list See who is in the group and how many tickets each holds
POST /e-events/ticket-groups/{groupId}/transfer New Move any number of my tickets to another group of the same ticket type
POST /e-events/ticket-groups/{groupId}/withdraw New Leave with any number of my tickets, full refund
PATCH /e-events/booking-orders/{bookingId}/cancel Same Also works for bookings that came from a group

When does a group end? A timer for its exact deadline, plus a check every 10 minutes as a safety net. Tickets are issued in the same moment as the payment that fills the group — there is no gap where a full group has no tickets.


Part 0 — Which part do I need?

I am building… Read
The organizer's ticket settings screen Part 1
The ticket page: "Buy together", open groups, join by code Part 2 and Part 3
"My groups", moving tickets, leaving a group Part 4
Notifications and where a tap goes Part 5

Part 1 — Organizer: turn group buying on

PATCH /e-events/tickets/{ticketId}/group-buying

Only the event organizer can call it.

{
  "enabled": true,
  "groupPrice": 15000,
  "groupSize": 3,
  "timeLimitHours": 24
}
Field Rule
enabled Required. false turns it off; groups already running carry on and finish normally
groupPrice Required when turning on. Above 0 and below the ticket price
groupSize Required when turning on. At least 2, at most the ticket's total quantity
timeLimitHours Required when turning on. 1 to 720

Changing price or size later does not affect groups already running — each group keeps the price and size it started with.

Errors (all 400, message shown as-is):

The groupBuying object on ticket responses

Every ticket response (GET /e-events/tickets/{eventId}/{ticketId}, the event's ticket list, and the PATCH above) now has a groupBuying object. It is missing when group buying is off and no group is running.

"groupBuying": {
  "enabled": true,
  "groupPrice": 15000.0,
  "groupSize": 3,
  "timeLimitHours": 24,
  "availableToBuy": 14,
  "heldInOpenGroups": 3,
  "paidInOpenGroups": 2,
  "openGroups": 1,
  "sold": 0
}
Field Meaning
availableToBuy Tickets anyone can still buy right now
heldInOpenGroups Tickets kept back for groups that are still filling (a whole group's worth is kept from the moment it starts)
paidInOpenGroups How many of those have actually been paid for
openGroups Groups running now
sold Tickets that became real bookings, including from filled groups

Part 2 — Browse groups

GET /e-events/ticket-groups/ticket/{ticketTypeId}/open

Open groups for a ticket type, soonest deadline first. Use it for the list on the ticket page.

GET /e-events/ticket-groups/code/{groupCode}

Look a group up by the code a friend shared (TG- + 6 characters, case does not matter).

GET /e-events/ticket-groups/{groupId}

One group.

A group looks like this (same shape everywhere):

{
  "groupId": "3a6d…",
  "groupCode": "TG-U9LDUE",
  "groupName": "Yoyo Crew",
  "eventId": "1635…",
  "eventTitle": "Group Ticket Test Night",
  "ticketTypeId": "8a31…",
  "ticketTypeName": "VIP Pass",
  "regularPrice": 20000.00,
  "groupPrice": 15000.00,
  "totalSeats": 3,
  "seatsOccupied": 2,
  "seatsRemaining": 1,
  "members": 1,
  "status": "OPEN",
  "initiatorUserName": "mamayoyo",
  "createdAt": "2026-10-07T11:34:55",
  "expiresAt": "2026-10-08T11:34:55",
  "myTickets": 2,
  "participants": [
    {
      "userId": "7c1e…",
      "userName": "mamayoyo",
      "userProfileMedia": { "fileId": "…", "status": "READY", "thumbUrl": "https://…", "mimeType": "image/jpeg" },
      "tickets": 2,
      "joinedAt": "2026-10-07T11:34:55",
      "isMe": true
    }
  ]
}
Field Notes
status OPEN, COMPLETED (full, tickets issued) or FAILED (ended, refunded)
endReason Only when ended: FILLED, EXPIRED, EMPTIED (everyone moved out), EVENT_CANCELLED, TICKET_DELETED
endedAt Only when ended
myTickets How many of the viewer's tickets are in this group. Missing when none
participants Who is in the group, first to join first. One entry per person, with how many tickets they hold. No money is shown. Empty once a group has FAILED (everyone was refunded)
participants[].isMe true on the viewer's own entry, missing on everyone else's — use it to highlight "you"

Part 3 — Start or join a group at checkout

The normal event checkout, with one extra object, group.

POST /e-events/checkout

Start a group:

{
  "eventId": "1635…",
  "ticketTypeId": "8a31…",
  "ticketsForMe": 2,
  "group": { "groupName": "Yoyo Crew" }
}

Join a group:

{
  "eventId": "1635…",
  "ticketTypeId": "8a31…",
  "ticketsForMe": 1,
  "group": { "groupId": "3a6d…" }
}

The price in the response is already the group price. The response has a new group object:

"group": { "groupName": "Yoyo Crew", "startsNewGroup": true }

(when joining: { "groupId": "3a6d…", "startsNewGroup": false })

Then pay exactly as today: POST /e-events/checkout/{sessionId}/payment.

What happens on payment:

Checkout errors (400):

Payment error you must handle: This group just filled up or closed. You have not been charged. — two people paid for the last place at the same moment and the other one got it. Nothing was taken from the wallet; send the user back to the group list.


Part 4 — My groups and moving tickets

GET /e-events/ticket-groups/my

Every group the user has (or had) tickets in, newest first, same shape as Part 2. myTickets tells you how many are theirs.

POST /e-events/ticket-groups/{groupId}/transfer

Move some or all of your tickets from this group ({groupId}, must be open) into another open group of the same event and same ticket type.

{ "targetGroupId": "7c1e…", "quantity": 1 }

Returns the target group. No money moves — the price is the same.

Errors (400):

POST /e-events/ticket-groups/{groupId}/withdraw

Leave an open group with some or all of your tickets and get them refunded in full straight away.

{ "quantity": 1 }

Returns the group with the new count (myTickets shows what you still hold). The places open up again for others. If nobody is left, the group ends with endReason: EMPTIED and its held tickets go back on sale.

To swap only part of your tickets into another group, withdraw them here and join the other group with a new checkout.

Errors (400): You can only leave a group that is still open, You have 2 tickets in this group, not 5.

Cancelling a booking that came from a group

Unchanged endpoint: PATCH /e-events/booking-orders/{bookingId}/cancel. It refunds exactly the tickets in that booking and puts them back on sale.


Part 5 — Notifications

All four open the group: targetType: "EVENT_TICKET_GROUP", targetId = the group id → GET /e-events/ticket-groups/{targetId}. As with every notification, an unknown targetType stays on the bell list.

Type Who gets it When
EVENT_TICKET_GROUP_CREATED The buyer who started it Group opened — prompt them to share the code
EVENT_TICKET_GROUP_JOINED Everyone in the group Someone joined or moved tickets in — refresh the "2 of 3"
EVENT_TICKET_GROUP_COMPLETED Everyone in the group Full — tickets are in My bookings
EVENT_TICKET_GROUP_FAILED Everyone still in the group Ended without filling — refunded in full

The refund itself also sends the existing ESCROW_REFUNDED notice, once per person (not once per ticket).


Revision #1
Created 31 October 2026 05:00:58 by Admin Qbit
Updated 31 October 2026 05:12:41 by Admin Qbit