HANGHUT
API Referencev1.0
Get API Key
API v1.0 β€” Live

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.

πŸ”—RESTful JSON API
πŸ”Bearer token authentication
πŸ’³Hosted checkout (GCash, Maya, cards)
⚑100 requests/minute rate limit
🌐5 language SDKs (curl, JS, Python, PHP, Ruby)

Base URL

https://www.hanghut.com/api/v1

Endpoints

GET/events
GET/events/:id
POST/events
PUT/events/:id
GET/events/:id/attendees
GET/events/:id/sections
POST/checkouts
GET/tickets/:id
POST/tickets/:id/check-in
POST/tickets/:id/refund
GET/orders
POST/webhooks
GET/analytics/sales
POST/promo-codes
GET/subscription-tiers
GET/subscriptions

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_.

Keep your keys secret. Never expose them in client-side code or public repositories.

Header

Authorization: Bearer hh_live_3dd28059a859bddb...

Example Request

cURL
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.

100
Requests
60s
Window
429
Exceeded

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.

CodeDescription
400Bad request β€” invalid parameters
401Unauthorized β€” bad or missing API key
404Not found
409Conflict β€” sold out or unavailable
429Rate limit exceeded
500Internal server error

Response

404
{
  "error": {
    "message": "Event not found",
    "status": 404
  }
}
GET/events

Returns a paginated list of events for your organization. Includes ticket tiers and real-time sold counts.

Query Parameters

pageintegerPage numberDefault: 1
per_pageintegerResults per page (max 50)Default: 20
statusstringExact 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
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
    }
  }
}
GET/events/:id

Returns full event details including description, venue, images, and ticket tiers with real-time available counts per tier.

Path Parameters

iduuidEvent IDREQUIRED

Each tier includes an available field (quantity_total - quantity_sold).

Request

cURL
curl "https://www.hanghut.com/api/v1/events/8db0f243-2e64-..." \
  -H "Authorization: Bearer hh_live_your_key"
POST/events

Create 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

titlestringEvent nameREQUIRED
start_datetimeISO 8601Start date/timeREQUIRED
end_datetimeISO 8601End date/time
descriptionstringPlain 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_htmlstringFormatted 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_namestringVenue name
addressstringStreet address
citystringCity
latitudenumberVenue latitude. If omitted, we geocode venue_name + address + city; send both coordinates to skip that.
longitudenumberVenue longitude
capacityintegerMax attendees. Also sizes the default General Admission tier.REQUIRED
ticket_pricenumberPrice in PHP for the default General Admission tier (0 = free)
sales_end_datetimeISO 8601When sales close. Defaults to one hour before start_datetime.
event_typeenumOne of concert, workshop, conference, sports, social, food, nightlife, art, other. Defaults to other.
cover_image_urlstringPublic 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.
Formatting a description? Send 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
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
  }
}
PUT/events/:id

Update 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
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}'
GET/events/:id/attendees

Paginated list of attendees. Filter by ticket status for guest lists or export.

Query Parameters

pageintegerPage numberDefault: 1
per_pageintegerResults per page (max 100)
statusstringOne 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
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 }
  }
}
GET/events/:id/sections

For 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

iduuidEvent IDREQUIRED
largest_block is the number that matters for parties. 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
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": []
  }
}
POST/checkouts

Creates a hosted checkout session. Redirect your customer to the returned URL to complete payment via GCash, Maya, bank transfer, or card.

Always use webhooks to confirm payment β€” the customer may close the browser before being redirected.

Request Body

event_iduuidEvent to buy tickets forREQUIRED
tier_iduuidTicket tier (default if omitted)
section_iduuidSeated 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.
quantityintegerNumber of tickets (min 1)REQUIRED
customer.namestringCustomer full nameREQUIRED
customer.emailstringEmail for ticket deliveryREQUIRED
customer.phonestringPhone number
success_urlstringRedirect after paymentREQUIRED
cancel_urlstringRedirect if cancelled

Request

cURL
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" }
    ]
  }
}
GET/tickets/:id

Verify a ticket's status, check-in state, and associated event/customer details.

