# Kommunity > Kommunity is a community platform where people discover communities and social events near them — and where organizers run those communities and events in one place. Attendees can explore events in their city, follow communities they care about, RSVP, and get tickets. Organizers get membership, events and RSVPs, ticketing, discussions, monetization, and communications. This file points AI agents and crawlers to the most useful resources on kommunity.com. ## Discover events & communities - [Explore events](https://kommunity.com/explore/events): Browse upcoming social events. Discover things to do and follow what's happening — including events in your city. - [Explore communities](https://kommunity.com/explore/kommunities): Find and join communities around your interests. - [Browse categories](https://kommunity.com/categories): Explore communities and events by category (tech, arts, sports, business, and more). - [Search](https://kommunity.com/search): Search across communities, events, and people. City and interest pages narrow discovery further: `https://kommunity.com/city/{city-slug}` for events in a specific city, and `https://kommunity.com/interests/{interest-slug}` for a specific interest. ## Developers - [API catalog](https://kommunity.com/.well-known/api-catalog): RFC 9727 API catalog (`application/linkset+json`) for automated discovery — links each API to its OpenAPI spec, documentation, and health endpoint. - [Agent authentication (auth.md)](https://kommunity.com/auth.md): How agents authenticate with the Developer API — OAuth 2.0 client credentials, how credentials are provisioned, and how to exchange/use/revoke tokens. OAuth discovery: [authorization server](https://kommunity.com/.well-known/oauth-authorization-server) (RFC 8414), [protected resource](https://kommunity.com/.well-known/oauth-protected-resource) (RFC 9728). - [Public API — reference](https://kommunity.com/docs/public-api.md) ([OpenAPI](https://kommunity.com/docs/api/public-api.openapi.json)): Read-only public endpoints that power discovery — explore events and communities, and read a single community or event. No auth required. - [Developer API — reference](https://kommunity.com/docs/developer-api.md) ([OpenAPI](https://kommunity.com/docs/api/developer-api.openapi.json)): Community-scoped API for organizers: read community, event, ticket and attendee data, issue/refund/cancel tickets, manage sponsors, speakers and schedules, and sell tickets from your own site through a hosted checkout. OAuth client-credentials auth. - [Webhooks — developer reference](https://kommunity.com/docs/webhooks.md): Machine-readable spec for the self-serve webhooks feature — delivery envelope, all event types, payload schemas, example payloads, signature verification, and delivery semantics. ## Product - [Community platform](https://kommunity.com/community-platform): Overview of running a community on Kommunity. - [Events & RSVP](https://kommunity.com/rsvp): Create events and manage RSVPs. - [Tickets](https://kommunity.com/tickets): Sell and manage event tickets. - [Monetization](https://kommunity.com/monetization): Ways to monetize a community. - [Paid subscriptions](https://kommunity.com/paid-subscription): Membership tiers and paid subscriptions. - [Payouts](https://kommunity.com/payout): How organizers get paid. - [Discussions](https://kommunity.com/discussions): Community discussion feed. - [Communication](https://kommunity.com/communication): Reach members via email and notifications. - [Marketing](https://kommunity.com/marketing): Grow and promote a community. - [Forms](https://kommunity.com/forms): Custom registration and forms. - [Pricing](https://kommunity.com/pricing): Plans and pricing. ## Blog - [Kommunity Blog](https://kommunity.com/blog): Product updates, guides, and community & events stories. - [Introducing Webhooks](https://kommunity.com/blog/introducing-webhooks-send-your-kommunity-events-anywhere): Product update announcing self-serve webhooks. ## Get started - [Sign up](https://kommunity.com/signup): Create a Kommunity account. - [Log in](https://kommunity.com/login): Sign in to Kommunity. --- # Full content The sections below inline the full text of the markdown documents referenced above, per the llms.txt convention for llms-full.txt. --- # Kommunity Developer API — Reference > Community-scoped API for organizers and the developers they work with. It reads community and event data (events, ticket types, attendees, answers, speakers, sponsors, agenda, photos, members), writes ticket, sponsor, speaker and schedule changes, and lets a community sell Kommunity tickets from its own website through a hosted checkout. Access is via an OAuth 2.0 client-credentials token issued from the community's Developer panel; the token is scoped to one community, so paths never carry a community id. - **OpenAPI spec:** https://kommunity.com/docs/api/developer-api.openapi.json - **Webhooks:** https://kommunity.com/docs/webhooks.md - **Setup UI:** Organizer panel → your community → **Developer → Clients** - **Health:** `https://api.kommunity.com/api/health-check` ## Base URL ``` https://api.kommunity.com ``` - API version: `dev/v1` (part of the path). - Always send `Accept: application/json`. ## Authentication Every request must carry a Bearer access token in the Authorization header. Tokens are issued to an API client that belongs to your community — the API is scoped to that community by the token itself, so paths never include a community identifier. You exchange the client credentials for an access token (valid for 24 hours), then send that token with each call. - Scheme: OAuth 2.0 Bearer token - Auth header on every request: ``` Authorization: Bearer ``` Steps: 1. Create an API client for your community in the Clients tab. The client_secret is shown once — store it safely. 2. Exchange the client_id and client_secret for an access token via a POST to the token endpoint. 3. Send the returned access_token as a Bearer token on every API request. 4. When the token expires (24h), request a new one the same way. Exchange client credentials for an access token: ```bash curl -X POST "https://api.kommunity.com/api/oauth/token" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "client_credentials", "client_id": "", "client_secret": "" }' ``` The response contains an `access_token` (and its `expires_in`). Send that token as the Bearer token shown above. Request a new one when it expires. > API clients are created by a community organizer in the Developer → Clients tab. The client_secret is shown once, at creation. ## Conventions - All responses are JSON. Single records are returned under a top-level `data` object. - List endpoints return an array under `data`; paginated ones add `meta` and `links`. - Pass `page` to move through paginated results and `limit` to set the page size (1 to 50, default 20; the products list defaults to 10). - The API is scoped to the community the client belongs to — paths never include a community identifier. - `{event_id}` is the immutable event UUID (from the events list or the webhook envelope). The event slug is also accepted, but slugs can change — prefer the UUID. - Write endpoints answer with the affected record under `data`, in the same shape the read endpoints use. Most take a JSON body — send `Content-Type: application/json`. The sponsor create/edit endpoints carry a logo file and take `multipart/form-data` instead; each endpoint below states which. - A rejected write answers `422` with the reason under `errors`. This covers plain validation as well as business rules ("Only active tickets can be refunded.", "3 ticket(s) left for Early Bird") — read the message, it is the same one the organizer console shows. - An id that does not belong to your community answers `404`, never `403`: the API will not confirm that somebody else's record exists. - A missing, invalid, expired or revoked token answers `401`. Request a new token and retry once; if it still fails, the client may have been deleted. - Amounts are not all in one unit. Checkout, orders, webhooks and `price_minor` use the minor unit (kuruş/cents). The deprecated ticket `price`, fixed-amount discount codes and product prices use the major unit. Each field below says which. - Opening a checkout takes an `Idempotency-Key` header; send one so a retry cannot open a second order. The other writes are not idempotent and take no key. A call that times out may still have taken effect, so re-read the ticket before retrying. ## Rate limits - API requests: **300 per minute per client**. Over budget the API answers `429` with a `Retry-After` header. - Token requests (`/api/oauth/token`, `client_credentials`): **30 per minute per client and IP**. Cache the access token until it expires instead of requesting a new one per call. - Limits are counted per API client, so one busy integration cannot exhaust another's budget. ## Selling tickets on your own site You can run the whole purchase on your own site and use Kommunity only as the ticketing and payment backend. The buyer never sees kommunity.com. The round trip, once: 1. **Show what is on sale.** `GET /api/dev/v1/events/{event_id}/tickets` gives you the ticket types, their prices (`price_minor`, in kuruş/cents like every other amount), currency, sale window and remaining quota. Render your own basket from that, offering only types whose `status` is `1` (active): passive and paused types are in the list too. `GET /api/dev/v1/events/{event_id}` gives you `purchase_terms`: show its sentence and links next to your consent checkbox. 2. **Open a checkout.** `POST /api/dev/v1/events/{event_id}/checkout` with the buyer, one entry per ticket, your `return_url`, and an `Idempotency-Key` header. You get back an `order_id`, a `payment_url` and an `expires_at` ten minutes out. The seats are held for that window and for no longer. If the event issues invoices, the `billing_*` fields are required too. 3. **Send the buyer to `payment_url`.** That page belongs to the payment provider (Stripe, Mony, Iyzico or Sipay, whichever the event runs on). Card details never reach your servers, which is what keeps you out of PCI scope. 4. **Kommunity confirms the payment and issues the tickets.** This happens on our side and does not wait for the buyer to come back or for you to read the order. We confirm when the provider hands the buyer back, when the provider notifies us, and otherwise by asking the provider ourselves: every minute at first, then less often, for up to 24 hours. A buyer who pays and closes the tab still gets their tickets. The attendee e-mails, the wallet passes, and the `event.ticket.created` and `order.paid` webhooks all fire here. 5. **The buyer lands on your `return_url`**, with `?kommunity_order_id=&status=` appended. 6. **Read the order back before you trust anything.** `GET /api/dev/v1/orders/{order_id}`, and gate your "thank you" page on `tickets_issued`. The `status` in the redirect came through the buyer's browser; it is a rendering hint, not proof of payment. ### Before your first call Register the site you sell from: Developer → Clients → your client → **Allowed return origins**, e.g. `https://tickets.example.com`. A checkout whose `return_url` is not on one of those origins is refused. Without this the endpoint would let anyone holding your token bounce buyers off a Kommunity payment page to a site of their choosing. `http://` is accepted only on `localhost`, for development. ### Things that will bite you if you skip them - **The 10-minute hold is real.** `expires_at` is when the seats go back on sale. If the buyer wanders off, open a new checkout rather than reusing the old `order_id`: its seats may already be sold, and paying it late can end in `paid_no_tickets`. - **`status=pending` is not a failure.** It means the outcome is not known yet: we could not reach the provider on the way back, the tickets are still being issued, or the order was being checked at that very moment. Poll `GET /orders/{order_id}` for a short while instead of telling the buyer the payment failed. - **`status=paid_no_tickets` means the buyer was charged.** The payment went through but the tickets could not be issued (for example the seats sold out after the hold lapsed). Do not show it as a failed payment; tell the buyer we are on it and contact Kommunity support, who will issue or refund. - **Do not fulfil on the redirect.** Anyone can open your `return_url` with any order id in the query string. `tickets_issued` from the API is the only thing that means the sale happened. - **A free basket skips steps 3–5 entirely.** `payment_required` comes back `false`, `payment_url` is `null`, and the tickets are already in the response. Handle that branch or free registrations will hang waiting for a payment page. - **Subscribe to `order.paid`** to learn about a sale even when the buyer never comes back to your site. It fires once per paid order, after the tickets were issued, and carries the same fields as the `data` of `GET /orders/{order_id}`, at the top level next to the webhook envelope keys (there is no `data` wrapper). - **Subscribe to `event.ticket.created`** if your site keeps its own record of who is coming. It fires per ticket, whether the ticket was sold through your site, the Kommunity page, or issued by an organizer by hand. - **All amounts are in the minor unit.** `price_minor` on the tickets list, and every amount in the checkout and order responses, is in kuruş/cents: `25000` = 250.00. The tickets list still carries the old major-unit `price` for existing integrations; do not mix the two. - **Refunds and cancellations** go through the ticket operations, not through your payment provider directly. Refunding in the provider's dashboard leaves the ticket valid and the attendee still on the list. ## Webhooks Organizers register webhook endpoints in Developer → Webhooks, for the whole community or for a single event. Each delivery is a JSON `POST`. The body starts with a common envelope (`event_type`, `event_id`, `event_slug`, `community_slug`, `community_name`) followed by the fields of that event type, all at the top level. - **Verify the signature.** Every request carries `Kommunity-Signature: t=,v1=`. Compute HMAC-SHA256 over `.` with the signing secret shown in Developer → Webhooks, compare it with `v1` in constant time, and reject the request if `t` is more than 5 minutes old. Community-wide endpoints use the community secret; an endpoint registered for one event uses that event's own secret. Rotating a secret invalidates the old one at once. - **Answer with a 2xx quickly.** A failed delivery is retried up to 7 more times over about 8 hours, so the same delivery can arrive more than once: deduplicate on `order_id` / `order_ticket_id`. - The older `Kommunity-Signature-256` header (a fixed value) is still sent for existing integrations. Use `Kommunity-Signature` for anything new. ### `event.ticket.created` — Ticket purchased | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | Unique id of the sold ticket | | `status` | integer | Ticket status: 1 active, 2 paused, 3 refunded, 4 cancelled | | `attendee_name` | string | Attendee full name | | `email` | string | Attendee e-mail address | | `phone_number` | string | null | Attendee phone number | | `pnr` | string | Booking reference (PNR) printed on the ticket | | `ticket_name` | string | null | Name of the ticket type that was bought | | `paid_price` | integer | Amount actually paid after discounts, in the currency's minor unit (kuruş/cents): 2500 = 25.00 | | `discount_code` | string | null | Discount code used at checkout, if any | | `attendee_company_name` | string | null | Custom registration field: company | | `attendee_title` | string | null | Custom registration field: job title | ### `event.ticket.updated` — Ticket updated | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | Unique id of the sold ticket | | `status` | integer | Ticket status: 1 active, 2 paused, 3 refunded, 4 cancelled | | `attendee_name` | string | Attendee full name | | `email` | string | Attendee e-mail address | | `phone_number` | string | null | Attendee phone number | | `pnr` | string | Booking reference (PNR) printed on the ticket | | `ticket_name` | string | null | Name of the ticket type that was bought | | `paid_price` | integer | Amount actually paid after discounts, in the currency's minor unit (kuruş/cents): 2500 = 25.00 | | `discount_code` | string | null | Discount code used at checkout, if any | | `attendee_company_name` | string | null | Custom registration field: company | | `attendee_title` | string | null | Custom registration field: job title | ### `event.ticket.canceled` — Ticket canceled or refunded | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | Unique id of the sold ticket | | `status` | integer | Ticket status: 1 active, 2 paused, 3 refunded, 4 cancelled | | `attendee_name` | string | Attendee full name | | `email` | string | Attendee e-mail address | | `phone_number` | string | null | Attendee phone number | | `pnr` | string | Booking reference (PNR) printed on the ticket | | `ticket_name` | string | null | Name of the ticket type that was bought | | `paid_price` | integer | Amount actually paid after discounts, in the currency's minor unit (kuruş/cents): 2500 = 25.00 | | `discount_code` | string | null | Discount code used at checkout, if any | | `attendee_company_name` | string | null | Custom registration field: company | | `attendee_title` | string | null | Custom registration field: job title | ### `order.paid` — Order paid Sent once per paid order, after the payment cleared and every ticket on it was issued. It carries the same fields as the data of GET /api/dev/v1/orders/{order}, at the top level next to the envelope keys, so one parser covers both once you unwrap data. Free orders do not send it: their tickets come back in the checkout response, and each ticket still sends event.ticket.created. | Field | Type | Description | |---|---|---| | `order_id` | string | Order id, the same id the checkout returned | | `status` | string | Order status; 'tickets_created' on this event | | `paid` | boolean | Always true on this event | | `tickets_issued` | boolean | Always true on this event | | `price` | integer | Amount charged, in the currency's minor unit (kuruş/cents): 5000 = 50.00 | | `total_price` | integer | Basket total before discounts, in minor units | | `currency` | string | ISO 4217 currency code, e.g. TRY | | `payment_provider` | string | null | stripe, iyzico, mony or sipay | | `buyer` | object | name, email and phone of the buyer | | `card` | object | last_four_digits and association (visa, mastercard, ...) of the card used | | `created_at` | string | When the order was opened, ISO 8601 | | `tickets` | array | Issued tickets, same fields as event.ticket.created plus currency, checked_in_at and a meta object for the custom registration fields | ### `event.created` — Event created | Field | Type | Description | |---|---|---| | `id` | string | Event id | | `name` | string | Event name | | `slug` | string | Event slug | | `detail` | string | Event description (HTML) including the RSVP/ticket link | | `detail_without_html` | string | Plain-text version of the description | | `url` | string | Public event page URL | | `start_date` | string | Start date, ISO 8601 | | `end_date` | string | End date, ISO 8601 | ### `event.rsvp.join` — Someone RSVP'd to an event | Field | Type | Description | |---|---|---| | `id` | string | User id | | `name` | string | User full name | | `username` | string | Kommunity username | | `avatar` | string | null | Avatar image URL | ### `event.rsvp.join_the_waiting_list` — Someone joined an event waiting list | Field | Type | Description | |---|---|---| | `id` | string | User id | | `name` | string | User full name | | `username` | string | Kommunity username | | `avatar` | string | null | Avatar image URL | ### `event.left` — Someone left an event | Field | Type | Description | |---|---|---| | `id` | string | User id | | `name` | string | User full name | | `username` | string | Kommunity username | | `avatar` | string | null | Avatar image URL | | `reason` | string | Why the registration went away: 'self' (the attendee or an organizer removed it), 'subscription_canceled' (a subscriber-only event whose subscription ended), 'subscription_payment_failed' (the subscription ended because the payment could not be collected) | ### `community.joined` — Member joined the community | Field | Type | Description | |---|---|---| | `id` | string | User id | | `name` | string | User full name | | `username` | string | Kommunity username | | `avatar` | string | null | Avatar image URL | ### `community.left` — Member left the community | Field | Type | Description | |---|---|---| | `id` | string | User id | | `name` | string | User full name | | `username` | string | Kommunity username | | `avatar` | string | null | Avatar image URL | ### `wall.post.created` — New wall post | Field | Type | Description | |---|---|---| | `id` | string | Post id | | `content` | null | Always null — the post body is not included in the webhook | | `wall` | string | Id of the community the post belongs to | | `url` | string | null | Public URL of the post | | `author_id` | string | null | Author user id | | `author_username` | string | null | Author username | | `author_name` | string | null | Author full name | ## Endpoints ### Event Read data scoped to a single event. #### Get an event ``` GET /api/dev/v1/events/{event_id} ``` Return the detail of a single event — title, description, schedule, location/online settings, capacity and status. The event must belong to the API client's community. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | string | Event UUID. | | `name` | string | Event title. | | `slug` | string | Event slug. | | `detail` | string | Event description / body. | | `start_date` | datetime | Start date & time (ISO 8601). | | `end_date` | datetime | End date & time (ISO 8601). | | `timezone` | string | Event timezone. | | `status` | integer | Event status code. | | `is_online` | boolean | Whether the event is online. | | `is_canceled` | boolean | Whether the event is canceled. | | `attendance_type` | integer | How people attend (RSVP or ticket). | | `rsvp_status` | boolean | Whether RSVPs are open. | | `rsvp_limit` | integer | RSVP capacity (null = unlimited). | | `url` | string | Public event page URL. | | `attendees_count` | integer | For a ticketed event, the number of issued tickets that are not cancelled (refunded ones are included). For an RSVP event, the people who said they are going. | | `venue` | object | Location — name, address, lat, lng (null for online events). | | `purchase_terms` | object | What `accepted: 1` on checkout stands for. `consent_text` is the sentence to show next to your checkbox and `documents` lists the Sales Contract, Terms of Use and Privacy Policy as `{ key, title, url }`. Show them to the buyer before they accept. The text and titles follow your `Accept-Language` header (en, tr, uk, fr). | #### List ticket types ``` GET /api/dev/v1/events/{event_id}/tickets ``` List the ticket types for an event — name, price, quota, sold/remaining counts and sale window, oldest first. Every type is returned, including passive and paused ones that the public event page hides; check `status` before offering one to buyers. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/tickets" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `id` | string | Ticket type UUID. | | `name` | string | Ticket name. | | `description` | string | Ticket description. | | `price_minor` | integer | Ticket price in the currency's minor unit (kuruş/cents): `25000` = 250.00 TRY. The same unit as every amount in the checkout and order responses. Use this one. | | `price` | number | Deprecated. The same price in the major unit (`250`), kept for existing integrations. Use `price_minor`. | | `currency` | string | Currency code. | | `quota` | integer | Total tickets available (null = unlimited). | | `sold_count` | integer | Tickets sold so far. | | `remaining` | integer | Tickets still available (null = unlimited). | | `status` | integer | 0 passive, 1 active, 2 paused. Only active types are on sale on the event page; passive and paused ones can still be issued with `ignore_status`. | | `type` | integer | Ticket type code (e.g. paid / free). | | `sale_start_date` | string | When sales open, as `YYYY-MM-DD HH:MM:SS` in the event's timezone, with no offset. (Unlike the ISO 8601 event dates.) | | `sale_end_date` | string | When sales close, in the same format. | #### List attendees ``` GET /api/dev/v1/events/{event_id}/attendees ``` List the people registered for this event. Each item is tagged with `attendance_type` and carries a nested `ticket` or `rsvp` object with the type-specific fields. An event is either ticketed or RSVP, so every item in a response is the same type. Email and paid/ticket details are only on `ticket`; RSVP attendees never expose an email. Ticket rows are one per order ticket, newest first, and include refunded and cancelled tickets: filter on `ticket.status` if you only want valid ones. RSVP rows list only the people who are going. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `page` | query | no | Page number for pagination (starts at 1). | | `limit` | query | no | Items per page: default 20, at most 50. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/attendees" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — Paginated: the array is under `data`; `meta` (current_page, last_page, per_page, total) and `links` (first, last, prev, next) describe the pages. | Field | Type | Description | |---|---|---| | `attendance_type` | string | "ticket" or "rsvp" - tells you which nested object is present. | | `ticket` | object | Present when attendance_type is "ticket". Fields: order_ticket_id, status (0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled), attendee_name, email, phone_number, pnr, ticket_name, paid_price, currency, discount_code, checked_in_at, and meta (attendee custom fields: attendee_company_name, attendee_title). | | `rsvp` | object | Present when attendance_type is "rsvp" (no email). Fields: id, name, username, avatar, rsvp_status, guests. | | `created_at` | datetime | When the person registered (ISO 8601). | #### List registration answers ``` GET /api/dev/v1/events/{event_id}/answers ``` List attendees' answers to the event registration questions. The question definitions are returned once, in a top-level `questions` array (null when the event has no registration form). Oldest first. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `page` | query | no | Page number for pagination (starts at 1). | | `limit` | query | no | Items per page: default 20, at most 50. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/answers" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — Paginated: the array is under `data`; `meta` (current_page, last_page, per_page, total) and `links` (first, last, prev, next) describe the pages. | Field | Type | Description | |---|---|---| | `id` | string | Answer record UUID. | | `user` | object | The attendee who answered (id, name, username). Null if the account no longer exists. | | `answers` | array | The submitted answers ({question_id, answer}). | | `has_approved` | boolean | Whether the registration is approved. | | `created_at` | datetime | When the answers were submitted. | #### List discount codes ``` GET /api/dev/v1/events/{event_id}/discount-codes ``` List the discount codes configured for this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/discount-codes" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `id` | string | Discount code UUID. | | `name` | string | Human-readable label. | | `code` | string | The code attendees enter. | | `discount_type` | integer | 0 fixed amount, 1 percentage. | | `amount` | number | For a fixed amount, the discount in the major unit (`50` = 50.00, unlike the minor-unit amounts in checkout). For a percentage, the percent. | | `code_usage` | string | Whether the code can be used once or many times. | | `start_date` | datetime | When the code becomes valid. | | `end_date` | datetime | When the code stops being valid. | | `use_limit` | integer | Maximum number of uses (null = unlimited). | | `used_count` | integer | How many times it has been used. | #### List speakers ``` GET /api/dev/v1/events/{event_id}/speakers ``` List the speakers for this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/speakers" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Speaker id. | | `name` | string | Speaker name. | | `slug` | string | Speaker slug. | | `title` | string | Job title / role. | | `company_name` | string | Company / organisation. | | `bio` | string | Short biography. | | `avatar` | string | Avatar image URL. | | `social_networks` | object | Social profile links, keyed by network (null when none). | #### List sponsors ``` GET /api/dev/v1/events/{event_id}/sponsors ``` List the sponsors for this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/sponsors" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Sponsor id — the id the update, delete and sort endpoints take. | | `name` | string | Sponsor name. | | `link` | string | Sponsor's website URL. | | `detail` | string | Description / blurb (null when not given). | | `logo` | string | Logo image URL. | | `category` | string | Sponsor tier / category, free text — e.g. "Gold" (null when not given). | | `button_text` | string | Label of the call-to-action button on the sponsor card. | | `button_link` | string | Where that button goes. | | `youtube_video_id` | string | YouTube video shown on the sponsor card (null when not given). | | `resources` | array | Extra links on the sponsor card, each `{ text, link }`. Empty when none. | | `sort` | integer | Position in the sponsor list, 1-based. | #### Get agenda (tracks & sessions) ``` GET /api/dev/v1/events/{event_id}/agenda ``` The event agenda: a list of tracks in display order, each with its sessions (times, descriptions and speakers are nested inside). **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/agenda" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Track id. | | `name` | string | Track name. | | `day` | string | The agenda day, as a midnight UTC timestamp (`2026-10-01T00:00:00.000000Z`). Only the date part is meaningful. Null when not set. | | `sort` | integer | Position of the track. | | `sessions` | array | Sessions in the track — id, title, description, start_at, end_at, is_keynote, and speakers as `{ id, name }` (fetch the speaker list for the rest). | #### List photos ``` GET /api/dev/v1/events/{event_id}/photos ``` List the photos uploaded to this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `page` | query | no | Page number for pagination (starts at 1). | | `limit` | query | no | Items per page: default 20, at most 50. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/photos" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — Paginated: the array is under `data`; `meta` (current_page, last_page, per_page, total) and `links` (first, last, prev, next) describe the pages. | Field | Type | Description | |---|---|---| | `id` | integer | Photo id. | | `url` | string | Full photo URL. | | `mime_type` | string | MIME type. | | `size` | integer | File size in bytes. | ### Community Read community-level data. #### Get your community ``` GET /api/dev/v1/community ``` Return the API client's community profile — name, slug, description, and member / event counts. The community is resolved from the token. Example: ```bash curl "https://api.kommunity.com/api/dev/v1/community" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | string | Community UUID. | | `name` | string | Community name. | | `slug` | string | Community slug. | | `short_description` | string | Short description. | | `description` | string | Community description. | | `members_count` | integer | Number of members. | | `upcoming_events_count` | integer | Number of events that have not ended yet (running ones included). | | `past_events_count` | integer | Number of past events. | | `url` | string | Public community page URL. | | `created_at` | datetime | When the community was created. | #### List community events ``` GET /api/dev/v1/community/events ``` List your community's events. By default returns events that have not ended yet (running ones included), soonest first. Pass `status=past` for ended events, most recent first. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `status` | query | no | Set to `past` to return past events instead of upcoming. Any other value is treated as upcoming. | | `page` | query | no | Page number for pagination (starts at 1). | | `limit` | query | no | Items per page: default 20, at most 50. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/community/events" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — Paginated: the array is under `data`; `meta` (current_page, last_page, per_page, total) and `links` (first, last, prev, next) describe the pages. | Field | Type | Description | |---|---|---| | `id` | string | Event UUID. | | `name` | string | Event title. | | `slug` | string | Event slug (use it to fetch the detail). | | `start_date` | datetime | Start date & time (ISO 8601). | | `end_date` | datetime | End date & time (ISO 8601). | | `timezone` | string | Event timezone. | | `status` | integer | Event status code. | | `is_online` | boolean | Whether the event is online. | | `is_canceled` | boolean | Whether the event is canceled. | | `attendance_type` | integer | How people attend (RSVP or ticket). | | `url` | string | Public event page URL. | | `venue` | object | Location — name, address (null for online events). | #### List community members ``` GET /api/dev/v1/community/members ``` List your community's members, newest first. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `page` | query | no | Page number for pagination (starts at 1). | | `limit` | query | no | Items per page: default 20, at most 50. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/community/members" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — Paginated: the array is under `data`; `meta` (current_page, last_page, per_page, total) and `links` (first, last, prev, next) describe the pages. | Field | Type | Description | |---|---|---| | `id` | string | User UUID. | | `name` | string | Member name. | | `username` | string | Member username. | | `avatar` | string | Avatar image URL. | | `role` | integer | Member's role: 0 member, 1 organizer, 2 co-organizer, 3 waiting for approval, 4 staff. | | `joined_at` | datetime | When they joined. | #### List community products ``` GET /api/dev/v1/community/products ``` List your community's membership products: those assigned to a subscription tier, ordered by tier, inactive ones included (check `status`). **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `page` | query | no | Page number for pagination (starts at 1). | | `limit` | query | no | Items per page: default 10, at most 50. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/community/products" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — Paginated: the array is under `data`; `meta` (current_page, last_page, per_page, total) and `links` (first, last, prev, next) describe the pages. | Field | Type | Description | |---|---|---| | `id` | string | Product UUID. | | `name` | string | Product name. | | `description` | string | Product description. | | `tier` | integer | Subscription tier number of this product. | | `status` | boolean | Whether the product is active. | | `advantages` | string | What membership gives, as free text entered on the product (null when empty). | | `ends_at` | datetime | When the product stops being offered (null for no end). | | `prices` | array | Available prices. Each has `amount` as a formatted major-unit string (`"250.00"`, `"1,250.00"` with a thousands separator; unlike checkout amounts, strip the commas before parsing), `currency`, `status`, `type` and `recurring`. | ### Ticket operations Issue tickets and manage issued ones. These endpoints write. They run through the same services the organizer console uses, so quota, sales dates, attendee e-mails, wallet passes and `event.ticket.*` webhooks all behave exactly as they do in the console. #### Issue tickets ``` POST /api/dev/v1/events/{event_id}/tickets/{ticket_id}/issue ``` Issue one ticket per attendee for the given ticket type, as the console's "send ticket" flow does. Each attendee is e-mailed their ticket and an `event.ticket.created` webhook fires per ticket. Tickets issued through the API always carry a zero paid price, so they can later be cancelled but never refunded. The request is rejected with 422 when the ticket type is passive or paused (under `errors.ticket_status`, unless `ignore_status` is set), today falls outside its sales dates, the event has ended, the remaining quota is smaller than the number of attendees (unless `ignore_quota` is set), or an attendee already holds a free ticket of this type. Issue calls for the same ticket type run one at a time; a call that arrives while another is running gets a 422 and can be retried. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `ticket_id` | path | yes | The ticket type's UUID (from the event's tickets list). | **Request body** (`application/json`) — The people to issue tickets to — one ticket each. Between 1 and 100 per request. | Field | Type | Required | Description | |---|---|---|---| | `attendees` | array | yes | One entry per ticket to issue (1–100). | | `attendees[].name` | string | yes | Attendee full name (max 255 characters). | | `attendees[].email` | string | yes | Attendee e-mail address — the ticket is sent here. | | `attendees[].phone` | string | no | Attendee phone number (max 255 characters). | | `attendees[].attendee_company_name` | string | no | Custom attendee field: company name. | | `attendees[].attendee_title` | string | no | Custom attendee field: job title. | | `ignore_quota` | boolean | no | Issue even when the remaining quota is smaller than the attendee count — overbook the ticket type. Every other check (sale window, event not ended, one free ticket per address) still applies, and a passive or paused type also needs `ignore_status`. Also accepted inside an attendee entry, where it applies to the whole request. Defaults to false. | | `ignore_status` | boolean | no | Issue tickets for a passive or paused ticket type, one kept off the public event page on purpose (a VIP or guest-list ticket, say). Without it such a type is rejected with 422 under `errors.ticket_status`. Quota, sale window, event end and the one-free-ticket-per-address rule still apply. Top level only. Defaults to false. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/tickets/{ticket_id}/issue" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "attendees": [ { "name": "Ada Lovelace", "email": "ada@example.com", "phone": "+905551112233", "attendee_company_name": "Analytical Engines", "attendee_title": "Engineer" } ] }' ``` **Response** — 201 · A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | #### Correct attendee details ``` PATCH /api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id} ``` Correct the attendee details on an issued ticket. Despite the PATCH verb this is a full replacement, matching the organizer console: a field you leave out is cleared, not kept — send `phone`, `attendee_company_name` and `attendee_title` again even when they are not changing. An `event.ticket.updated` webhook fires. Changing `email` is treated as handing the ticket to a different person, not as a typo fix: the ticket is given a new `pnr`, so the QR code already in the previous holder's hands stops working, any wallet pass they installed is voided, the ticket moves to the new address's account, and a ticket e-mail is sent there. Correcting a misspelled address does the same, so re-send the ticket rather than editing the address if the holder has not changed. Changing only the letter case or spaces of the address is not treated as a hand-over. Everything else — including a name-only correction — leaves the QR alone. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `order_ticket_id` | path | yes | An issued ticket's UUID — the `order_ticket_id` returned when it was issued, listed under the event's attendees, or carried by an `event.ticket.*` webhook. The ticket lookup and check-in endpoints also accept the ticket's `pnr` (the code in its QR code) here. | **Request body** (`application/json`) — The complete attendee details. Omitted optional fields are cleared. | Field | Type | Required | Description | |---|---|---|---| | `attendee_name` | string | yes | Attendee full name (max 255 characters). | | `email` | string | yes | Attendee e-mail address. | | `phone` | string | no | Attendee phone number. Omit to clear it. | | `attendee_company_name` | string | no | Custom attendee field: company name. Omit to clear it. | | `attendee_title` | string | no | Custom attendee field: job title. Omit to clear it. | Example: ```bash curl -X PATCH "https://api.kommunity.com/api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "attendee_name": "Ada Lovelace", "email": "ada@example.com", "phone": "+905551112233", "attendee_company_name": "Analytical Engines", "attendee_title": "Engineer" }' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | #### Get a ticket by its code ``` GET /api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id} ``` Look up one issued ticket — and its attendee — by the code on the ticket. Here `order_ticket_id` may be the ticket's UUID or its `pnr`, the code printed in the ticket's QR code (what a door scanner reads). The ticket must belong to the event in the path; anything else answers 404. Returns the attendee name, e-mail, phone and the extra attendee fields under `meta` (company, title), plus the ticket's check-in state. `entry_check` is non-null when the ticket has an entry time that has not been reached yet — check-in will answer 409 until then. Read-only: it does not check anyone in. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `order_ticket_id` | path | yes | An issued ticket's UUID — the `order_ticket_id` returned when it was issued, listed under the event's attendees, or carried by an `event.ticket.*` webhook. The ticket lookup and check-in endpoints also accept the ticket's `pnr` (the code in its QR code) here. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | | `check_in_count` | integer | Entries recorded for the ticket so far (0 = never checked in). | | `check_in_dates` | array | One `{ index, checked_in_at }` entry per recorded entry, oldest first. | | `today_check_in_count` | integer | Entries recorded on the event's current local day (for multi-day events). | | `entry_check` | object | The door rule the ticket would break if scanned now — `{ reason, message, opens_at }` — or null when it can come in. | #### Check a ticket in ``` POST /api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/check-in ``` Check a ticket in at the door, under the same rules as the Kommunity scanner app. `order_ticket_id` may be the ticket's UUID or its `pnr` (the code in its QR code). Only an active ticket is admitted: a refunded, paused or inactive one answers 422 with the reason under `errors.order_ticket`. A ticket type with an entry time scanned before it answers 409 with `reason: "too_early"`, `entry_check` and `can_override: true`, and nothing is recorded; send `override: true` to admit it anyway (the override is logged). Scanning the same ticket again is not an error — it is recorded as a re-entry, `check_in_count` goes up and `already_checked_in` is `true`, so the caller decides whether to turn a repeat away. Every successful call counts as one entry, so do NOT blindly retry after a timeout: look the ticket up first and compare `check_in_count`, or undo an unwanted extra entry with the DELETE call. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `order_ticket_id` | path | yes | An issued ticket's UUID — the `order_ticket_id` returned when it was issued, listed under the event's attendees, or carried by an `event.ticket.*` webhook. The ticket lookup and check-in endpoints also accept the ticket's `pnr` (the code in its QR code) here. | **Request body** (`application/json`) — Optional. | Field | Type | Required | Description | |---|---|---|---| | `override` | boolean | no | Admit the ticket although its entry time has not been reached. Default false. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/check-in" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | | `check_in_count` | integer | Entries recorded for the ticket so far (0 = never checked in). | | `check_in_dates` | array | One `{ index, checked_in_at }` entry per recorded entry, oldest first. | | `today_check_in_count` | integer | Entries recorded on the event's current local day (for multi-day events). | | `entry_check` | object | The door rule the ticket would break if scanned now — `{ reason, message, opens_at }` — or null when it can come in. | | `already_checked_in` | boolean | `true` when the ticket had been checked in before this call (a re-entry). | #### Undo the latest check-in ``` DELETE /api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/check-in ``` Take back the most recent check-in of a ticket (a mis-scan). `order_ticket_id` may be the ticket's UUID or its `pnr`. Only the latest entry is removed, and only within the undo window the scanner app uses (30 minutes by default); older entries stay. Answers 422 when the ticket has not been checked in, the window has passed, or the ticket is not active. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `order_ticket_id` | path | yes | An issued ticket's UUID — the `order_ticket_id` returned when it was issued, listed under the event's attendees, or carried by an `event.ticket.*` webhook. The ticket lookup and check-in endpoints also accept the ticket's `pnr` (the code in its QR code) here. | Example: ```bash curl -X DELETE "https://api.kommunity.com/api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/check-in" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | | `check_in_count` | integer | Entries recorded for the ticket so far (0 = never checked in). | | `check_in_dates` | array | One `{ index, checked_in_at }` entry per recorded entry, oldest first. | | `today_check_in_count` | integer | Entries recorded on the event's current local day (for multi-day events). | | `entry_check` | object | The door rule the ticket would break if scanned now — `{ reason, message, opens_at }` — or null when it can come in. | #### Refund a ticket ``` POST /api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/refund ``` Refund an issued ticket through the payment provider it was paid with, and mark it refunded. The attendee is notified, their wallet pass is expired so the check-in barcode stops working, and an `event.ticket.canceled` webhook fires. Refunds must be enabled for the event or the community, otherwise the call is rejected with 422 — as it is for a ticket that is not active, an order that is not refundable, a free ticket (including every ticket issued through this API), or a refund the provider declines. Use cancel for free tickets. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `order_ticket_id` | path | yes | An issued ticket's UUID — the `order_ticket_id` returned when it was issued, listed under the event's attendees, or carried by an `event.ticket.*` webhook. The ticket lookup and check-in endpoints also accept the ticket's `pnr` (the code in its QR code) here. | **Request body** (`application/json`) — Why the ticket is being refunded. Stored on the ticket and shown in the console. | Field | Type | Required | Description | |---|---|---|---| | `reason` | string | yes | One of `organizer_cancel`, `buyer_request`, `other`. | | `description` | string | no | Free-text detail kept alongside the reason (max 500 characters). | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/refund" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "reason": "buyer_request", "description": "Requested by e-mail, cannot attend" }' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | #### Cancel a ticket ``` POST /api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/cancel ``` Cancel an issued ticket, with or without giving the money back. A free ticket ends up cancelled (`status` 4); a paid one always ends up refunded (`status` 3). A paid ticket collected through Kommunity can only be cancelled by issuing a refund — `issue_refund: false` is accepted only when the organizer holds the money themselves through a connected payment account and has already refunded it there. Either way the wallet pass is expired and an `event.ticket.canceled` webhook fires. Cancelling a paid ticket requires refunds to be enabled for the event or the community; a free ticket (including every ticket issued through this API) can always be cancelled. A ticket that is not active is rejected with 422. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `order_ticket_id` | path | yes | An issued ticket's UUID — the `order_ticket_id` returned when it was issued, listed under the event's attendees, or carried by an `event.ticket.*` webhook. The ticket lookup and check-in endpoints also accept the ticket's `pnr` (the code in its QR code) here. | **Request body** (`application/json`) — Why the ticket is being cancelled, and whether the buyer gets their money back. | Field | Type | Required | Description | |---|---|---|---| | `reason` | string | yes | One of `organizer_cancel`, `buyer_request`, `other`. | | `issue_refund` | boolean | yes | Whether to refund the payment. Required — never defaulted, because giving the buyer their money back is not a decision to make on your behalf. | | `description` | string | no | Free-text detail kept alongside the reason (max 500 characters). | | `notify` | boolean | no | Whether to e-mail the attendee about the cancellation. Defaults to true. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/order-tickets/{order_ticket_id}/cancel" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "reason": "organizer_cancel", "issue_refund": false, "description": "Session moved to next month", "notify": true }' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | The issued ticket's UUID — the id every other ticket endpoint takes. | | `status` | integer | Ticket status: 0 passive, 1 active, 2 paused, 3 refunded, 4 cancelled. | | `attendee_name` | string | Name printed on the ticket. | | `email` | string | Attendee e-mail — where the ticket was sent. | | `phone_number` | string | Attendee phone number (null when not given). | | `pnr` | string | Human-readable ticket reference, shown on the ticket and at check-in. | | `ticket_name` | string | Name of the ticket type this ticket belongs to. | | `paid_price` | integer | Amount paid, in the currency's minor unit (kuruş/cents). Always 0 for tickets issued through the API. | | `currency` | string | Currency of `paid_price`. | | `discount_code` | string | Discount code used at purchase (null when none). | | `checked_in_at` | datetime | When the attendee was checked in (null if not yet). | | `meta.attendee_company_name` | string | Custom attendee field: company name (null when not given). | | `meta.attendee_title` | string | Custom attendee field: job title (null when not given). | ### Sponsor operations Build and maintain an event's sponsor wall. These endpoints write. They run through the same service the organizer console uses, so the logo lands in the same place and the event page picks the change up immediately instead of serving a cached sponsor list. #### Add a sponsor ``` POST /api/dev/v1/events/{event_id}/sponsors ``` Add a sponsor to the event, appended to the end of the list. The logo is required, so this endpoint takes multipart/form-data rather than JSON — send the image as a file part. Any aspect ratio is accepted, but the console crops to 400x200, so a 2:1 logo is what the sponsor wall is laid out for. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | **Request body** (`multipart/form-data`) — The sponsor, with its logo as a file part. | Field | Type | Required | Description | |---|---|---|---| | `name` | string | yes | Sponsor name (max 255 characters). | | `link` | string | yes | Sponsor's website — a full URL including the scheme. | | `logo` | file | yes | Logo image: jpg, jpeg or png, up to 5 MB. 400x200 is what the layout expects. | | `button_text` | string | yes | Label of the call-to-action button on the sponsor card. | | `button_link` | string | yes | Where that button goes — a full URL. | | `detail` | string | no | Description / blurb shown on the sponsor card. | | `category` | string | no | Sponsor tier, free text — e.g. "Gold". Used to group the sponsor wall. | | `youtube_video_id` | string | no | YouTube video id to embed on the sponsor card. | | `resources` | array | no | Extra links on the card. Send as `resources[0][text]` / `resources[0][link]` form parts. | | `resources[].text` | string | yes | Link label. | | `resources[].link` | string | yes | Link target — a full URL. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/sponsors" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -F "name=Acme Corp" \ -F "link=https://acme.example.com" \ -F "logo=@acme-logo.png" \ -F "button_text=Visit Acme" \ -F "button_link=https://acme.example.com/kommunity" \ -F "category=Gold" ``` **Response** — 201 · A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Sponsor id — the id the update, delete and sort endpoints take. | | `name` | string | Sponsor name. | | `link` | string | Sponsor's website URL. | | `detail` | string | Description / blurb (null when not given). | | `logo` | string | Logo image URL. | | `category` | string | Sponsor tier / category, free text — e.g. "Gold" (null when not given). | | `button_text` | string | Label of the call-to-action button on the sponsor card. | | `button_link` | string | Where that button goes. | | `youtube_video_id` | string | YouTube video shown on the sponsor card (null when not given). | | `resources` | array | Extra links on the sponsor card, each `{ text, link }`. Empty when none. | | `sort` | integer | Position in the sponsor list, 1-based. | #### Edit a sponsor ``` POST /api/dev/v1/events/{event_id}/sponsors/{sponsor_id} ``` Replace a sponsor's details. This is a full replacement, matching the console's edit form: a field you leave out is cleared, not kept — send `detail`, `category`, `youtube_video_id` and `resources` again even when they are not changing. The logo is the one exception: omit it and the stored one is kept. POST rather than PATCH because the body may carry the logo file, and PHP only reads an uploaded file on POST. Send multipart/form-data when replacing the logo, plain JSON when you are not. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `sponsor_id` | path | yes | A sponsor's id, from the event's sponsors list. An integer, not a UUID. | **Request body** (`multipart/form-data`) — The complete sponsor details. Omitted optional fields are cleared; an omitted logo is kept. Plain JSON is accepted too when there is no logo to send. | Field | Type | Required | Description | |---|---|---|---| | `name` | string | yes | Sponsor name (max 255 characters). | | `link` | string | yes | Sponsor's website — a full URL including the scheme. | | `button_text` | string | yes | Label of the call-to-action button on the sponsor card. | | `button_link` | string | yes | Where that button goes — a full URL. | | `logo` | file | no | A replacement logo, as a multipart file part. Omit to keep the current one. | | `detail` | string | no | Description / blurb. Omit to clear it. | | `category` | string | no | Sponsor tier. Omit to clear it. | | `youtube_video_id` | string | no | YouTube video id. Omit to clear it. | | `resources` | array | no | Extra links on the card, each `{ text, link }`. Omit to clear them. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/sponsors/{sponsor_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -F "name=Acme International" \ -F "link=https://acme.example.com" \ -F "button_text=Visit Acme" \ -F "button_link=https://acme.example.com/kommunity" \ -F "category=Platinum" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Sponsor id — the id the update, delete and sort endpoints take. | | `name` | string | Sponsor name. | | `link` | string | Sponsor's website URL. | | `detail` | string | Description / blurb (null when not given). | | `logo` | string | Logo image URL. | | `category` | string | Sponsor tier / category, free text — e.g. "Gold" (null when not given). | | `button_text` | string | Label of the call-to-action button on the sponsor card. | | `button_link` | string | Where that button goes. | | `youtube_video_id` | string | YouTube video shown on the sponsor card (null when not given). | | `resources` | array | Extra links on the sponsor card, each `{ text, link }`. Empty when none. | | `sort` | integer | Position in the sponsor list, 1-based. | #### Remove a sponsor ``` DELETE /api/dev/v1/events/{event_id}/sponsors/{sponsor_id} ``` Remove a sponsor from the event. The remaining sponsors keep their positions — call reorder afterwards if you want them renumbered. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `sponsor_id` | path | yes | A sponsor's id, from the event's sponsors list. An integer, not a UUID. | Example: ```bash curl -X DELETE "https://api.kommunity.com/api/dev/v1/events/{event_id}/sponsors/{sponsor_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — 204 · An empty body. #### Reorder sponsors ``` POST /api/dev/v1/events/{event_id}/sponsors/sort ``` Set the order sponsors appear in on the event page. Send the ids in the order you want them; they are renumbered 1..n. An id that belongs to another event is skipped rather than rejected, and a sponsor you leave out keeps the position it had — so send the full list. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | **Request body** (`application/json`) — The sponsor ids, in the order they should appear. | Field | Type | Required | Description | |---|---|---|---| | `sponsor_ids` | array | yes | Sponsor ids in display order. Renumbered 1..n in the order given. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/sponsors/sort" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "sponsor_ids": [ 42, 17, 8 ] }' ``` **Response** — A JSON object with the list under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Sponsor id — the id the update, delete and sort endpoints take. | | `name` | string | Sponsor name. | | `link` | string | Sponsor's website URL. | | `detail` | string | Description / blurb (null when not given). | | `logo` | string | Logo image URL. | | `category` | string | Sponsor tier / category, free text — e.g. "Gold" (null when not given). | | `button_text` | string | Label of the call-to-action button on the sponsor card. | | `button_link` | string | Where that button goes. | | `youtube_video_id` | string | YouTube video shown on the sponsor card (null when not given). | | `resources` | array | Extra links on the sponsor card, each `{ text, link }`. Empty when none. | | `sort` | integer | Position in the sponsor list, 1-based. | ### Selling tickets on your own site Sell Kommunity tickets from your own website. You collect the buyer and attendee details in your own checkout, open an order here, and send the buyer to the payment page that comes back. Card details never touch your site — the payment page belongs to the provider. After the payment Kommunity issues the tickets, mails them, fires the `event.ticket.created` webhook, and hands the buyer back to your `return_url`. Every ticket sold this way is an ordinary Kommunity ticket: it counts against the quota, shows up in the organizer console, scans at the door, and can be refunded or cancelled through the ticket operations above. #### Open a checkout ``` POST /api/dev/v1/events/{event_id}/checkout ``` Reserve the seats and mint a payment page. The basket is validated with exactly the rules kommunity.com uses, so a sold-out ticket type, a closed sale window, an unknown discount code or a second free ticket for an address that already holds one is rejected with 422 before any money moves. Also refused with 422: a cancelled or subscriber-only event (`errors.event`), an attendee who is not on the event's allow-list (`errors.email`), a ticket type that requires another one missing from the basket, and a payment session the provider refuses (`errors.payment`). On success you get an `order_id` and a `payment_url` — redirect the buyer there. The seats are held for 10 minutes; after that the hold lapses and the buyer has to start again. A basket that costs nothing has no payment page: its tickets are issued on the spot and returned in this same response. `return_url` must be on an origin registered for your API client under Developer → Clients, otherwise the call is rejected. Send an `Idempotency-Key` header (any unique string up to 255 visible ASCII characters, e.g. a UUID per purchase attempt) so a retry after a timeout cannot open a second order: the same key within 24 hours returns the first response unchanged, with an `Idempotent-Replayed: true` header. The same key with a different body is refused with 422, and a retry that arrives while the first call is still running gets 409. A call that was refused does not use up its key. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | **Request body** (`application/json`) — The buyer, and one entry per ticket being bought. Two people on one order means two entries; two tickets for the same person means two entries with the same e-mail. | Field | Type | Required | Description | |---|---|---|---| | `buyer_name` | string | yes | Who is paying. Appears on the order and the receipt. | | `buyer_email` | string | yes | The buyer's e-mail. The order summary goes here, and a Kommunity account is created for this address if none exists. | | `buyer_phone_number` | string | no | Buyer phone in international format (+905551112233). | | `accepted` | integer | yes | Send `1` to record that the buyer accepted the purchase terms: the Sales Contract, Terms of Use and Privacy Policy, and that Kommunity may share their details with the organizer. Get the sentence and the links from `purchase_terms` on the event and show them next to your checkbox. Required: the order is refused without it. | | `tickets` | array | yes | One entry per ticket being bought. | | `tickets[].ticket_id` | string | yes | The ticket type's UUID, from the event's tickets list. | | `tickets[].attendee_name` | string | yes | Name printed on this ticket. | | `tickets[].email` | string | yes | Attendee e-mail — where this ticket is sent. | | `tickets[].phone` | string | no | Attendee phone in international format. | | `tickets[].attendee_company_name` | string | no | Custom attendee field: company name, when the event asks for it. | | `tickets[].attendee_title` | string | no | Custom attendee field: job title, when the event asks for it. | | `discount_code` | string | no | A discount code for this event. Rejected with 422 if unknown or outside its validity window. A code that does not cover the chosen ticket types, or whose usage limit is used up, is accepted but not applied: compare `price` with `total_price` in the response before telling the buyer they got a discount. | | `return_url` | string | yes | Where the buyer is sent once the payment resolves. Must be on an origin registered for this API client. | | `cancel_url` | string | no | Where an abandoned payment goes. Same origin rule. Defaults to `return_url` with a cancelled status. | | `buyer_ip` | string | no | The buyer's own IP, from your site's request. Passed to the provider's fraud checks — without it every order carries your server's IP. | | `billing_contact_name` | string | no | Invoice name (max 100). Required when the event issues invoices; the other `billing_*` fields follow the same rule. | | `billing_country` | string | no | Invoice country as a two-letter ISO code (`TR`). Required when the event issues invoices. | | `billing_city` | string | no | Invoice city (max 100). Required when the event issues invoices. | | `billing_state` | string | no | Invoice state / district (max 100). Required when the event issues invoices. | | `billing_address` | string | no | Invoice street address (max 200). Required when the event issues invoices. | | `billing_zip_code` | string | no | Invoice postal code: letters and digits, max 10. Required when the event issues invoices. | | `billing_tax_number` | string | no | Tax or national id number (max 100). Required when the event issues invoices. | | `billing_tax_office` | string | no | Tax office (max 100). Optional even on invoiced events. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/checkout" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "buyer_name": "Ada Lovelace", "buyer_email": "ada@example.com", "buyer_phone_number": "+905551112233", "accepted": 1, "tickets": [ { "ticket_id": "9f1c2a7e-4d5b-4c3a-9e2f-1b0d8a6c5e41", "attendee_name": "Ada Lovelace", "email": "ada@example.com" } ], "return_url": "https://tickets.example.com/checkout/done", "cancel_url": "https://tickets.example.com/checkout/cancelled", "buyer_ip": "203.0.113.7" }' ``` **Response** — 201 · A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_id` | string | The order's UUID. Store it — it is how you read the sale back and how the buyer is identified when they return. | | `status` | string | `created` while the payment is outstanding, `tickets_created` once the tickets exist (immediately, for a free basket). | | `payment_required` | boolean | False when the basket costs nothing — there is no payment page and the tickets are already in `tickets`. | | `payment_url` | string | The payment provider's hosted page. Redirect the buyer here. Null for a free basket. | | `payment_provider` | string | Which provider the payment page belongs to (`stripe`, `mony`, …), decided by the event's settings. | | `expires_at` | string | When the seat hold ends (10 minutes). The provider's payment page may stay open a little longer, but paying after this point can end in `paid_no_tickets`, so send a late buyer through a new checkout. Null for a free basket. | | `price` | integer | What the buyer pays, in the currency's minor unit (kuruş/cents), after any discount code. | | `total_price` | integer | The basket before discounts, in the same minor unit. | | `currency` | string | Currency of `price` and `total_price`. | | `return_url` | string | The address the buyer will be handed back to, echoed back for confirmation. | | `tickets` | array | The issued tickets — filled only for a free basket, empty while a payment is outstanding. | #### Read an order ``` GET /api/dev/v1/orders/{order_id} ``` The authoritative answer to whether the buyer paid. Call this when the buyer lands back on your `return_url`: the `status` in that redirect travelled through the buyer's browser and proves nothing, this does. Gate fulfilment on `tickets_issued`. Only orders opened with this API client can be read; any other order id answers 404. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `order_id` | path | yes | An order's UUID — the `order_id` returned when the checkout was opened. | Example: ```bash curl "https://api.kommunity.com/api/dev/v1/orders/{order_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `order_id` | string | The order's UUID. | | `status` | string | `created` (not paid, whether still outstanding or refused), `successful` (paid, tickets being issued), `tickets_created` (done), `tickets_not_created` (paid, but the tickets could not be issued: the buyer was charged, contact support), `refunded`. | | `paid` | boolean | Whether the money was taken: true for `successful`, `tickets_created` and `tickets_not_created`; false for `created` and `refunded`. | | `tickets_issued` | boolean | Whether the tickets exist yet. This is the flag to gate fulfilment on. | | `price` | integer | Amount paid, in the currency's minor unit. | | `total_price` | integer | Basket total before discounts, in the same minor unit. | | `currency` | string | Currency of the amounts. | | `payment_provider` | string | Which provider took the payment. | | `event_id` | string | The event the order belongs to. | | `buyer` | object | Who bought: `name`, `email`, `phone`. | | `card` | object | A card summary for reconciliation: `last_four_digits`, `association`. Never full card data. | | `created_at` | string | When the checkout was opened. | | `tickets` | array | The issued tickets, once they exist — each in the ticket shape used everywhere else in this API. | ### Speakers and schedule operations Mirror speakers and an agenda using the community API client. Create speakers first, then dated tracks, then sessions with those speaker IDs. Session times are UTC HH:mm, matching agenda storage; convert source local times using the event timezone. All IDs are scoped to the event. PATCH preserves omitted fields. Changes clear event caches. #### Create a speaker ``` POST /api/dev/v1/events/{event_id}/speakers ``` Create a record for this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | **Request body** (`application/json`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `name` | string | yes | Speaker name, up to 200 characters. | | `title` | string | yes | Title, up to 200 characters. | | `company_name` | string | no | Company name; null clears it. | | `bio` | string | no | Biography; null clears it. | | `social_networks` | object | no | Map of social network names to HTTP(S) URLs. | | `avatar_url` | string | no | HTTP(S) image URL, up to 255 characters. Stored as a source URL without downloading; the source must remain accessible. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/speakers" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "name": "Ada Lovelace", "title": "Engineer", "avatar_url": "https://example.com/ada.png" }' ``` **Response** — 201 · A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `name` | string | Speaker name, up to 200 characters. | | `title` | string | Title, up to 200 characters. | | `company_name` | string | Company name; null clears it. | | `bio` | string | Biography; null clears it. | | `social_networks` | object | Map of social network names to HTTP(S) URLs. | | `id` | integer | Speaker ID. | | `avatar` | string | Image URL. | #### Update a speaker ``` PATCH /api/dev/v1/events/{event_id}/speakers/{speaker_id} ``` Update only the fields sent; omitted fields are preserved. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `speaker_id` | path | yes | An integer speaker ID from the event speakers list. | **Request body** (`application/json`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `name` | string | no | Speaker name, up to 200 characters. | | `title` | string | no | Title, up to 200 characters. | | `company_name` | string | no | Company name; null clears it. | | `bio` | string | no | Biography; null clears it. | | `social_networks` | object | no | Map of social network names to HTTP(S) URLs. | | `avatar_url` | string | no | HTTP(S) image URL, up to 255 characters. Stored as a source URL without downloading; the source must remain accessible. | Example: ```bash curl -X PATCH "https://api.kommunity.com/api/dev/v1/events/{event_id}/speakers/{speaker_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "bio": "Updated biography" }' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `name` | string | Speaker name, up to 200 characters. | | `title` | string | Title, up to 200 characters. | | `company_name` | string | Company name; null clears it. | | `bio` | string | Biography; null clears it. | | `social_networks` | object | Map of social network names to HTTP(S) URLs. | | `id` | integer | Speaker ID. | | `avatar` | string | Image URL. | #### Delete a speaker ``` DELETE /api/dev/v1/events/{event_id}/speakers/{speaker_id} ``` Soft-delete this record. Track deletion also deletes its sessions; session and speaker deletion remove speaker assignments. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `speaker_id` | path | yes | An integer speaker ID from the event speakers list. | Example: ```bash curl -X DELETE "https://api.kommunity.com/api/dev/v1/events/{event_id}/speakers/{speaker_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — 204 · An empty body. #### Create a track ``` POST /api/dev/v1/events/{event_id}/tracks ``` Create a record for this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | **Request body** (`application/json`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `name` | string | yes | Track name. | | `day` | string | yes | Date, YYYY-MM-DD. | | `sort` | integer | no | Non-negative display order. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/tracks" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "name": "Main stage", "day": "2026-10-20", "sort": 0 }' ``` **Response** — 201 · A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Track ID. | | `name` | string | Track name. | | `day` | string | Date, YYYY-MM-DD. | | `sort` | integer | Display order. | | `sessions` | array | Sessions in this track. | #### Update a track ``` PATCH /api/dev/v1/events/{event_id}/tracks/{track_id} ``` Update only the fields sent; omitted fields are preserved. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `track_id` | path | yes | An integer track ID from the event agenda. | **Request body** (`application/json`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `name` | string | no | Track name. | | `day` | string | no | Date, YYYY-MM-DD. | | `sort` | integer | no | Non-negative display order. | Example: ```bash curl -X PATCH "https://api.kommunity.com/api/dev/v1/events/{event_id}/tracks/{track_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "sort": 1 }' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Track ID. | | `name` | string | Track name. | | `day` | string | Date, YYYY-MM-DD. | | `sort` | integer | Display order. | | `sessions` | array | Sessions in this track. | #### Delete a track ``` DELETE /api/dev/v1/events/{event_id}/tracks/{track_id} ``` Soft-delete this record. Track deletion also deletes its sessions; session and speaker deletion remove speaker assignments. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `track_id` | path | yes | An integer track ID from the event agenda. | Example: ```bash curl -X DELETE "https://api.kommunity.com/api/dev/v1/events/{event_id}/tracks/{track_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — 204 · An empty body. #### Create a session ``` POST /api/dev/v1/events/{event_id}/tracks/{track_id}/sessions ``` Create a record for this event. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `track_id` | path | yes | An integer track ID from the event agenda. | **Request body** (`application/json`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `title` | string | yes | Session title. | | `start_at` | string | yes | UTC time, HH:mm. | | `end_at` | string | yes | UTC time, HH:mm; strictly after start_at on the same UTC day. | | `description` | string | no | Description; null clears it. | | `is_keynote` | boolean | no | Whether this is a keynote. | | `speakers` | array | no | Integer speaker IDs from this event. [] removes all speakers; omission preserves assignments on update. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/tracks/{track_id}/sessions" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "title": "Opening talk", "start_at": "09:00", "end_at": "10:00", "speakers": [ 1 ] }' ``` **Response** — 201 · A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Session ID. | | `title` | string | Session title. | | `start_at` | string | UTC time, HH:mm. | | `end_at` | string | UTC time, HH:mm; strictly after start_at on the same UTC day. | | `description` | string | Description; null clears it. | | `is_keynote` | boolean | Whether this is a keynote. | | `speakers` | array | Assigned speaker objects, including id, name, title, company_name, bio, social_networks and avatar. | #### Update a session ``` PATCH /api/dev/v1/events/{event_id}/tracks/{track_id}/sessions/{session_id} ``` Update only the fields sent; omitted fields are preserved. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `track_id` | path | yes | An integer track ID from the event agenda. | | `session_id` | path | yes | An integer session ID within the track, from the event agenda. | **Request body** (`application/json`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `title` | string | no | Session title. | | `start_at` | string | no | UTC time, HH:mm. | | `end_at` | string | no | UTC time, HH:mm; strictly after start_at on the same UTC day. | | `description` | string | no | Description; null clears it. | | `is_keynote` | boolean | no | Whether this is a keynote. | | `speakers` | array | no | Integer speaker IDs from this event. [] removes all speakers; omission preserves assignments on update. | Example: ```bash curl -X PATCH "https://api.kommunity.com/api/dev/v1/events/{event_id}/tracks/{track_id}/sessions/{session_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "title": "Updated talk" }' ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `id` | integer | Session ID. | | `title` | string | Session title. | | `start_at` | string | UTC time, HH:mm. | | `end_at` | string | UTC time, HH:mm; strictly after start_at on the same UTC day. | | `description` | string | Description; null clears it. | | `is_keynote` | boolean | Whether this is a keynote. | | `speakers` | array | Assigned speaker objects, including id, name, title, company_name, bio, social_networks and avatar. | #### Delete a session ``` DELETE /api/dev/v1/events/{event_id}/tracks/{track_id}/sessions/{session_id} ``` Soft-delete this record. Track deletion also deletes its sessions; session and speaker deletion remove speaker assignments. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `track_id` | path | yes | An integer track ID from the event agenda. | | `session_id` | path | yes | An integer session ID within the track, from the event agenda. | Example: ```bash curl -X DELETE "https://api.kommunity.com/api/dev/v1/events/{event_id}/tracks/{track_id}/sessions/{session_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" ``` **Response** — 204 · An empty body. #### Upload a speaker avatar ``` POST /api/dev/v1/events/{event_id}/speakers/{speaker_id} ``` Partial update alias for multipart uploads. Send avatar as a JPG, PNG or WebP file up to 5 MB; any aspect ratio is accepted. Never combine avatar and avatar_url. Creation also accepts multipart/form-data on POST /speakers with name, title and optional avatar. If no image is supplied at creation, a letter avatar is generated. **Parameters** | Parameter | In | Required | Description | |---|---|---|---| | `event_id` | path | yes | The event's immutable UUID (from the events list, the event detail, or the webhook envelope's event_id). The event slug is also accepted, but slugs can change when an event is renamed — prefer the UUID. | | `speaker_id` | path | yes | An integer speaker ID from the event speakers list. | **Request body** (`multipart/form-data`) — JSON. | Field | Type | Required | Description | |---|---|---|---| | `avatar` | file | no | New image file. | Example: ```bash curl -X POST "https://api.kommunity.com/api/dev/v1/events/{event_id}/speakers/{speaker_id}" \ -H "Authorization: Bearer " \ -H "Accept: application/json" \ -F "avatar=@ada.png" ``` **Response** — A JSON object with the record under `data`. | Field | Type | Description | |---|---|---| | `name` | string | Speaker name, up to 200 characters. | | `title` | string | Title, up to 200 characters. | | `company_name` | string | Company name; null clears it. | | `bio` | string | Biography; null clears it. | | `social_networks` | object | Map of social network names to HTTP(S) URLs. | | `id` | integer | Speaker ID. | | `avatar` | string | Image URL. | --- Two limits worth knowing before you build against the ticket operations: - Tickets issued through the API always carry a zero paid price, so they can be **cancelled but never refunded**. Refunding is for tickets somebody actually paid for. - Refunding, and cancelling a paid ticket, require ticket refunds to be enabled for the event or the community. Ask the organizer to turn them on in the console if you get "Ticket refunds are not enabled for this event." A free ticket can always be cancelled. - A passive or paused ticket type (one hidden from the event page, such as a VIP ticket) can still be issued: send `ignore_status: true`. Without it the call is rejected under `errors.ticket_status`. And two on the sponsor operations: - Editing a sponsor REPLACES it. A field you leave out is cleared — read the sponsor first and send its fields back, minus what you are changing. The logo is the exception: leave it out and it is kept. - Sponsor ids are integers, not UUIDs, so they are sequential and guessable. An id outside your community answers `404`, but do not treat one you happen to be able to read as one you were meant to touch. --- # Kommunity Public API — Reference > Read-only public endpoints that power discovery on kommunity.com: browse events and communities, and read a single community or event. No authentication is required. Responses are JSON; list endpoints use Laravel pagination (`data`, `links`, `meta`). - **OpenAPI spec:** https://kommunity.com/docs/api/public-api.openapi.json - **Base URL:** `https://api.kommunity.com/api/v1` - **Health:** `https://api.kommunity.com/api/health-check` --- ## Discover ### List events — `GET /explore/events` Browse upcoming public events. Powers https://kommunity.com/explore/events. Query params: `page`, `city_id`, `category_id`, `language` (all optional). ``` GET https://api.kommunity.com/api/v1/explore/events?page=1 ``` ### List communities — `GET /explore/communities` Find public communities. Powers https://kommunity.com/explore/kommunities. Query params: `page`, `category_id` (optional). --- ## Detail ### Get a community — `GET /{community_slug}` Public detail for one community. Powers `https://kommunity.com/{community_slug}` (e.g. `/mdisec`). ``` GET https://api.kommunity.com/api/v1/mdisec ``` Returns `data`: `id`, `name`, `slug`, `description`, `avatar`, `cover_photo`, `members_count`, `url`. ### Get an event — `GET /{community_slug}/events/{event_slug}` Public detail for one event. Powers `https://kommunity.com/{community_slug}/events/{event_slug}`. ``` GET https://api.kommunity.com/api/v1/latentshiftai/events/latentshiftai-conference-... ``` Returns `data`: `id`, `name`, `slug`, `detail` (HTML), `start_date`, `end_date` (ISO 8601), `timezone`, `is_online`, `attendance_type` (0 = RSVP, 1 = ticketed), `cover_photo`, `community`, `venue`, `url`. --- ## Notes - This catalog documents the discovery-oriented subset of the public API. The full `/api/v1` surface has many more endpoints (used by the Kommunity web app) that are not part of this public, agent-facing contract. - For organizers who need programmatic access to their own community's data (attendees, tickets, members), see the [Developer API](https://kommunity.com/docs/developer-api.md). --- # Kommunity Webhooks — Developer Reference > Machine-readable reference for integrating with Kommunity webhooks. Kommunity is a community platform for organizers to run communities and events. Webhooks let you receive real-time HTTP notifications when things happen in a community (members joining, RSVPs, ticket sales, wall posts) and send them anywhere — Slack, a CRM, a data warehouse, or your own service. This complements the existing Zapier integration; webhooks are the direct, self-hosted option. - **Human announcement:** https://kommunity.com/blog/introducing-webhooks-send-your-kommunity-events-anywhere - **Setup UI:** Organizer panel → your community → **Developer → Webhooks** tab - **Transport:** HTTPS `POST`, `Content-Type: application/json` --- ## Setup 1. Open the community's **Developer** panel → **Webhooks** tab. 2. Add an endpoint: enter a **Payload URL** you control and select one or more **event types**. 3. Save. Deliveries begin immediately. Scope: - **Community-scoped endpoint** — added from the community Developer panel; receives events across the whole community. - **Event-scoped endpoint** — added from a single event's Developer panel; receives that event's events only. You may register multiple endpoints, each subscribed to a different set of event types. Organizers do not choose a secret: Kommunity generates a signing secret per community and per event, shown in the Webhooks tab (see Verification). --- ## Delivery envelope Every delivery is a single JSON `POST`. The body always begins with this envelope, then merges in the event-specific fields as flat top-level keys: | Field | Type | Notes | |---|---|---| | `event_type` | string | The event type slug (e.g. `event.ticket.created`) | | `event_id` | string \| null | Present for event-scoped events; `null` for community/wall events | | `event_slug` | string \| null | Present for event-scoped events; `null` for community/wall events | | `community_slug` | string | The community's slug | | `community_name` | string | The community's display name | | _…event fields…_ | — | Merged in at the top level (not nested) | --- ## Event types Eleven event types, grouped by scope. | Event type | Scope | Fires when | Payload group | |---|---|---|---| | `community.joined` | community | A member joins the community | Member | | `community.left` | community | A member leaves the community | Member | | `event.created` | event | A new event is created | Event | | `event.rsvp.join` | event | A user RSVPs "yes" to an event | Member | | `event.rsvp.join_the_waiting_list` | event | A user joins an event's waiting list / approval queue | Member | | `event.left` | event | A registration to an event goes away: the user cancels, an organizer removes it, or the subscription behind a subscriber-only event ends | Member + `reason` | | `event.ticket.created` | event | A ticket is purchased | Ticket | | `event.ticket.updated` | event | A ticket is updated | Ticket | | `event.ticket.canceled` | event | A ticket is canceled or refunded | Ticket | | `order.paid` | event | A paid order cleared and all its tickets were issued (once per order; free orders do not send it) | Order | | `wall.post.created` | community | A new post is added to the community wall | Wall post | --- ## Payload schemas All fields listed below appear at the top level of the JSON body, alongside the envelope fields. ### Member payload Used by: `community.joined`, `community.left`, `event.rsvp.join`, `event.rsvp.join_the_waiting_list`, `event.left`. | Field | Type | Description | |---|---|---| | `id` | string | User id | | `name` | string | User full name | | `username` | string | Kommunity username | | `avatar` | string \| null | Avatar image URL | `event.left` adds one field to the member payload: | Field | Type | Description | |---|---|---| | `reason` | string | Why the registration went away: `self` (the attendee or an organizer removed it), `subscription_canceled` (a subscriber-only event whose subscription ended), `subscription_payment_failed` (the subscription ended because the payment could not be collected). | ### Event payload Used by: `event.created`. | Field | Type | Description | |---|---|---| | `id` | string | Event id | | `name` | string | Event name | | `slug` | string | Event slug | | `detail` | string | Event description as HTML. For RSVP events an RSVP link is appended; for ticket events a ticket link is appended. | | `detail_without_html` | string | Plain-text version of `detail` | | `url` | string | Public event page URL | | `start_date` | string | ISO 8601 | | `end_date` | string | ISO 8601 | ### Ticket payload Used by: `event.ticket.created`, `event.ticket.updated`, `event.ticket.canceled`. | Field | Type | Description | |---|---|---| | `order_ticket_id` | string | Unique id of the sold ticket | | `status` | integer | Ticket status code. `1` = active; higher codes represent canceled/refunded states. On `event.ticket.canceled` this reflects the cancel/refund state. | | `attendee_name` | string | Attendee full name | | `email` | string | Attendee email address | | `phone_number` | string \| null | Attendee phone number | | `pnr` | string | Booking reference (PNR) | | `ticket_name` | string \| null | Name of the ticket type purchased | | `paid_price` | integer | Amount actually paid after discounts, in the currency's minor unit (kuruş/cents): `2500` = 25.00. `0` for free tickets and tickets issued by an organizer. | | `discount_code` | string \| null | Discount code used at checkout | | `attendee_company_name` | string \| null | Custom registration field: company | | `attendee_title` | string \| null | Custom registration field: job title | ### Order payload Used by: `order.paid`. The body is the same as `GET /api/dev/v1/orders/{order_id}` in the Developer API, so one parser covers both. | Field | Type | Description | |---|---|---| | `order_id` | string | Order id, the same id the checkout returned | | `status` | string | Order status; `tickets_created` on this event | | `paid` | boolean | Always `true` on this event | | `tickets_issued` | boolean | Always `true` on this event | | `price` | integer | Amount charged, in the currency's minor unit (kuruş/cents): `5000` = 50.00 | | `total_price` | integer | Basket total before discounts, in minor units | | `currency` | string | ISO 4217 currency code, e.g. `TRY` | | `payment_provider` | string \| null | `stripe`, `iyzico`, `mony` or `sipay` | | `buyer` | object | `name`, `email` and `phone` of the buyer | | `card` | object | `last_four_digits` and `association` (visa, mastercard, ...) of the card used | | `created_at` | string | When the order was opened, ISO 8601 | | `tickets` | array | Issued tickets: the ticket payload fields plus `currency`, `checked_in_at` and a `meta` object for the custom registration fields | ### Wall post payload Used by: `wall.post.created`. | Field | Type | Description | |---|---|---| | `id` | string | Post id | | `content` | null | Always `null` — the post body is not included in the webhook | | `wall` | string | Community id the post belongs to | | `url` | string \| null | Public post URL | | `author_id` | string \| null | Post author user id | | `author_username` | string \| null | Post author username | | `author_name` | string \| null | Post author full name | --- ## Example payloads ### `event.ticket.created` ```json { "event_type": "event.ticket.created", "event_id": "01HXAMPLE...", "event_slug": "annual-conf-2026", "community_slug": "acme", "community_name": "Acme Community", "order_ticket_id": "01HTICKET...", "status": 1, "attendee_name": "Jane Doe", "email": "jane@example.com", "phone_number": null, "pnr": "ABC123", "ticket_name": "General Admission", "paid_price": 2500, "discount_code": null, "attendee_company_name": null, "attendee_title": null } ``` ### `order.paid` ```json { "event_type": "order.paid", "event_id": "e1e2e3e4-0000-4000-8000-000000000001", "event_slug": "annual-conf-2026", "community_slug": "acme", "community_name": "Acme Community", "order_id": "0199a3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b", "status": "tickets_created", "paid": true, "tickets_issued": true, "price": 5000, "total_price": 5000, "currency": "TRY", "payment_provider": "stripe", "buyer": { "name": "Jane Doe", "email": "jane@acme.com", "phone": null }, "card": { "last_four_digits": "4242", "association": "visa" }, "created_at": "2026-09-29T10:15:00.000000Z", "tickets": [ { "order_ticket_id": "9425c9e5-2c01-415d-9d88-14c5de64167d", "status": 1, "attendee_name": "Jane Doe", "email": "jane@acme.com", "phone_number": null, "pnr": "837682272137143951", "ticket_name": "General admission", "paid_price": 2500, "discount_code": null, "meta": { "attendee_company_name": null, "attendee_title": null }, "currency": "TRY", "checked_in_at": null } ] } ``` ### `community.joined` ```json { "event_type": "community.joined", "event_id": null, "event_slug": null, "community_slug": "acme", "community_name": "Acme Community", "id": "01HUSER...", "name": "Jane Doe", "username": "janedoe", "avatar": "https://api.kommunity.com/api/v1/user/avatar/janedoe" } ``` ### `event.created` ```json { "event_type": "event.created", "event_id": "01HXAMPLE...", "event_slug": "annual-conf-2026", "community_slug": "acme", "community_name": "Acme Community", "id": "01HXAMPLE...", "name": "Annual Conf 2026", "slug": "annual-conf-2026", "detail": "

