Donations

Donations

Cobuntu lets hosts accept donations alongside (or instead of) a ticket purchase or a product sale. The same backend supports three distinct flows so you can route every donor — paying buyer, free-RSVP attendee, or fan who just wants to support — through one consistent API.

This page is the developer-flow guide. Start here, then dive into REST API: events and REST API: products for the endpoint reference.

The three flows

FlowWhen to useAPI path
Sidecar on a paid checkoutBuyer wants the ticket / product and leave a tip on top.POST /events/:slug/checkout with donation: { amount }
Free RSVP + donationFree event; buyer wants to RSVP and donate.POST /events/:slug/rsvp with donation: { amount }, successUrl, cancelUrl
Just donate (standalone)Buyer wants to support without taking the ticket / product.POST /events/:slug/donate or POST /products/:sku/donate

All three flows land in the same Stripe Checkout funnel and fire the same donation.received webhook on completion. Refunds fire donation.refunded.

How the money moves

Donations are 100% to the seller minus Stripe's percentage fee (~2.9%). No community commission, no platform fee. A €10 donation lands ~€9.71 in the host's payout.

The donation rides in the same Stripe Checkout session as the primary purchase (sidecar / free-RSVP) or as the entire session (standalone). Either way, the funds land in Cobuntu's platform balance and are paid out to the seller on the same schedule as their other sales.

Configuring donations on an event or product

Hosts configure donations from the cobuntu admin: open the event or product → Edit priceDonations section. Two modes:

  • Fixed amounts — chip group (e.g. €5 · €10 · €25). Buyers pick one. Up to 8 chips per config.
  • Pay what you want — buyer enters any amount. Optional minimum floor.

Plus an optional label rendered above the picker on the buyer side. Defaults to "Add a donation" when omitted.

No public-API config endpoint in v1. Hosts must configure via the admin UI. A PUT /donations endpoint is on the roadmap; subscribe to the changelog for updates.

A minimal landing-page flow

This is what a custom landing page integration looks like end-to-end. Bela Escala uses this exact pattern.

1. Fetch the event + its donationConfig

const event = await fetch(
  `https://api.cobuntu.com/api/v1/communities/${communityTag}/events/${slug}`,
  { headers: { 'X-API-Key': PUBLISHABLE_KEY } }
).then(r => r.json());
 
// event.donationConfig: { enabled, mode, amounts?, minAmount?, currency, label? } | null

2. Render a donation picker when enabled

If event.donationConfig?.enabled, render a chip group (mode === 'fixed') or an amount input (mode === 'pwyw'). Capture the buyer's amount in your component state.

3. Wire it onto the existing checkout / rsvp call

When the buyer hits Buy / RSVP, forward the donation amount as a sidecar:

await fetch(`https://api.cobuntu.com/api/v1/communities/${tag}/events/${slug}/checkout`, {
  method: 'POST',
  headers: {
    'X-API-Key': PUBLISHABLE_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    tierId: pickedTierId,
    quantity: 1,
    successUrl: `${origin}/eventos/${slug}?checkout=success`,
    cancelUrl: `${origin}/eventos/${slug}?checkout=cancel`,
    donation: donationAmount > 0 ? { amount: donationAmount } : undefined,
  }),
}).then(r => r.json());
// → { checkoutUrl, sessionId }
// Redirect the buyer: window.location.href = checkoutUrl

4. Add a "Just donate" affordance

Show a secondary button once the buyer has picked an amount but doesn't want the ticket:

await fetch(`https://api.cobuntu.com/api/v1/communities/${tag}/events/${slug}/donate`, {
  method: 'POST',
  headers: {
    'X-API-Key': PUBLISHABLE_KEY,
    'Content-Type': 'application/json',
    'X-Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    donation: { amount: donationAmount },
    customer: { email: donorEmail },
    successUrl: `${origin}/eventos/${slug}?donate=success`,
    cancelUrl: `${origin}/eventos/${slug}?donate=cancel`,
  }),
}).then(r => r.json());

5. Handle the success redirect

Stripe redirects back to your successUrl with ?session_id=cs_.... Show a thank-you page. The donation is already recorded server-side by the time the buyer's browser returns — no client-side confirmation needed.

6. Subscribe to the webhook (optional)

For automated thank-you emails, CRM sync, or real-time fundraising progress widgets, subscribe to donation.received via your community's webhook subscription. Each donation fires exactly once (idempotent on transactionId).

Member attribution

If the donor is a signed-in member on your landing, forward their identity so the donation is attributed to their user record:

  • X-Cobuntu-Auth-Token: <cobuntu_auth cookie value> — for landings that already have the buyer logged in via the standard community-app cookie.
  • X-Cobuntu-Member-Token: <SSO token> — for SSO-handshake flows.

Without these headers the donor is treated as anonymous (email-only via the customer.email field).

Showing fundraising progress

The aggregated summary endpoints power public progress widgets:

const { total, count, currency } = await fetch(
  `https://api.cobuntu.com/api/v1/communities/${tag}/events/${slug}/donations/summary`,
  { headers: { 'X-API-Key': PUBLISHABLE_KEY } }
).then(r => r.json());
 
// → { total: 42000, count: 18, currency: "EUR" }

Anonymized: returns aggregates only, never individual donor identities. Same shape on the product endpoint.

Limits & guardrails

  • Stripe minimum charge. The standalone /donate endpoints enforce a 50-smallest-unit floor (≈ €0.50). Sidecar donations on a paid checkout don't have this floor — the ticket carries the session total.
  • Rate limit. 30 standalone donations per IP per hour. Sidecar / free-RSVP donations share the broader per-IP API rate limits.
  • Idempotency. Pass X-Idempotency-Key on /donate calls to dedupe retransmits at Stripe's session-create layer.
  • Currency. A donation must use the parent event/product's pricing currency. Cross-currency donations are out of scope in v1.

What's not in v1

  • Standalone donation refunds via the public API. Refunds today fire only when the parent sale is refunded. Direct "refund this donation" endpoints are on the roadmap.
  • Partial donation refunds. Today's refund flow doesn't distinguish ticket vs donation portions; full refunds refund the donation too, partial refunds leave the donation attributed.
  • Public-API donation-config write endpoint. Configure via the admin UI for now.
  • Recurring donations. One-shot only; recurring patronage via a subscription tier is a separate primitive.