This endpoint returns the stored status verbatim β€” a scanned ticket reads used here. /events/:id/attendees aliases that same state to checked_in. Handle both if your code reads from both endpoints.

Path Parameters

iduuidTicket IDREQUIRED

Ticket Statuses

validSold and not yet scanned β€” admit
usedAlready scanned at the door
refundedRefunded β€” do not admit
cancelledCancelled β€” do not admit

Request

cURL
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"
    }
  }
}
POST/tickets/:id/check-in

Mark a ticket as checked in. Returns 409 if already used, refunded, or cancelled.

Build your own QR scanner β€” scan the ticket ID, call this endpoint, and show the result.

Request

cURL
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" }
  }
}
POST/tickets/:id/refund

Mark a ticket as refunded. Updates status and decrements sold count.

Note: This only updates the ticket status. The actual payment refund must be processed separately through your payment provider.

Request

cURL
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 }
  }
}
GET/orders

Paginated purchase orders across all events. Filter by event_id.

Query Parameters

pageintegerPage numberDefault: 1
per_pageintegerResults per page (max 50)
event_iduuidFilter by event

Request

cURL
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.purchasedA ticket was purchased
ticket.refundedA ticket was refunded
ticket.checked_inA ticket was scanned
event.updatedEvent 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

cURL
# Webhook payloads are sent to YOUR endpoint.
# Verify the X-HangHut-Signature header:
# HMAC-SHA256(body, webhook_secret) === signature
POST/webhooks

Register a webhook endpoint. The response includes a secret for signature verification β€” save it, it's only shown once.

Request Body

urlstringHTTPS endpoint URLREQUIRED
eventsstring[]Event types to subscribe toREQUIRED

Request

cURL
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
  }
}
GET/analytics/sales

Revenue analytics with per-event breakdown. Optionally filter by event and date range.

Query Parameters

event_iduuidFilter to specific event
fromISO 8601Start of date range
toISO 8601End of date range

Request

cURL
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
      }
    ]
  }
}
GET/subscription-tiers

Your subscription tiers, cheapest first. A tier is the plan a fan subscribes to β€” the recurring counterpart to a ticket tier.

Read-only. Tiers are created in your dashboard, not through the API.

Request

cURL
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"
      }
    ]
  }
}
GET/subscriptions

Your fan subscriptions, newest first. This is the endpoint to reconcile against: it tells you who is currently entitled to what.

Query Parameters

statusstringFilter by status, e.g. "active", "grace_period", "cancelled"
tier_iduuidOnly subscriptions on this tier
pageintegerPage number (default 1)
per_pageintegerResults per page (default 20)
Entitlement is a date, not a status. Treat a fan as subscribed only while 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
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
    }
  }
}
GET/subscriptions/:id

A single subscription, in the same shape as the list endpoint. Returns404 if it belongs to another partner β€” ownership is never leaked as a 403.

200Subscription returned
404Not found, or not yours

Request

cURL
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
    }
  }
}
POST/subscriptions/:id/cancel

Cancels a subscription at the end of the paid period. The fan keeps access until current_period_end.

Not available yet. This endpoint, and 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.
503Not yet available β€” current behaviour
200Cancelled, once enabled
404Not found, or not yours
409Already cancelled

Request

cURL
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."
}
POST/promo-codes

Create a promo code with percentage or fixed amount discounts, optional usage limits, and expiry.

Request Body

event_iduuidTarget event. Codes are scoped to one eventREQUIRED
codestringPromo code (min 3 chars). Stored and matched uppercaseREQUIRED
discount_typestring"percentage" or "fixed_amount"REQUIRED
discount_amountnumberPercent (max 100) or a peso amountREQUIRED
usage_limitintegerMax uses (unlimited if omitted)
starts_atISO 8601Not redeemable before this. Active immediately if omitted
expires_atISO 8601Expiry date
usage_count is derived, not stored by you. It counts completed orders only, so a code sitting in an abandoned checkout is not consumed and the count can go down if an order is voided.
Events only. Promo codes also exist for experiences, but they are created in the dashboard and are not reachable through this endpoint yet. Passing an experience id to event_id returns 404.

Request

cURL
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.