Integrating with CowTicket
Your website owns the event pages and the brand. CowTicket runs availability, orders, payment and check-in behind this API. This page is the whole integration — four endpoints cover a normal storefront.
Authentication
Create an API key in the app (API Keys page — the raw key is shown once). Your server exchanges it for a short-lived JWT, then sends that as a Bearer token. Do both from your backend — never ship the API key or the JWT to the browser.
POST https://api.cow-ticket.dev/accounts/token
api-key: <your-api-key>
{ "accountId": "<your-account-id>", "type": "customer" }
→ 200 { "token": "<jwt>", "exp": "..." } // valid 1 day — cache and refresh
# All calls below:
authorization: Bearer <jwt>1 · Show what's on sale
Returns ACTIVE ticket types currently inside their selling window, with a live available count, plus the event's refund policy so you can show it before checkout. Prices are in satang (฿1 = 100 satang).
GET /events/ticketConfigs?eventCode=my-event-2026
→ 200 {
"data": [{
"code": "early-bird", "name": "Early Bird",
"group": "Day 1 (Sat 14 Mar)", // section label, or null
"price": 90000, "currency": "thb",
"available": 42, "limitPerOrder": 4,
"startSellingDate": "...", "endSellingDate": "..."
}],
"refundPolicy": { "allowed": true, "termEn": "...", "termTh": "..." }
}data comes back in the organizer's chosen display order — keep it. group is an optional section label organizers use to split ticket options into sessions or days: start a new titled section whenever it changes, and render null-group items flat with no heading. It's display text only — no shared behavior, and one order can mix items from different groups.
2 · Create an order
Reserves the tickets atomically (no overselling) and returns a Stripe Checkout URL — redirect the buyer there. Unpaid orders expire after 1 hour and release their seats. Free orders skip payment: status comes back PAID with checkoutUrl: null. metadata is yours — attendee form answers put here appear in the organizer's exports.
POST /orders
{
"accountId": "<your-account-id>",
"eventCode": "my-event-2026",
"email": "buyer@example.com",
"items": [{ "code": "early-bird", "quantity": 2 }],
"metadata": { "phone": "081-234-5678" },
"successUrl": "https://yourevent.com/thanks?orderId={orderId}",
"cancelUrl": "https://yourevent.com/tickets"
}
→ 200 {
"orderId": "...", "orderNo": "...",
"status": "PENDING", "expiresAt": "...",
"checkoutUrl": "https://checkout.stripe.com/..."
}3 · Confirm payment
When the buyer lands on your successUrl, poll the order until it's PAID (webhooks usually beat the redirect, but don't assume). Statuses: PENDING → PAID → REFUNDED, or EXPIRED/CANCELED.
GET /orders/detail?orderId=...
→ 200 { "status": "PAID", "tickets": [...], "refundPolicy": {...}, ... }4 · Show the tickets
The order's confirmation email links back to your successUrl — your page renders the tickets and their QR codes (encode the ticket id). At the event, staff scan with the organizer's scanner setup; a ticket redeems once, and voided or refunded tickets are rejected automatically.
GET /tickets/getIdsByOrder?orderId=... → ticket ids
GET /tickets/detail?ticketId=... → holder, status, type
# Scanner side (organizer JWT):
POST /tickets/redeem { "ticketId": "...", "gate": "main" }Good to know
- Currency is Thai Baht; every price and amount is satang.
- Payments are direct charges on the organizer's connected Stripe account — connect Stripe in the app before selling paid tickets.
- Ticket issuance is idempotent — a webhook retry can't double-issue tickets.
- The buyer gets one email from us: the order confirmation with a receipt and a link to your
successUrl. Everything else the buyer sees is your site. - Stuck or missing something? support@cow-ticket.dev — we read everything.