Loading...
Give your team's coding agent this page and it can build a working booking site on the Vihaya API. Ticketing, payment, QR codes and check-in already exist — the agent only has to build your front end and call us.
This is written to be pasted whole. It points the agent at the machine-readable context rather than inlining it, so the agent reads the API as it is today instead of trusting what it remembers.
The brief
Paste this into Claude Code, Cursor, or any coding agent.
Build a ticket-booking website using the Vihaya Events API as the backend.
Before writing any code, fetch and read these:
https://docs.vihaya.app/llms-full.txt — every endpoint, field, error code, and the traps that are not in the schema
https://docs.vihaya.app/starter-kit.txt — a working Next.js storefront you may copy from
Non-negotiables:
1. The Vihaya API key is server-side only. It goes in .env.local as VIHAYA_API_KEY with no
NEXT_PUBLIC_ prefix, and is read only inside route handlers or server components. The browser
must never see it. If you are about to put it in a client component, stop.
2. Paid registration is TWO calls to POST /api/v1/events/{id}/register. The first returns
{ orderId, registrationId, amount, currency, key }; you then open Razorpay with those; then you
call the SAME endpoint again with exactly { registrationId, orderId, paymentId }. Do not send a
signature field — the endpoint has none. Only the second call confirms.
3. A free event confirms on the FIRST call and returns { success, registrationId } instead. Branch
on which shape came back, not on what you think the price is.
4. amount is already in the minor unit (paise for INR). Pass it to Razorpay unchanged.
5. Do not build ticketing, QR codes, check-in or emails. Vihaya already does all of that and emails
the ticket itself. Build the storefront and call the API.
What I want:
- A listing page of published events.
- An event detail page with description, date, venue and remaining capacity.
- A booking form that handles both the free and the paid path, and shows real errors
(sold out, validation, payment cancelled) rather than a generic failure.
- A confirmation state that tells the user their ticket has been emailed.
Stack: Next.js App Router + TypeScript. Ask me before adding any other dependency.You will need a key first — create one in the developer portal. A Checkout only key is enough for a storefront and cannot read your attendee list. See Authentication.
All four are plain text, generated at build time from the running API. Nothing here is retyped by hand, so nothing here can quietly fall behind the service.
The short map. What exists and where.
https://docs.vihaya.app/llms.txtEndpoints, fields, errors, gotchas. Built from the live spec.
https://docs.vihaya.app/llms-full.txtThe whole booking storefront as one file.
https://docs.vihaya.app/starter-kit.txtGenerated from the routes themselves.
https://events.vihaya.app/api/v1/openapi.jsonWorking in a terminal? Pipe the context straight in:
curl -s https://docs.vihaya.app/llms-full.txtThese are in the context bundle too, so you do not have to relay them — but they are the things worth reviewing in whatever your agent produces.
Never put the API key in the browser
The key is a bearer credential scoped to a whole account. Call the Vihaya API from your own server (a route handler, a server action, an edge function) and let the browser talk only to your server. A key pasted into client-side code is readable by every visitor. If you must call from a browser, use a Checkout-only key AND pin it to your origin.
Paying is two calls to the same endpoint, not one
POST /api/v1/events/{id}/register with the attendee details returns { orderId, registrationId, amount, currency, key } — it does NOT confirm anything yet. Open Razorpay checkout with that orderId, then call the SAME endpoint a second time with exactly { registrationId, orderId, paymentId }. Only the second call confirms. There is no `signature` field on this endpoint — do not send one. A free event confirms on the first call and returns { success, registrationId } instead, so branch on which shape came back rather than assuming.
A paymentId is not proof of payment
Do not mark anything paid in your own database because you saw a paymentId. The server verifies it against Razorpay and binds it to both the order and the registration before confirming. Treat the second call's response as the only source of truth.
A new event is a draft until you publish it
POST /api/v1/events creates a draft. It is not visible to anyone and cannot take bookings. Publish with PATCH /api/v1/events/{id} sending { status: "published" }. Publishing requires organiser access on the account, so a key that can create may still be refused at publish time with 403.
Tracks are addressed by index, and the index is positional
A track is edited with PATCH /api/v1/events/{id}/tracks/{index} where index is its position in the array, zero-based. It is not a stable id. Re-read the tracks before patching if anything may have been added or reordered in between.
Pick the smaller key
A Checkout-only key can read an event, its tracks and seat counts, and can create registrations and take payment. It deliberately CANNOT read attendee names, emails or phones, create or publish events, or email anyone. If you are building a storefront, that is the key you want, and GET /registrations will 403 — which is correct, not a bug to work around.
Money is in the minor unit
Amounts returned for checkout are in paise for INR. 49900 is ₹499.00. Hand that number to Razorpay unchanged and divide only when displaying.
Check capacity before you show a seat as bookable
GET /api/v1/events/{id} carries capacity and current counts. Registration can still lose a race and come back 4xx if the last seat went while the user was typing, so surface that error rather than assuming success.
The QR code contains the bare registration id — nothing else
No URL, no JSON, no signature. The ticket QR encodes the registration id as a plain string, e.g. "pNd1m0TRqN8Hx5a1QopI". You therefore do NOT need anything from Vihaya to draw a ticket: encode registrationId with any QR library and it scans in the Vihaya check-in app identically to one we sent. There is NO endpoint that returns a QR image — do not go looking for one, and do not tell a user to wait for one. Get the id from `registrationId` on the register call or the registration.confirmed webhook, or `id` on each row of GET /events/{id}/registrations. Team bookings issue one ticket per member, each with its own id; the webhook carries only the primary.
Webhooks: one event type, configured in the dashboard, never retried
There is NO /v1/webhooks route — the URL and signing secret are set at /profile/integrations, per organiser account. Exactly ONE event type exists: registration.confirmed. Names like event.created, payment.succeeded, registration.checked_in or booking.confirmed appear in older documentation and DO NOT FIRE. Payloads are signed HMAC-SHA256 (not SHA512) over `${timestamp}.${rawBody}`, with the signature in X-Vihaya-Signature prefixed "sha256=" — strip the prefix before a timing-safe compare, or the buffers are different lengths and it throws. Delivery is best-effort: 5s timeout, no retries, response body never read. A dropped delivery is gone, so reconcile against GET /events/{id}/registrations rather than treating it as a queue.
Ticket emails cannot be templated, but the organiser can send their own
Vihaya's ticket email has fixed branding — there is no template uploader, no per-organiser logo or colour, and the sender is always [email protected]. Do not promise anyone a custom template. What they CAN do is send their own: the registration.confirmed webhook carries the attendee and the registrationId, and that id is the QR payload, so the whole ticket can be issued from their own system on their own domain. The one step they cannot self-serve is switching OUR email off — sendTicketEmails is fixed in Firestore rules so an organiser cannot silence their own attendees' tickets, and support has to flip it. The API accepts sendTicketEmails on event creation but does not write it. Until support acts, the attendee receives BOTH emails. Switching it off does not reduce the platform fee — the delivery slice of the rate is currently 0%.
A complete Next.js booking storefront: listing, event page, and a checkout that handles the free path and the paid two-step path. Seven files. The key stays on the server in all of them.
.env.local
Server-only. No NEXT_PUBLIC_ prefix, so Next will not inline it into the client bundle.
# Create a key at https://events.vihaya.app/profile/developer/keys
# A "Checkout only" key is enough for a storefront.
VIHAYA_API_KEY=vh_live_xxxxxxxxxxxxxxxxxxxxHand it over in one line
Rather than copying each file, point the agent at https://docs.vihaya.app/starter-kit.txt — the same seven files as a single document, each with its path.
Almost everything people build is one of these. The endpoint list is at Endpoints.
Sell tickets from your own site (headless storefront)
Run an event you created programmatically
Send your own ticket email, with your own template and branding
Official clients exist for seven languages and wrap the read and register calls. They do not cover event management — that is REST only. See SDKs for what each one actually implements before you tell an agent to use one.
Base URL https://events.vihaya.app. Every request carries x-api-key. If something on this page disagrees with the spec, the spec is right — tell us and we will fix the page.