Join us...

RSVP: https://kommunity.com/acme/events/annual-conf-2026

", "detail_without_html": "Join us... RSVP: https://kommunity.com/acme/events/annual-conf-2026", "url": "https://kommunity.com/acme/events/annual-conf-2026", "start_date": "2026-09-01T18:00:00+00:00", "end_date": "2026-09-01T21:00:00+00:00" } ``` --- ## Verification Every delivery carries a timestamped HMAC signature: ``` Kommunity-Signature: t=1759140900,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd ``` - `t` is the Unix time the attempt was sent. Each retry is signed again with its own `t`. - `v1` is the hex HMAC-SHA256 of the string `.`, keyed with your **signing secret**. - The signing secret is shown in the **Webhooks** tab. A community-wide endpoint uses the community's secret; an endpoint added on a single event uses that event's secret. **Create a new secret** replaces it; the old one stops working at once. To verify a request: 1. Read the raw body exactly as received, before any JSON parsing. 2. Split the header on `,` and take `t` and every `v1` value. 3. Reject the request if `t` is more than 5 minutes away from your clock. This is what stops a captured request from being replayed later. 4. Compute HMAC-SHA256 of `.` with your secret and compare it to `v1` with a constant-time comparison. ```python import hashlib, hmac, time SECRET = b"" def verify(header: str, raw_body: bytes) -> bool: parts = [p.split("=", 1) for p in header.split(",") if "=" in p] timestamp = next((v for k, v in parts if k.strip() == "t"), None) signatures = [v for k, v in parts if k.strip() == "v1"] if timestamp is None or abs(time.time() - int(timestamp)) > 300: return False expected = hmac.new(SECRET, f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(expected, s) for s in signatures) ``` ### Legacy token Deliveries also still carry the older fixed token, for integrations built before the signature above: ``` Kommunity-Signature-256: ``` It never changes and is not tied to the body, so anyone who sees one request can forge the next. Use `Kommunity-Signature` for new integrations and move existing ones over. --- ## Delivery semantics | Property | Value | |---|---| | HTTP method | `POST` | | Content-Type | `application/json` | | Timeout | ~3 seconds per attempt (slower responses count as a failure) | | Retries | Up to 8 attempts. After a failure we wait 1 min, 5 min, 15 min, 30 min, 1 h, 2 h and 4 h, so an endpoint that is down for up to about 8 hours still gets the delivery. | | Guarantee | At-least-once (design your handler to be idempotent) | | TLS | Required; certificate is verified | Return any `2xx` status to acknowledge receipt. Non-`2xx` responses or timeouts are retried. Every attempt — success or failure, with the response code and the exact JSON sent — is visible under **Recent deliveries** in the Webhooks tab. --- ## Integration checklist 1. Register an endpoint in **Developer → Webhooks** and select the event types you need. 2. Store the signing secret from the panel in your config. 3. On each request: verify the `Kommunity-Signature` header against the raw body, then parse the JSON. 4. Branch on `event_type`; read the envelope + event fields. 5. Return `2xx` quickly; do heavy work asynchronously (retries + ~3s timeout). 6. Make the handler idempotent (dedupe on `order_id` / `order_ticket_id` / `id` as appropriate). 7. Use **Recent deliveries** to inspect failures and replay logic.