HangHut API
Integrate event ticketing directly into your website or mobile app. List events, sell tickets through hosted checkout, and verify tickets at the door β all with simple REST calls.
Base URL
https://www.hanghut.com/api/v1
Endpoints
Authentication
Authenticate every request with your API key in the Authorization header as a Bearer token.
Keys are generated from your Organizer Dashboard. All keys begin with hh_live_.
Header
Authorization: Bearer hh_live_3dd28059a859bddb...
Example Request
curl https://www.hanghut.com/api/v1/events \ -H "Authorization: Bearer hh_live_your_key"
Your API key
Used only by your browser to call this API directly. It is never stored and never sent anywhere else β close the tab and it is gone.
Rate Limits
Keep each API key under 100 requests per minute. Traffic above that ceiling may be throttled with 429, so handle that status with a retry after the window rather than assuming it cannot happen.
Response
429{
"error": {
"message": "Rate limit exceeded. Max 100 requests per minute.",
"status": 429
}
}Wait for the window to reset (60 seconds) before retrying.
Errors
All errors return a consistent JSON structure.
| Code | Description |
|---|---|
| 400 | Bad request β invalid parameters |
| 401 | Unauthorized β bad or missing API key |
| 404 | Not found |
| 409 | Conflict β sold out or unavailable |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Response
404{
"error": {
"message": "Event not found",
"status": 404
}
}/eventsReturns a paginated list of events for your organization. Includes ticket tiers and real-time sold counts.
Query Parameters
| page | integer | Page numberDefault: 1 |
| per_page | integer | Results per page (max 50)Default: 20 |
| status | string | Exact match on one of draft, active, paused, hidden, sold_out, cancelled, completed. Not a list β one value per request. Because it defaults to active, a plain call does NOT return your hidden or paused events even though those are still selling.Default: active |
Request
curl "https://www.hanghut.com/api/v1/events?page=1&per_page=10" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"events": [
{
"id": "bdb74865-8347-...",
"title": "Tinda Tindahan",
"status": "active",
"start_datetime": "2026-04-14T11:07:00+00:00",
"end_datetime": null,
"venue_name": "98 Escolta St",
"city": "Manila",
"capacity": 100,
"cover_image_url": "https://api.hanghut.com/storage/v1/...",
"ticket_price": 700,
"event_type": "art",
"tickets_sold": 10,
"ticket_tiers": [
{
"id": "a8af9742-...",
"name": "General Admission",
"price": 700,
"quantity_total": 100,
"quantity_sold": 0,
"is_active": true,
"sort_order": 0
}
]
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 2,
"total_pages": 1,
"has_more": false
}
}
}/events/:idReturns full event details including description, venue, images, and ticket tiers with real-time available counts per tier.
Path Parameters
| id | uuid | Event IDREQUIRED |
Each tier includes an available field (quantity_total - quantity_sold).
Request
curl "https://www.hanghut.com/api/v1/events/8db0f243-2e64-..." \ -H "Authorization: Bearer hh_live_your_key"
/eventsCreate a new event. Events are created in draft status with one General Admission tier sized to capacity at ticket_price. Publish with PUT {status: "active"}.
Request Body
| title | string | Event nameREQUIRED |
| start_datetime | ISO 8601 | Start date/timeREQUIRED |
| end_datetime | ISO 8601 | End date/time |
| description | string | Plain text. Real newlines are preserved β we convert blank lines to paragraphs and single newlines to line breaks when you do not send description_html. |
| description_html | string | Formatted description, used in preference to description when both are sent. Sanitized on arrival, so unsupported tags and any script are stripped. Send this if you want links, lists or bold. |
| venue_name | string | Venue name |
| address | string | Street address |
| city | string | City |
| latitude | number | Venue latitude. If omitted, we geocode venue_name + address + city; send both coordinates to skip that. |
| longitude | number | Venue longitude |
| capacity | integer | Max attendees. Also sizes the default General Admission tier.REQUIRED |
| ticket_price | number | Price in PHP for the default General Admission tier (0 = free) |
| sales_end_datetime | ISO 8601 | When sales close. Defaults to one hour before start_datetime. |
| event_type | enum | One of concert, workshop, conference, sports, social, food, nightlife, art, other. Defaults to other. |
| cover_image_url | string | Public HTTPS URL of the cover image. Stored exactly as sent and served from wherever you host it β we do not copy or re-host the file, so it must stay publicly reachable for as long as the event is live. |
description_html. Putting HTML in the plain description field works, but it is the field the dashboard editor writes back to β so the next person who opens the event in the UI and saves it can flatten your markup.Request
curl -X POST "https://www.hanghut.com/api/v1/events" \
-H "Authorization: Bearer hh_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Friday Night Comedy",
"description": "An hour of stand-up from Manila's best.",
"event_type": "social",
"start_datetime": "2026-04-10T20:00:00+08:00",
"venue_name": "Comedy Bar Manila",
"address": "Makati Ave, Makati",
"city": "Manila",
"capacity": 150,
"ticket_price": 800
}'Response
201{
"data": {
"id": "f47ac10b-58cc-...",
"title": "Friday Night Comedy",
"status": "draft",
"start_datetime": "2026-04-10T20:00:00+08:00",
"venue_name": "Comedy Bar Manila",
"address": "Makati Ave, Makati",
"city": "Manila",
"latitude": 14.5654,
"longitude": 121.0287,
"capacity": 150,
"event_type": "social",
"ticket_price": 800
}
}/events/:idUpdate event details. Only include fields you want to change. Set status to active to publish.
Accepts the same fields as POST /events, including description_html. Anything you omit is left untouched.
Request
curl -X PUT "https://www.hanghut.com/api/v1/events/f47ac10b-..." \
-H "Authorization: Bearer hh_live_your_key" \
-H "Content-Type: application/json" \
-d '{"status": "active", "capacity": 200}'/events/:id/attendeesPaginated list of attendees. Filter by ticket status for guest lists or export.
Query Parameters
| page | integer | Page numberDefault: 1 |
| per_page | integer | Results per page (max 100) |
| status | string | One of valid (sold, not yet scanned), checked_in (scanned at the door), refunded, cancelled. Omit for all. checked_in is an alias we map to the stored value used. |
Request
curl "https://www.hanghut.com/api/v1/events/8db0f243-.../attendees?status=valid" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"event": { "id": "8db0f243-...", "title": "S10MAIC" },
"attendees": [
{
"ticket_id": "a1b2c3d4-...",
"ticket_number": "TK-00042",
"status": "valid",
"checked_in_at": null,
"customer": {
"name": "Juan Dela Cruz",
"email": "juan@example.com"
},
"tier": { "name": "General Admission", "price": 1000 }
}
],
"meta": { "page": 1, "total": 85, "has_more": true }
}
}/events/:id/sectionsFor seated events: the sections a checkout can name, with live availability. Call this before POST /checkouts to choose a section_id.
General admission events answer { seated: false, sections: [] } β a 200, not an error. Branch on seated rather than treating an empty list as a failure.
Path Parameters
| id | uuid | Event IDREQUIRED |
available counts free seats anywhere in the section; largest_block is the longest run of them side by side. A section with 8 available and a largest_block of 2 cannot seat a group of 4 together β we will split them.A section with on_sale: false is visible on the map but not buyable β its tier is locked or outside its sales window. Both counts are computed at request time and include seats other buyers are holding, so treat them as a snapshot, not a reservation.
Request
curl "https://www.hanghut.com/api/v1/events/8db0f243-.../sections" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"seated": true,
"selection_mode": "both",
"sections": [
{
"id": "0887cc4a-...",
"label": "Orchestra Left",
"available": 42,
"largest_block": 6,
"on_sale": true,
"prices": [
{
"tier_id": "7c33ac2d-...",
"name": "Orchestra",
"price": 1500,
"available": 42,
"largest_block": 6
}
]
}
]
}
}General admission event
Response
200{
"data": {
"seated": false,
"sections": []
}
}/checkoutsCreates a hosted checkout session. Redirect your customer to the returned URL to complete payment via GCash, Maya, bank transfer, or card.
Request Body
| event_id | uuid | Event to buy tickets forREQUIRED |
| tier_id | uuid | Ticket tier (default if omitted) |
| section_id | uuid | Seated events only (required there): the section to seat the party in. We assign the best available seats together, splitting only if the section can't seat them side by side. List sections with GET /events/{id}/sections. |
| quantity | integer | Number of tickets (min 1)REQUIRED |
| customer.name | string | Customer full nameREQUIRED |
| customer.email | string | Email for ticket deliveryREQUIRED |
| customer.phone | string | Phone number |
| success_url | string | Redirect after paymentREQUIRED |
| cancel_url | string | Redirect if cancelled |
Request
curl -X POST "https://www.hanghut.com/api/v1/checkouts" \
-H "Authorization: Bearer hh_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"event_id": "8db0f243-2e64-...",
"tier_id": "66a5cdd3-d097-...",
"quantity": 2,
"customer": {
"name": "Juan Dela Cruz",
"email": "juan@example.com"
},
"success_url": "https://your-site.com/success",
"cancel_url": "https://your-site.com/cancel"
}'Response
201{
"data": {
"checkout_id": "pi_abc123def456",
"checkout_url": "https://checkout.hanghut.com/...",
"expires_at": "2026-03-22T00:00:00Z",
"assigned_seats": [ // seated events only
{ "section": "Orchestra Left", "row": "D", "seat": 12, "label": "D12" },
{ "section": "Orchestra Left", "row": "D", "seat": 13, "label": "D13" }
]
}
}/tickets/:idVerify a ticket's status, check-in state, and associated event/customer details.
used here. /events/:id/attendees aliases that same state to checked_in. Handle both if your code reads from both endpoints.Path Parameters
| id | uuid | Ticket IDREQUIRED |
Ticket Statuses
valid | Sold and not yet scanned β admit |
used | Already scanned at the door |
refunded | Refunded β do not admit |
cancelled | Cancelled β do not admit |
Request
curl "https://www.hanghut.com/api/v1/tickets/a1b2c3d4-..." \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"id": "a1b2c3d4-...",
"status": "valid",
"checked_in_at": null,
"purchased_at": "2026-03-21T10:30:00Z",
"event": {
"id": "8db0f243-...",
"title": "S10MAIC",
"start_datetime": "2026-03-27T19:00:00+00:00",
"venue_name": "Mow's"
},
"tier": {
"name": "General Admission",
"price": 1000
},
"customer": {
"name": "Juan Dela Cruz",
"email": "juan@example.com"
}
}
}/tickets/:id/check-inMark a ticket as checked in. Returns 409 if already used, refunded, or cancelled.
Request
curl -X POST "https://www.hanghut.com/api/v1/tickets/a1b2c3d4-.../check-in" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"id": "a1b2c3d4-...",
"status": "used",
"checked_in_at": "2026-03-27T19:15:00Z",
"event": { "id": "8db0f243-...", "title": "S10MAIC" },
"customer": { "name": "Juan Dela Cruz" }
}
}/tickets/:id/refundMark a ticket as refunded. Updates status and decrements sold count.
Request
curl -X POST "https://www.hanghut.com/api/v1/tickets/a1b2c3d4-.../refund" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"id": "a1b2c3d4-...",
"status": "refunded",
"event": { "id": "8db0f243-...", "title": "S10MAIC" },
"tier": { "name": "General Admission", "price": 1000 }
}
}/ordersPaginated purchase orders across all events. Filter by event_id.
Query Parameters
| page | integer | Page numberDefault: 1 |
| per_page | integer | Results per page (max 50) |
| event_id | uuid | Filter by event |
Request
curl "https://www.hanghut.com/api/v1/orders?event_id=8db0f243-..." \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"orders": [
{
"id": "9d13f49e-...",
"event": { "id": "bdb74865-...", "title": "Tinda Tindahan" },
"customer": {
"name": "Juan Dela Cruz",
"email": "juan@example.com",
"phone": "+639171234567"
},
"quantity": 1,
"total_amount": 720,
"subtotal": 700,
"status": "completed",
"payment_method": "GCASH",
"paid_at": "2026-03-30T08:44:12.267+00:00",
"created_at": "2026-03-30T08:39:23.276+00:00"
}
],
"meta": { "page": 1, "per_page": 20, "total": 32, "total_pages": 2, "has_more": true }
}
}Webhooks
Receive real-time notifications when events happen. Register an HTTPS endpoint and we'll POST signed payloads.
Available Events
| ticket.purchased | A ticket was purchased |
| ticket.refunded | A ticket was refunded |
| ticket.checked_in | A ticket was scanned |
| event.updated | Event details changed |
Each delivery includes an X-HangHut-Signature header (HMAC-SHA256) for verification.
Response
200{
"id": "evt_abc123...",
"type": "ticket.purchased",
"created_at": "2026-03-21T10:30:00Z",
"data": {
"ticket_id": "a1b2c3d4-...",
"event_id": "8db0f243-...",
"customer": { "name": "Juan Dela Cruz" },
"amount": 1000
}
}Verify Signature
# Webhook payloads are sent to YOUR endpoint. # Verify the X-HangHut-Signature header: # HMAC-SHA256(body, webhook_secret) === signature
/webhooksRegister a webhook endpoint. The response includes a secret for signature verification β save it, it's only shown once.
Request Body
| url | string | HTTPS endpoint URLREQUIRED |
| events | string[] | Event types to subscribe toREQUIRED |
Request
curl -X POST "https://www.hanghut.com/api/v1/webhooks" \
-H "Authorization: Bearer hh_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-site.com/webhook",
"events": ["ticket.purchased", "ticket.refunded"]
}'Response
201{
"data": {
"id": "wh_abc123...",
"url": "https://your-site.com/webhook",
"events": ["ticket.purchased", "ticket.refunded"],
"secret": "whsec_a1b2c3d4e5f6...",
"is_active": true
}
}/analytics/salesRevenue analytics with per-event breakdown. Optionally filter by event and date range.
Query Parameters
| event_id | uuid | Filter to specific event |
| from | ISO 8601 | Start of date range |
| to | ISO 8601 | End of date range |
Request
curl "https://www.hanghut.com/api/v1/analytics/sales?from=2026-03-01&to=2026-03-31" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"total_revenue": 11856.5,
"total_tickets_sold": 35,
"total_orders": 32,
"total_discounts": 285,
"date_range": { "from": null, "to": null },
"events": [
{
"id": "bdb74865-...",
"title": "Tinda Tindahan",
"start_datetime": "2026-04-14T11:07:00+00:00",
"capacity": 100,
"revenue": 4960,
"tickets_sold": 7,
"orders": 7,
"discount_total": 0
}
]
}
}/subscription-tiersYour subscription tiers, cheapest first. A tier is the plan a fan subscribes to β the recurring counterpart to a ticket tier.
Request
curl "https://www.hanghut.com/api/v1/subscription-tiers" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"tiers": [
{
"id": "3f2a1b9c-...",
"name": "Backstage",
"description": "Early access to tickets and monthly livestreams",
"price_monthly": 250,
"is_active": true,
"perks": ["Early ticket access", "Monthly livestream"],
"created_at": "2026-02-11T08:14:22Z"
}
]
}
}/subscriptionsYour fan subscriptions, newest first. This is the endpoint to reconcile against: it tells you who is currently entitled to what.
Query Parameters
| status | string | Filter by status, e.g. "active", "grace_period", "cancelled" |
| tier_id | uuid | Only subscriptions on this tier |
| page | integer | Page number (default 1) |
| per_page | integer | Results per page (default 20) |
current_period_end is in the future. A status of active on a lapsed period is not access, and grace_period means payment failed but access has not been withdrawn yet.Request
curl "https://www.hanghut.com/api/v1/subscriptions?status=active&page=1&per_page=20" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"subscriptions": [
{
"id": "9c4e7d21-...",
"status": "active",
"tier": {
"id": "3f2a1b9c-...",
"name": "Backstage",
"price_monthly": 250
},
"customer": {
"id": "b7d1f004-...",
"email": "juan@example.com",
"name": "Juan Dela Cruz"
},
"currency": "PHP",
"interval": "monthly",
"current_period_start": "2026-08-01T00:00:00Z",
"current_period_end": "2026-09-01T00:00:00Z",
"cancelled_at": null,
"created_at": "2026-06-01T09:12:00Z",
"updated_at": "2026-08-01T00:00:04Z"
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1,
"has_more": false
}
}
}/subscriptions/:idA single subscription, in the same shape as the list endpoint. Returns404 if it belongs to another partner β ownership is never leaked as a 403.
200 | Subscription returned |
404 | Not found, or not yours |
Request
curl "https://www.hanghut.com/api/v1/subscriptions?status=active&page=1&per_page=20" \ -H "Authorization: Bearer hh_live_your_key"
Response
200{
"data": {
"subscriptions": [
{
"id": "9c4e7d21-...",
"status": "active",
"tier": {
"id": "3f2a1b9c-...",
"name": "Backstage",
"price_monthly": 250
},
"customer": {
"id": "b7d1f004-...",
"email": "juan@example.com",
"name": "Juan Dela Cruz"
},
"currency": "PHP",
"interval": "monthly",
"current_period_start": "2026-08-01T00:00:00Z",
"current_period_end": "2026-09-01T00:00:00Z",
"cancelled_at": null,
"created_at": "2026-06-01T09:12:00Z",
"updated_at": "2026-08-01T00:00:04Z"
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1,
"has_more": false
}
}
}/subscriptions/:id/cancelCancels a subscription at the end of the paid period. The fan keeps access until current_period_end.
POST /subscriptions, currently return 503. Recurring billing is not wired up, so creating or cancelling a subscription through the API would change our records without changing what the fan is actually charged. Both are documented here so you can build against them now; the read endpoints above are live and unaffected.503 | Not yet available β current behaviour |
200 | Cancelled, once enabled |
404 | Not found, or not yours |
409 | Already cancelled |
Request
curl -X POST "https://www.hanghut.com/api/v1/subscriptions/sub_id/cancel" \ -H "Authorization: Bearer hh_live_your_key"
Response
503{
"error": "Subscription management via API is not yet available."
}/promo-codesCreate a promo code with percentage or fixed amount discounts, optional usage limits, and expiry.
Request Body
| event_id | uuid | Target event. Codes are scoped to one eventREQUIRED |
| code | string | Promo code (min 3 chars). Stored and matched uppercaseREQUIRED |
| discount_type | string | "percentage" or "fixed_amount"REQUIRED |
| discount_amount | number | Percent (max 100) or a peso amountREQUIRED |
| usage_limit | integer | Max uses (unlimited if omitted) |
| starts_at | ISO 8601 | Not redeemable before this. Active immediately if omitted |
| expires_at | ISO 8601 | Expiry date |
event_id returns 404.Request
curl -X POST "https://www.hanghut.com/api/v1/promo-codes" \
-H "Authorization: Bearer hh_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"event_id": "8db0f243-...",
"code": "EARLYBIRD",
"discount_type": "percentage",
"discount_amount": 20,
"usage_limit": 50,
"expires_at": "2026-04-01T00:00:00Z"
}'Response
201{
"data": {
"id": "pc_abc123...",
"code": "EARLYBIRD",
"discount_type": "percentage",
"discount_amount": 20,
"usage_limit": 50,
"usage_count": 0,
"is_active": true
}
}Need help integrating? Contact support@hanghut.com
Β© 2026 HangHut. All rights reserved.