Guides
There are no API keys to issue, no OAuth app gallery, and no documented REST for third parties. If you need an integration, contact us. Do not POST at payment webhooks; they are not your endpoint.
pheaks.com is a Next.js App Router app. The browser talks to Next.js. Signed-in reads and writes go through that app to a Postgres database (Supabase), with row-level security on. PayMongo Checkout handles any Pheaks convenience fee due (the only money the platform takes) plus Trail Pass and featured billing. Auth is email password, magic link, or Google OAuth. TOTP 2FA must reach AAL2 on every path. Booking mail is transactional email from bookings@pheaks.com.
The product is phone-first: bottom navigation, 48px tap targets, Asia/Manila timestamps, pesos with two decimal places. There is no desktop sidebar and no public social API.
| Layer | Choice |
|---|---|
| UI | Next.js, TypeScript, Tailwind |
| Identity | Supabase Auth (email, magic link, Google) |
| Data | Postgres, RLS, server-side settlement |
| Money | PayMongo Checkout (convenience fee only, when one is due) |
| Time | Stored as timestamptz, shown in +08 |
The client is a view. Prices, capacity, seats, and attendance are not taken from whatever the browser POSTs. Checkout shows a quote. The database accepts or rejects the booking. The server, not the browser, mints the ticket. Coordinators cannot invent an Attended mark without a live lookup. Joiners cannot check themselves in.
Roles are not self-assigned. Organizer approval is a Pheaks decision. Privileged columns are not writable from the account settings screen.
Think in these nouns. They are how the product is shaped, not a public JSON API you can GET with a token.
Booking
A slot is held while you pay. Nothing is charged yet. You can release it.
Payment landed. You have a ticket, a QR, and a typed check-in code.
A coordinator scanned you at the jump-off. You cannot cancel after this.
The slot went back on the trip. Refund rules depend on who cancelled, and when.
Trip date
Only the organizer sees it. Not on the joiner home.
Bookable. Joiners can pick this date.
No slots left. The listing stays visible.
The organizer marked the day done. Certificates and reviews unlock.
The trip was called off. Tickets still show that it did not run.
These URLs are the web app. HTML for humans, plus the usual Next metadata. Listing pages are shareable without a session; booking is not.
| Method | Path | Who |
|---|---|---|
| GET | / | Marketing homepage |
| GET | /events/{id} | Public listing |
| GET | /organizers/{id} | Public organizer profile |
| GET | /guides, /help, /terms, … | Guides and legal |
| GET | /sitemap.xml | Public pages and listed trips |
| POST | /api/webhooks/paymongo | PayMongo only, signature-checked |
Signed-in routes include /home, /bookings, /dashboard, /coordinator, /scan, /profile, and /admin. They require a session and the matching role. Crawlers are asked not to index them.
Mutations are server actions on pheaks.com, bound to the signed-in user, not REST resources you can curl with a bearer token. In product language they do things like: start an online checkout, release an unpaid pending booking, cancel a reserved booking before call time, claim a van seat, look up a ticket, mark attendance, publish or complete a date, assign a coordinator, add a walk-in.
Online payment follows PayMongo's hosted checkout when the charge is at least ₱1. When PayMongo does charge and says the payment paid, Pheaks settles the booking: status becomes Reserved and the ticket exists. That settle is idempotent. Replaying a webhook does not mint a second ticket.
POST /api/webhooks/paymongo is inbound from PayMongo. It is authenticated as PayMongo, not as a joiner. There is no documented request body for third parties, no sandbox key we hand out for this path, and no support for sending your own “paid” events. If a charge did not go through PayMongo, this URL will not help you.
These objects match the Pulag sample used everywhere in the guides. They are illustrations of fields a person can already see, not response contracts, not stable field names you should parse, and not a complete schema.
{
"title": "Mt. Pulag via Ambangeg",
"event_type": "Hike",
"location": "Benguet",
"organizer": "Ridge Runners PH",
"next_start_at": "2026-10-17T03:00:00+08:00",
"status": "Published",
"total_fee": 2500,
"downpayment_required": 500,
"currency": "PHP",
"slots_remaining": 6,
"more_dates": 2,
"pickup_points": [
"SM North EDSA",
"Trinoma",
"Ambangeg Ranger Station"
]
}{
"trip_total": 2500,
"downpayment": 500,
"convenience_fee": 0,
"pay_online_now": 0,
"pay_organizer": 2500,
"currency": "PHP",
"rail": "Organizer Collected",
"note": "One Pheaks convenience fee per slot. The server settles any charge due."
}{
"status": "Reserved",
"rail": "Organizer Collected",
"pickup": "SM North EDSA",
"ticket": {
"qr": "issued when the booking is Reserved",
"check_in_code": "same string the camera reads"
},
"balance_due": 2000
}Money is PHP with two decimal places, rendered with a peso sign and tabular numbers. On-site cash is the trip total minus what that rail already collected for the trip, never a number the joiner types in. Each head consumes a slot and owes one Pheaks convenience fee, quoted on checkout; how Pheaks computes it internally is not part of this document.
Call times are Philippine local. The app does not guess the browser's zone. Philippines has no DST, so a 3:00 a.m. Pulag call is 3:00 a.m. on the ticket.
Fee formulas, listing rank, featured placement internals, database policies, scanner token format beyond “QR and typed code are the same,” and anything that would let someone skip a slot fee or forge attendance. The Help Center and these guides describe the product you use. They are not a spec for rebuilding it.
Humans still answer
Product questions: how it works. Legal: Terms. Inbox: Contact.