Event catalog
Cobuntu emits 49 webhook events across 8 domains. Subscribe to whichever ones you need in admin → Integrations → Webhooks.
What we expose vs. what we keep silent
Cobuntu is built to be the invisible OS for your community. Setup-side signals — Stripe Connect verification, payout-rail health, account onboarding state — are handled by us on your behalf and are not exposed as customer webhooks. The events catalogued here cover what's happening inside your community: members, events, marketplace, sales, payouts. If a signal feels like platform plumbing, that's by design — those are ours to reconcile, not yours to subscribe to.
Every event payload arrives with the same envelope:
{
"event": "member.approved",
"deliveryId": "del_8f9a2b3c4d5e6f7g",
"createdAt": "2026-05-26T15:33:20Z",
"communityTag": "my-community",
"data": { /* per-event fields below */ }
}Delivery is at-least-once, HMAC-signed (see Verify signatures), and retried with exponential backoff (see Retry & delivery).
Quick index
Memberships
member.requested — Membership requested
Someone submitted a membership form (Tally / JotForm / in-app form / CSV import). Fires for every new request, regardless of community accessibility.
When it fires. A prospective member submits a membership form — from an in-app form, a Tally / JotForm embed, the marketing landing page, or a bulk CSV import. Fires once per submission. For an OPEN community the request is auto-approved and member.approved fires immediately afterward; for review-required communities the request is left PENDING.
Example payload (data field):
{
requestId: "f1c2a8d3-4e5b-4c7a-9f1e-8d2b6a5c3f01",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
segmentName: "Founding Members",
email: "alice@example.com",
name: "Alice Souza",
source: "tally",
answers: { company: "Acme", referral: "Friend", whyJoin: "Looking for like-minded founders." }
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
requestId | uuid | Stable identifier for this membership request. | |
segmentId | uuid | ✓ | Segment the request targets; null when applying without a specific tier. |
segmentName | string | ✓ | Human-readable segment name, mirrors segmentId. |
email | string | Email submitted on the form. | |
name | string | ✓ | Display name; null when the form didn't collect one. |
source | string | Where the request came from — e.g. tally, jotform, landing_page, csv_import, admin_added. | |
answers | object | Raw form answers keyed by field name. Shape varies per community form. |
Related: member.approved, member.rejected, application.withdrawn
member.invited — Member invited
An admin sent a personal invitation to join the community (or a specific tier / role group within it).
When it fires. An admin clicks Send in Members > Invite Members. Fires once per recipient email. The invite carries a token; the recipient clicks the link, lands on /join, and accepts → member.approved fires next.
Example payload (data field):
{
email: "alice@example.com",
inviterName: "Diogo Cesar",
inviterAvatar: "https://storage.googleapis.com/.../diogo.png",
customMessage: "Hey Alice — I'd love for you to join us.",
acceptUrl: "https://my-community.cobuntu.com/join?invite=t_abc123…",
segmentName: "Founding Members",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
communityDescription: "A community for travellers who want to experience Lisbon like a local."
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
email | string | Invitee's email address. | |
inviterName | string | Display name of the admin who sent the invitation. | |
inviterAvatar | string | ✓ | Profile-image URL of the inviter; null when no avatar set. |
customMessage | string | ✓ | Optional personal message the admin attached to the invitation. |
acceptUrl | string | Tokenised URL the invitee opens to accept. Points at the community subdomain's /join (or admin.cobuntu.com/join for role-gated invites). | |
segmentName | string | ✓ | Human-readable name of the tier the invite targets; null when no tier was specified. |
segmentId | uuid | ✓ | Tier (segment) the invite targets; null when none was specified. |
communityDescription | string | ✓ | Short description of the community, used to give the recipient context. |
Related: member.approved, member.rejected
member.approved — Membership approved
A membership request was approved (manually by an admin OR auto-approved for OPEN communities).
When it fires. A pending membership request transitions to APPROVED — either an admin clicked Approve in the admin app, or the community is OPEN and the request was auto-approved immediately after member.requested. The user has full member access from this point.
Example payload (data field):
{
requestId: "f1c2a8d3-4e5b-4c7a-9f1e-8d2b6a5c3f01",
email: "alice@example.com",
name: "Alice Souza"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
requestId | uuid | Matches the requestId from the original member.requested event. | |
email | string | Approved member's email. | |
name | string | ✓ | Display name; null when unknown. |
Related: member.requested, member.rejected
member.rejected — Membership rejected
A membership request was rejected by an admin.
When it fires. An admin reviewed a pending membership request and rejected it. The applicant does not get member access; they can re-apply unless explicitly banned.
Example payload (data field):
{
requestId: "f1c2a8d3-4e5b-4c7a-9f1e-8d2b6a5c3f01",
email: "alice@example.com",
name: "Alice Souza"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
requestId | uuid | Matches the requestId from the original member.requested event. | |
email | string | Rejected applicant's email. | |
name | string | ✓ | Display name; null when unknown. |
Related: member.requested, member.approved
form.submitted — Form submitted (deprecated)
DEPRECATED — alias for member.requested, fired alongside it for back-compat. New integrations should subscribe to member.requested instead.
When it fires. Fires alongside every member.requested event for backwards compatibility with older integrations that pre-date the rename. New integrations should subscribe to member.requested directly — this alias will be removed in a future major version.
Example payload (data field):
{
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
segmentName: "Founding Members",
email: "alice@example.com",
name: "Alice Souza",
source: "tally",
answers: { company: "Acme", referral: "Friend" }
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
segmentId | uuid | ✓ | Segment the form was scoped to; null when unscoped. |
segmentName | string | ✓ | Human-readable segment name. |
email | string | Submitted email. | |
name | string | ✓ | Display name. |
source | string | Form provider — tally, jotform, landing_page. | |
answers | object | Raw form answers keyed by field name. |
Related: member.requested
member.kicked — Member kicked
An admin removed a member from the community. The member can re-apply unless also banned.
When it fires. An admin removed an active member from the community (kicked, not banned). The user loses member access immediately but can submit a fresh membership request later.
Example payload (data field):
{
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
usertag: "alice",
email: "alice@example.com",
name: "Alice Souza",
kickedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
userId | uuid | The kicked member's user id. | |
usertag | string | Their @usertag (handle). | |
email | string | Email at the time of kick. | |
name | string | ✓ | Display name; null when unknown. |
kickedBy | uuid | The admin user id who performed the kick. |
Related: member.banned
member.banned — Member banned
An admin banned a member — irreversible. The member is removed and prevented from re-applying or accepting a future invite.
When it fires. An admin banned a member — a stronger version of kick. The user loses access immediately and is blocked from re-applying or accepting future invites to this community. Bans are not auto-revoked.
Example payload (data field):
{
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
usertag: "alice",
email: "alice@example.com",
name: "Alice Souza",
bannedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
reason: "Repeated harassment after multiple warnings."
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
userId | uuid | The banned member's user id. | |
usertag | string | Their @usertag (handle). | |
email | string | Email at the time of ban. | |
name | string | ✓ | Display name; null when unknown. |
bannedBy | uuid | The admin user id who issued the ban. | |
reason | string | ✓ | Free-text reason recorded by the admin; null when not provided. |
Related: member.kicked
application.withdrawn — Application withdrawn
A user withdrew their own pending membership request before it was reviewed.
When it fires. An applicant cancelled their own pending membership request before any admin reviewed it. The request transitions to WITHDRAWN and is removed from the admin review queue.
Example payload (data field):
{
requestId: "f1c2a8d3-4e5b-4c7a-9f1e-8d2b6a5c3f01",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
name: "Alice Souza",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
requestId | uuid | Matches the original member.requested event. | |
userId | uuid | ✓ | User id if the applicant had an account; null for anonymous form submissions. |
email | string | Applicant email. | |
name | string | ✓ | Display name; null when unknown. |
segmentId | uuid | ✓ | Segment the application targeted; null when unscoped. |
Related: member.requested
Articles
article.published — Article published
An article was published or updated to PUBLISHED status.
When it fires. An admin-authored article transitions to PUBLISHED status. Fires both when an article is published for the first time and when its content is updated while already published — consumers should treat it as an upsert, not a first-publish signal.
Example payload (data field):
{
id: "a3f9c2e0-7e1a-4d2f-9b8a-1c5b3a8f6e92",
title: "The Quiet Side of Lisbon",
slug: "the-quiet-side-of-lisbon",
excerpt: "Beyond the tourist trails — eight hidden corners locals love.",
bannerUrl: "https://cdn.cobuntu.com/articles/lisbon-banner.jpg",
category: "City Guide",
publishedAt: "2026-05-18T09:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
id | uuid | Stable article identifier. | |
title | string | Article title at the time of publish. | |
slug | string | URL-safe slug used in the article's public URL. | |
excerpt | string | Short summary intended for cards and previews. | |
bannerUrl | url | ✓ | Hero image; null when no banner was uploaded. |
category | string | ✓ | Editorial category; null when uncategorized. |
publishedAt | iso-8601 | When the article entered PUBLISHED status. |
Related: article.deleted
article.deleted — Article deleted
An article was deleted.
When it fires. An article was permanently deleted by an admin. Fires once at deletion time and the article will not subsequently reappear under the same id.
Example payload (data field):
{
id: "a3f9c2e0-7e1a-4d2f-9b8a-1c5b3a8f6e92",
title: "The Quiet Side of Lisbon",
slug: "the-quiet-side-of-lisbon"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
id | uuid | The article's stable identifier — useful for cleaning up external mirrors. | |
title | string | Article title at the time of deletion (snapshot for logging). | |
slug | string | Slug that was previously in use; safe to reuse from this point. |
Related: article.published
Events
event.published — Event published
An event went from DRAFT to LIVE — listings became visible.
When it fires. A community event transitions from DRAFT to LIVE status — its listing becomes visible to the relevant audience (public, members-only, or tier-scoped). Fires once per publish transition; editing details after publish does not re-fire.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
slug: "summer-rooftop-meetup",
name: "Summer Rooftop Meetup",
startDate: "2026-07-12T18:00:00Z",
endDate: "2026-07-12T22:00:00Z",
accessibility: "PUBLIC"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | Stable event identifier. | |
slug | string | URL-safe slug used in the event's public URL. | |
name | string | Event title. | |
startDate | iso-8601 | ✓ | Event start; null for date-less events. |
endDate | iso-8601 | ✓ | Event end; null when not set or when same-day with implicit length. |
accessibility | `PUBLIC | MEMBERS_ONLY` |
Related: event.cancelled
event.cancelled — Event cancelled
An event was unpublished or deleted. Auto-refunds for escrowed paid attendees fire as separate refund.completed events.
When it fires. A LIVE event was unpublished or deleted. The listing is removed and registered attendees can no longer find it. If the event had paid attendees still in escrow, refunds are auto-issued and surface as separate refund.completed / refund.issued / event.ticket.refunded events per attendee — handle those for refund-specific actions.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
slug: "summer-rooftop-meetup",
name: "Summer Rooftop Meetup",
action: "unpublished",
attendeeCount: 42
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | Matches the eventId from event.published. | |
slug | string | Slug that was in use. | |
name | string | Event title. | |
action | `unpublished | deleted` | |
attendeeCount | number | Number of registered attendees at cancellation time — useful for sizing follow-up comms. |
Related: event.published, refund.completed
event.attendance.application_received — Application received (applicant-facing)
A member applied to a requires-approval event. Fires once per community-listing on PENDING attendance creation.
Default automation. [System] Event application received sends the applicant a "we received your application" email. Disable it in admin → Automations and consume this webhook to send your own confirmation.
When it fires. A member submits an event registration on an event with requiresApproval=true — the attendance lands in PENDING. Fires for both auth-user and guest-email applications. Does NOT fire for free no-approval events (those go straight to APPROVED + event.attendance.registered).
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "4a7d1c3b-5e2f-4b8a-9c1d-6f3a8b2e5d7c",
email: "alice@example.com",
name: "Alice Souza",
eventName: "Founders' Dinner — June",
eventStartDate: "2026-06-18T17:00:00Z",
eventLocation: "Plumas & Flores, Porto",
eventUrl: "https://my-community.cobuntu.com/c/my-community/events/founders-dinner-june",
isGuest: false
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event the member applied to. | |
attendanceId | uuid | The PENDING attendance row. | |
email | string | Applicant email (user or guest). | |
name | string | ✓ | Display name; null when guest with no name. |
eventName | string | Event title at apply-time. | |
eventStartDate | iso-datetime | ✓ | Event start (ISO 8601). |
eventLocation | string | ✓ | Physical location or online URL. |
eventUrl | url | Community-app event detail page. | |
isGuest | boolean | True when the applicant did not have a Cobuntu account. |
Related: event.attendance.application_awaiting_review, event.attendance.approved, event.attendance.rejected
event.attendance.application_awaiting_review — Application awaiting review (organiser-facing)
A new application is waiting for host or community-leader review. Fires once per community-listing of the event.
Default automation. [System] Event application awaiting review emails the event CREATOR host + community members with EVENTS_MANAGE_LISTINGS. Disable it in admin → Automations and iterate notifiedRecipients in the webhook payload to fan out from your own system.
When it fires. A member applied to attend a requires-approval event. Pairs with event.attendance.application_received (applicant-facing) at the same moment.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "4a7d1c3b-5e2f-4b8a-9c1d-6f3a8b2e5d7c",
applicantEmail: "alice@example.com",
applicantName: "Alice Souza",
applicantUsertag: "alice",
isGuest: false,
eventName: "Founders' Dinner — June",
pendingApprovalCount: 3,
reviewUrl: "https://admin.cobuntu.com/my-community/events/founders-dinner-june",
notifiedRecipients: [
{ email: "host@example.com", name: "Álvaro de Mello", role: "CREATOR" },
{ email: "leader@example.com", name: "Art Linkov", role: "EVENTS_MANAGE_LISTINGS" }
]
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event the member applied to. | |
attendanceId | uuid | The PENDING attendance row awaiting review. | |
applicantEmail | string | The member who applied. | |
applicantName | string | ✓ | Applicant's display name; null for unnamed guests. |
applicantUsertag | string | ✓ | Applicant's usertag; null for guests. |
isGuest | boolean | True when the applicant did not have a Cobuntu account. | |
eventName | string | Event title at apply-time. | |
pendingApprovalCount | number | Total PENDING attendances on this event right now (this one included). | |
reviewUrl | url | Admin event manage page deep-link to the Pending tab. | |
notifiedRecipients | array<{email, name, role}> | Emails that Cobuntu's [System] automation would notify: event CREATOR host + community leaders with EVENTS_MANAGE_LISTINGS. Use this list to drive your own fan-out. |
Related: event.attendance.application_received, event.attendance.approved, event.attendance.rejected
event.attendance.registered — Attendance registered
A person registered for an event — free, paid (after Stripe settlement), or an approved pending application.
When it fires. Someone successfully became an attendee on an event. Fires for free events on form submit, for paid events after Stripe settlement, and for review-required events when an admin approves a pending application. Fires once per attendance row.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "f9e1c3a7-2d4b-4a8c-9e6f-1d3a5b7c9e2f",
tierId: null,
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
name: "Alice Souza",
status: "APPROVED",
isGuest: false,
source: "free_rsvp"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event being registered for. | |
attendanceId | uuid | Stable identifier for this attendance row. | |
tierId | uuid | ✓ | Ticket tier; null for free-RSVP events without tiers. |
userId | uuid | ✓ | Logged-in user id; null for guest sign-ups (magic-link). |
email | string | Attendee email. | |
name | string | ✓ | Display name; null when not provided. |
status | APPROVED | PENDING | Always APPROVED for this event; PENDING means the registration entered the queue and this event has not yet fired. | |
isGuest | boolean | True for magic-link guests without an account. | |
source | free_rsvp | paid_sale | invitation | admin_added | How the registration was created. |
Related: event.attendance.approved, event.attendance.cancelled, event.ticket.purchased
event.attendance.approved — Attendance approved
A host approved a PENDING attendance — the applicant is now confirmed for the event.
When it fires. A host approved a pending application from the event's review queue. The applicant transitions to confirmed; downstream send a confirmation email or unlock event resources.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "f9e1c3a7-2d4b-4a8c-9e6f-1d3a5b7c9e2f",
email: "alice@example.com",
name: "Alice Souza",
reviewedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event being attended. | |
attendanceId | uuid | Matches the attendance row. | |
email | string | Attendee email. | |
name | string | ✓ | Display name; null when unknown. |
reviewedBy | uuid | The host user id who approved. |
Related: event.attendance.registered, event.attendance.rejected
event.attendance.payment_pending — Attendee approved — payment pending
Host approved a PENDING attendance on a paid + requires-approval event. The attendee has 48 hours to complete Stripe Checkout via the Pay-now magic link, or the row flips to CANCELLED.
Default automation. [System] Event payment pending emails the applicant the Pay-now link. Disable it in admin → Automations and use paymentUrl from this webhook to email the applicant from your own system — it's the same single-use signed link Cobuntu would have sent.
When it fires. A host approved a PENDING attendance AND the registrant's tier costs money AND the registrant is not a host of the event. Holds the seat for 48 hours. Does NOT fire for the host carve-out path — when a host of the event applies and gets approved, the row goes straight to APPROVED and event.attendance.approved fires instead.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "4a7d1c3b-5e2f-4b8a-9c1d-6f3a8b2e5d7c",
email: "alice@example.com",
name: "Alice Souza",
reviewedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
eventName: "Founders' Dinner — June",
eventUrl: "https://my-community.cobuntu.com/c/my-community/events/founders-dinner-june",
paymentUrl: "https://my-community.cobuntu.com/checkout/pay-event?token=...",
paymentExpiresAt: "2026-06-05T15:00:00Z",
tierName: "Standard ticket",
tierPriceCents: 2400,
tierCurrency: "EUR"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event being attended. | |
attendanceId | uuid | Matches the attendance row. | |
email | string | Attendee email. | |
name | string | ✓ | Display name; null when unknown. |
reviewedBy | uuid | The host user id who approved. | |
eventName | string | Event title. | |
eventUrl | url | Community-app event detail page. | |
paymentUrl | url | Magic-link to complete Stripe Checkout. Single-use, signed, expires with paymentExpiresAt. | |
paymentExpiresAt | iso-datetime | Deadline for completing payment. After this, the row flips to CANCELLED. | |
tierName | string | Tier the attendee was approved for. | |
tierPriceCents | number | Tier price in minor currency units (cents). | |
tierCurrency | string | ISO 4217 currency code. |
Related: event.attendance.approved, event.attendance.registered, event.attendance.cancelled
event.attendance.rejected — Attendance rejected
A host rejected a PENDING attendance. If the registration was paid, the refund is auto-issued and arrives as a separate refund.completed event.
When it fires. A host rejected a pending application. For paid events the rejection triggers an automatic refund, surfaced as a separate refund.completed / event.ticket.refunded pair.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "f9e1c3a7-2d4b-4a8c-9e6f-1d3a5b7c9e2f",
email: "alice@example.com",
name: "Alice Souza",
reviewedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
reason: "Sold out — added to the waitlist."
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event being attended. | |
attendanceId | uuid | Matches the attendance row. | |
email | string | Applicant email. | |
name | string | ✓ | Display name; null when unknown. |
reviewedBy | uuid | The host user id who rejected. | |
reason | string | ✓ | Optional free-text reason recorded by the host. |
Related: event.attendance.approved, refund.completed
event.attendance.cancelled — Attendance cancelled
An attendance row exited — self-cancel, admin removal, or 48h payment-window expired without payment.
Default automation. [System] Event attendance cancelled emails the attendee. Branch on cancellationReason in the payload to send different copy per cause. Disable it in admin → Automations and use the webhook directly if your messaging needs are richer than the default template.
When it fires. An attendance row entered the terminal CANCELLED state. Causes are carried in cancellationReason: USER_REQUEST (member clicked cancel / used magic-link unsubscribe), ADMIN_REMOVAL (host removed them post-approval), PAYMENT_TIMEOUT (PENDING_PAYMENT row sat unpaid for 48h and the expire-sweep flipped it). For paid registrations with a sale on file, a refund is processed and refund.completed + event.ticket.refunded fire separately.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "f9e1c3a7-2d4b-4a8c-9e6f-1d3a5b7c9e2f",
email: "alice@example.com",
name: "Alice Souza",
wasPaid: true,
refundAmount: 1500,
cancellationReason: "USER_REQUEST",
eventName: "Founders' Dinner — June"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | The event being left. | |
attendanceId | uuid | The attendance row that was cancelled. | |
email | string | Attendee email. | |
name | string | ✓ | Display name; null when unknown. |
wasPaid | boolean | True if the original registration was paid (not free RSVP or invitation). | |
refundAmount | number | ✓ | Refund amount in minor currency units (cents). Null for free cancellations. |
cancellationReason | USER_REQUEST | ADMIN_REMOVAL | PAYMENT_TIMEOUT | The trigger that produced this CANCELLED row. Branch on this to route different downstream automations. | |
eventName | string | Event title at cancel-time. |
For paid cancellations the refund is dispatched as a separate
refund.completed/event.ticket.refundedevent with full Stripe context. Don't try to derive accounting fromrefundAmounthere.
Related: event.attendance.registered, event.attendance.payment_pending, refund.completed, event.ticket.refunded
event.reminder.sent — Event reminder sent
A scheduled event reminder went out — one event per recipient × window × channel.
When it fires. The reminder cron dispatched a reminder for an event. Reminders fire on the four standard windows (7 days, 2 days, 1 day, 1 hour before start) over both EMAIL and WHATSAPP channels — communities can disable specific windows in event settings. One event per (recipient × window × channel) tuple.
Example payload (data field):
{
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
attendanceId: "4a7d1c3b-5e2f-4b8a-9c1d-6f3a8b2e5d7c",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
reminderType: "1d",
channel: "EMAIL",
sentAt: "2026-07-11T18:00:00Z",
eventName: "Summer Rooftop Meetup",
eventDate: "12 Jul 2026",
eventTime: "18:00–22:00 WEST",
eventUrl: "https://acme.cobuntu.com/events/summer-rooftop-meetup",
calendarUrl: "https://acme.cobuntu.com/events/summer-rooftop-meetup.ics"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
eventId | uuid | Event the reminder is for. | |
attendanceId | uuid | ✓ | Recipient's attendance row; null for non-attendee broadcast reminders. |
userId | uuid | ✓ | Recipient user id; null for guests addressed by email only. |
email | string | Reminder recipient. | |
reminderType | 7d | 2d | 1d | 1h | Which reminder window fired. | |
channel | EMAIL | WHATSAPP | Channel the reminder was sent on. | |
sentAt | iso-8601 | When the reminder was actually dispatched. | |
eventName | string | Event title at send time. | |
eventDate | string | Pre-formatted localized date string for rendering. | |
eventTime | string | Pre-formatted localized time-of-day string. | |
eventUrl | url | Public URL of the event. | |
calendarUrl | url | iCalendar (.ics) link for add-to-calendar. |
Related: event.attendance.registered
Marketplace (listings)
listing.requested — Listing submitted for review
A seller submitted a marketplace listing for community review.
When it fires. A seller submitted a new marketplace listing for moderation. The listing enters PENDING_REVIEW status — fires listing.approved or listing.rejected after admin action.
Example payload (data field):
{
listingId: "e5f1c3a7-2d4b-4c8e-9f6a-1b3d5c8a7e2f",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
sellerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
sellerEmail: "alice@example.com",
title: "Lisbon Photo Guide (PDF)",
listingType: "PRODUCT",
price: 2500,
currency: "eur"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
listingId | uuid | Stable listing identifier. | |
communityId | uuid | Community the listing was submitted to. | |
sellerUserId | uuid | Seller user id. | |
sellerEmail | string | Seller email. | |
title | string | Submitted listing title. | |
listingType | `PRODUCT | SERVICE` | |
price | number | Listed price in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). |
Related: listing.approved, listing.rejected, listing.withdrawn
listing.approved — Listing approved
A community admin approved a pending marketplace listing.
When it fires. A community admin approved a pending marketplace listing. The listing transitions to ACTIVE and becomes purchasable.
Example payload (data field):
{
listingId: "e5f1c3a7-2d4b-4c8e-9f6a-1b3d5c8a7e2f",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
sellerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
sellerEmail: "alice@example.com",
title: "Lisbon Photo Guide (PDF)",
reviewedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
listingId | uuid | Listing identifier. | |
communityId | uuid | Community that approved. | |
sellerUserId | uuid | Seller user id. | |
sellerEmail | string | Seller email. | |
title | string | Listing title. | |
reviewedBy | uuid | Admin user id who approved. |
Related: listing.requested, listing.rejected
listing.rejected — Listing rejected
A community admin rejected a pending marketplace listing with a reason.
When it fires. A community admin rejected a pending listing. The seller is informed; the listing does not go live.
Example payload (data field):
{
listingId: "e5f1c3a7-2d4b-4c8e-9f6a-1b3d5c8a7e2f",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
sellerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
sellerEmail: "alice@example.com",
title: "Lisbon Photo Guide (PDF)",
reviewedBy: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
reason: "Photos appear to be stock images rather than original work."
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
listingId | uuid | Listing identifier. | |
communityId | uuid | Community that rejected. | |
sellerUserId | uuid | Seller user id. | |
sellerEmail | string | Seller email. | |
title | string | Listing title. | |
reviewedBy | uuid | Admin user id who rejected. | |
reason | string | ✓ | Optional rejection reason recorded by the admin. |
Related: listing.requested, listing.approved
listing.deactivated — Listing deactivated
A previously-approved marketplace listing was deactivated (admin action or auto-takedown).
When it fires. An ACTIVE marketplace listing was deactivated — admin moderation, content-policy auto-takedown, or seller account suspension. The listing is no longer purchasable but historical data is preserved.
Example payload (data field):
{
listingId: "e5f1c3a7-2d4b-4c8e-9f6a-1b3d5c8a7e2f",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
sellerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
sellerEmail: "alice@example.com",
title: "Lisbon Photo Guide (PDF)",
reason: "Policy violation: misleading description."
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
listingId | uuid | Deactivated listing. | |
communityId | uuid | Community owning the listing. | |
sellerUserId | uuid | Seller user id. | |
sellerEmail | string | Seller email. | |
title | string | Listing title. | |
reason | string | ✓ | Optional reason recorded by the moderator. |
Related: listing.approved, listing.withdrawn
listing.withdrawn — Listing withdrawn
A seller withdrew their own marketplace listing.
When it fires. A seller pulled their own marketplace listing. The listing transitions to WITHDRAWN and is no longer purchasable.
Example payload (data field):
{
listingId: "e5f1c3a7-2d4b-4c8e-9f6a-1b3d5c8a7e2f",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
sellerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
sellerEmail: "alice@example.com",
title: "Lisbon Photo Guide (PDF)"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
listingId | uuid | Withdrawn listing. | |
communityId | uuid | Community owning the listing. | |
sellerUserId | uuid | Seller user id. | |
sellerEmail | string | Seller email. | |
title | string | Listing title. |
Related: listing.requested, listing.deactivated
Sales (orders + refunds)
sale.completed — Sale completed
A paid checkout completed — event ticket, marketplace product, or subscription first payment.
When it fires. A buyer-paid checkout completed successfully on Stripe and the sale was recorded. Fires for event tickets, marketplace products, and the first paid period of a subscription. Typed-by-domain siblings (event.ticket.purchased, product.purchased, subscription.purchase_receipt) fire alongside this generic event — subscribe to the typed event if you only care about one product class.
Example payload (data field):
{
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
transactionId: "3c5a8e1b-4f7d-4c2b-9a6e-1d8f3b5c7a9e",
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
productSnapshotId: null,
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
grossAmount: 1500,
currency: "eur",
productType: "EVENT"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
saleId | uuid | Stable sale identifier. | |
transactionId | uuid | Stripe-side transaction reference stored in Cobuntu. | |
eventId | uuid | ✓ | Set when the sale was for an event ticket. |
productSnapshotId | uuid | ✓ | Set when the sale was for a marketplace product (snapshot of price/title at purchase). |
buyerUserId | uuid | ✓ | Buyer user id; null for magic-link guest purchases. |
buyerEmail | string | Buyer email used at checkout. | |
grossAmount | number | Gross amount in minor currency units (e.g. 1500 = €15.00). | |
currency | string | ISO 4217 currency code in lowercase (e.g. eur, usd). | |
productType | EVENT | DIGITAL | SUBSCRIPTION | What was sold. |
Related: product.purchased, subscription.purchase_receipt, refund.completed
refund.completed — Refund completed
A refund was processed on Stripe and the sale was marked refunded (FULL or PARTIAL). Seller-facing — for buyer-facing automation/emails see refund.issued.
When it fires. Stripe confirmed a refund and the sale was updated to REFUNDED (full) or PARTIALLY_REFUNDED. This is the seller-facing event — refund.issued fires the same moment with buyer-facing context for separate consumer-style handling.
Example payload (data field):
{
refundId: "8c2a4e1b-3d5f-4a7c-9b6e-1d8f3a5c7b9e",
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
amount: 1500,
refundType: "FULL",
reason: "BUYER_REQUEST"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
refundId | uuid | Stable identifier for the refund. | |
saleId | uuid | The sale being refunded — matches sale.completed.saleId. | |
eventId | uuid | ✓ | Set when the underlying sale was for an event ticket. |
buyerUserId | uuid | ✓ | Buyer user id; null for guest purchases. |
buyerEmail | string | Buyer email. | |
amount | number | Refund amount in minor currency units. | |
refundType | FULL | PARTIAL | Whether the full sale or part of it was refunded. | |
reason | BUYER_REQUEST | HOST_CANCELLATION | CHARGEBACK | Why the refund was issued. |
Related: refund.issued, sale.completed, product.refunded
refund.issued — Refund issued (buyer-facing)
Buyer-facing refund event — same fire moment as refund.completed but exposed as its own event so leaders can wire automations that target the BUYER (e.g. drip survey, win-back) without confusing it with their internal accounting hooks on refund.completed.
When it fires. Same fire moment as refund.completed but typed for buyer-facing comms. Use this event when you want to message the buyer (survey, win-back, support follow-up) and refund.completed when you want to record the accounting side internally.
Example payload (data field):
{
refundId: "8c2a4e1b-3d5f-4a7c-9b6e-1d8f3a5c7b9e",
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
amount: 1500,
refundType: "FULL",
reason: "BUYER_REQUEST",
productName: "Summer Rooftop Meetup — General Admission"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
refundId | uuid | Matches refund.completed.refundId. | |
saleId | uuid | The sale being refunded. | |
buyerUserId | uuid | ✓ | Buyer user id; null for guest purchases. |
buyerEmail | string | Buyer email. | |
amount | number | Refund amount in minor currency units. | |
refundType | `FULL | PARTIAL` | |
reason | `BUYER_REQUEST | HOST_CANCELLATION | CHARGEBACK` |
productName | string | ✓ | Pre-formatted product/event title for the buyer-facing email. |
Related: refund.completed
event.ticket.purchased — Event ticket purchased
Typed-by-domain sibling of sale.completed for event tickets only. Lets leaders wire ticket-shaped flows (calendar invites, prep emails, attendee onboarding) without filtering generic sale events.
When it fires. A paid event-ticket checkout completed. Fires alongside sale.completed with event-specific fields. Subscribe here when you only care about event tickets (not marketplace products or subscriptions).
Example payload (data field):
{
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
transactionId: "3c5a8e1b-4f7d-4c2b-9a6e-1d8f3b5c7a9e",
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
eventName: "Summer Rooftop Meetup",
attendanceId: null,
tierId: "a3b5c7d9-2e4f-4a6b-8c0d-1f3e5a7b9c2e",
tierName: "General Admission",
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
buyerName: "Alice Souza",
grossAmount: 1500,
currency: "eur",
hostEmail: "host@acme.example.com",
hostName: "Carla Rocha",
totalTicketsSold: 38
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
saleId | uuid | Underlying sale id. | |
transactionId | uuid | Stripe transaction reference. | |
eventId | uuid | Event the ticket is for. | |
eventName | string | ✓ | Event title at purchase time; null if the event row is missing at dispatch (rare, e.g. concurrent delete). |
attendanceId | uuid | ✓ | Attendance row id when created in the same dispatch; null when the attendance is created downstream by a separate event.attendance.registered event. |
tierId | uuid | ✓ | Ticket tier id. |
tierName | string | ✓ | Tier label. |
buyerUserId | uuid | ✓ | Buyer user id; null for guest checkouts. |
buyerEmail | string | Buyer email. | |
buyerName | string | ✓ | Buyer display name. |
grossAmount | number | Ticket price in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
hostEmail | string | ✓ | Event host email — useful for host-facing notifications. |
hostName | string | ✓ | Event host display name. |
totalTicketsSold | number | Running count of tickets sold for this event after this purchase. |
Related: sale.completed, event.attendance.registered, event.ticket.refunded
product.purchased — Product purchased
A buyer completed checkout for a marketplace product. Fires alongside sale.completed; typed-by-domain so leaders can wire product-shaped flows (post-purchase upsell, fulfillment instructions, review request).
When it fires. A paid marketplace product checkout completed. Fires alongside sale.completed with product-specific fields. Subscribe here when you only care about marketplace products (not event tickets or subscriptions).
Example payload (data field):
{
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
transactionId: "3c5a8e1b-4f7d-4c2b-9a6e-1d8f3b5c7a9e",
productId: "ad1c5e3b-7f2a-4b6c-9e1d-3f8a5c2b7d9e",
productSnapshotId: "be2d6f4c-8a3b-4c7d-9f2e-4a9b6c3d8e1f",
productName: "Lisbon Photo Guide (PDF)",
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
buyerName: "Alice Souza",
grossAmount: 2500,
currency: "eur",
productType: "DIGITAL"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
saleId | uuid | Underlying sale id. | |
transactionId | uuid | Stripe-side transaction reference. | |
productId | uuid | ✓ | Product id (canonical); null if the product was deleted before this event was processed. |
productSnapshotId | uuid | ✓ | Snapshot at purchase time — use this for invoices since the canonical product may change. |
productName | string | Product title at purchase time. | |
buyerUserId | uuid | ✓ | Buyer user id; null for guest purchases. |
buyerEmail | string | Buyer email. | |
buyerName | string | ✓ | Buyer display name. |
grossAmount | number | Price in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
productType | `DIGITAL | EVENT` |
Related: sale.completed, product.refunded
product.refunded — Product refunded
A buyer was refunded for a marketplace product — typed-by-domain split of refund.completed/refund.issued. Lets leaders subscribe specifically to product refunds (e.g. revoke digital download access, mark return shipping, follow up).
When it fires. A refund for a marketplace product completed. Fires alongside refund.completed and refund.issued. Subscribe here when you only want product refunds.
Example payload (data field):
{
refundId: "8c2a4e1b-3d5f-4a7c-9b6e-1d8f3a5c7b9e",
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
productId: "ad1c5e3b-7f2a-4b6c-9e1d-3f8a5c2b7d9e",
productName: "Lisbon Photo Guide (PDF)",
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
amount: 2500,
refundType: "FULL",
reason: "BUYER_REQUEST"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
refundId | uuid | Stable refund identifier. | |
saleId | uuid | Original sale being refunded. | |
productId | uuid | ✓ | Product id; null if deleted. |
productName | string | Product title (snapshot). | |
buyerUserId | uuid | ✓ | Buyer user id; null for guest purchases. |
buyerEmail | string | Buyer email. | |
amount | number | Refund amount in minor currency units. | |
refundType | `FULL | PARTIAL` | |
reason | `BUYER_REQUEST | HOST_CANCELLATION | CHARGEBACK` |
Related: product.purchased, refund.completed, refund.issued
event.ticket.refunded — Event ticket refunded
Typed-by-domain sibling of refund.issued for event tickets only. Lets leaders subscribe specifically to ticket refunds — seat-release, attendee follow-up, host notification.
When it fires. A refund for an event ticket completed. Fires alongside refund.completed + refund.issued with ticket-specific fields. Subscribe here when you only want event-ticket refunds.
Example payload (data field):
{
refundId: "8c2a4e1b-3d5f-4a7c-9b6e-1d8f3a5c7b9e",
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
eventName: "Summer Rooftop Meetup",
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
amount: 1500,
refundType: "FULL",
reason: "BUYER_REQUEST",
productName: "Summer Rooftop Meetup — General Admission"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
refundId | uuid | Stable refund identifier. | |
saleId | uuid | Original sale being refunded. | |
eventId | uuid | Event the refunded ticket was for. | |
eventName | string | ✓ | Event title at the time of refund; null if the event row is missing. |
buyerUserId | uuid | ✓ | Buyer user id; null for guest purchases. |
buyerEmail | string | Buyer email. | |
amount | number | Refund amount in minor currency units. | |
refundType | FULL | PARTIAL | Whether the full sale or part of it was refunded. | |
reason | BUYER_REQUEST | HOST_CANCELLATION | CHARGEBACK | Why the refund was issued. | |
productName | string | ✓ | Pre-formatted display name (event name OR snapshot product name). |
Related: event.ticket.purchased, refund.completed, refund.issued
dispute.created — Chargeback dispute opened
Stripe notified that a buyer filed a chargeback dispute. Seller has until evidenceDeadline to submit evidence or the chargeback resolves in the buyer's favor and the amount is debited from payouts.
When it fires. Stripe sent a charge.dispute.created notification — a buyer filed a chargeback. The seller has until evidenceDeadline to upload evidence in the Stripe Dashboard or the dispute auto-resolves in the buyer's favor and the amount is debited from upcoming payouts.
Example payload (data field):
{
disputeId: "dp_1QABCDEFGHIJKLMNOP",
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
chargeId: "ch_1QABCDEFGHIJKLMNOP",
sellerCommunityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
buyerEmail: "alice@example.com",
orderName: "Summer Rooftop Meetup — General Admission",
disputeAmount: 1500,
currency: "eur",
reason: "fraudulent",
evidenceDeadline: "2026-06-01T23:59:59Z",
stripeDashboardUrl: "https://dashboard.stripe.com/disputes/dp_1QABCDEFGHIJKLMNOP"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
disputeId | string | Stripe dispute id (starts with dp_). | |
saleId | uuid | The Cobuntu sale the dispute relates to. | |
chargeId | string | Stripe charge id (starts with ch_). | |
sellerCommunityId | uuid | Community that received the original sale. | |
buyerEmail | string | Buyer email used at checkout. | |
orderName | string | Pre-formatted product/event title for display. | |
disputeAmount | number | Disputed amount in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
reason | string | Stripe's machine-readable reason code (e.g. fraudulent, product_not_received). | |
evidenceDeadline | iso-8601 | Deadline by which evidence must be submitted. | |
stripeDashboardUrl | url | Deep link to manage the dispute on Stripe Dashboard. |
Related: sale.completed, refund.completed
donation.received — Donation received
A buyer donated to an event or product. Fires for all three donation flows: sidecar (donation alongside a paid ticket / product), free-RSVP-with-donation (free event + tip), and standalone (donate without buying anything).
When it fires. A donation row was successfully recorded after checkout.session.completed. Idempotent on transactionId — webhook re-deliveries do not re-fire. Sidecar donations on paid sales also fire sale.completed for the underlying ticket/product; subscribe to whichever event matches your use case.
Example payload (data field):
{
donationId: "c3a7b1e4-5f6d-4a2c-9b8e-0d1f3a5c7b9d",
transactionId: "7d4e8c1a-9b2f-3a5c-6d7e-1f0a2b4c8d6e",
saleId: "1a5b3c8e-2f4d-6a7c-9b0d-1e3f5c8a4b7d",
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
productId: null,
buyerUserId: "8a4b2c6d-9e1f-3c5d-7b8a-0f2e4d6c1a3b",
buyerEmail: null,
amount: 500,
currency: "EUR",
flow: "sidecar"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
donationId | uuid | Stable donation identifier — the canonical idempotency key for this event. | |
transactionId | uuid | Parent checkout transaction. | |
saleId | uuid | ✓ | The sale this donation attached to. For sidecar donations on paid checkouts, this points at the primary ticket/product sale. For free-RSVP-with-donation and standalone donations, this points at a synthetic sale row that the payout system uses to route the seller's share. |
eventId | uuid | ✓ | Set when the donation parent is an event. Mutually exclusive with productId. |
productId | uuid | ✓ | Set when the donation parent is a marketplace product. Mutually exclusive with eventId. |
buyerUserId | uuid | ✓ | Authenticated donor id; null for guest donations (email-only). |
buyerEmail | string | ✓ | Guest email when the donor checked out without an account; null when the donor was an authenticated member (use buyerUserId). |
amount | number | Donation amount in minor currency units (e.g. 500 = €5.00). | |
currency | string | ISO 4217 currency code. | |
flow | sidecar | free-rsvp-donation | donation-only | Which checkout flow produced this donation. Useful for routing custom thank-you content per flow. |
Notes. Donation revenue routes 100% to the seller minus Stripe's percentage fee — no community commission, no platform fee.
Related: sale.completed, donation.refunded
donation.refunded — Donation refunded
A donation was refunded — either the parent sale of a sidecar donation was refunded (the donation rides with it), or a host explicitly refunded a standalone donation.
When it fires. Same fire moment as refund.completed on the underlying sale, but typed specifically for donations so consumers can subscribe to only the donation half of a multi-line refund. Fires on FULL refunds when the refunded sale carries a donation row.
v1 limitation. PARTIAL refunds don't fire this event yet — today's refund flow can't distinguish ticket vs donation portions. When the admin UI grows line-item-level refund picking, this event will fire for partial donation refunds too.
Example payload (data field):
{
donationId: "c3a7b1e4-5f6d-4a2c-9b8e-0d1f3a5c7b9d",
refundId: "8c2a4e1b-3d5f-4a7c-9b6e-1d8f3a5c7b9e",
saleId: "6b1e3c8f-2d4a-4b7c-9e5f-8a1c3b6d9f2e",
eventId: "9e2d4b7a-1c5f-4a3b-8e6c-2d9f1b4a7c3e",
productId: null,
buyerUserId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
buyerEmail: "alice@example.com",
amount: 500,
currency: "EUR",
refundType: "FULL",
reason: "BUYER_REQUEST"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
donationId | uuid | The donation being refunded — matches donation.received.donationId. | |
refundId | uuid | The underlying refund record id (same as refund.completed.refundId). | |
saleId | uuid | The sale row that carried the donation. | |
eventId | uuid | ✓ | Set when the donation parent was an event. |
productId | uuid | ✓ | Set when the donation parent was a product. |
buyerUserId | uuid | ✓ | Donor user id; null for guest donations. |
buyerEmail | string | ✓ | Donor email. |
amount | number | Refunded donation amount in minor currency units. | |
currency | string | ISO 4217 currency code. | |
refundType | FULL | Always FULL in v1 — partial refunds don't fire this event yet. | |
reason | BUYER_REQUEST | HOST_CANCELLATION | CHARGEBACK | STRIPE_EXTERNAL | Why the refund was issued. STRIPE_EXTERNAL indicates a refund initiated from outside Cobuntu (e.g. directly in the Stripe Dashboard). |
Notes. refund.completed fires the same moment with the parent sale's accounting context. Subscribe to this event when you only care about the donation portion (e.g. to issue a thank-you-for-trying email to a refunded donor).
Related: refund.completed, refund.issued, donation.received
Subscriptions
subscription.activated — Subscription activated
A subscription started — free segment activation OR first paid period after Stripe checkout.
When it fires. A user gained subscription access to a segment — either a free segment was activated for them, or a paid checkout completed and the first billing period started. For paid first-purchase, subscription.purchase_receipt fires alongside this for receipt-style comms.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
segmentName: "Founding Members",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
billingCycle: "YEARLY",
lockedPrice: 9900,
currency: "eur",
communityUrl: "https://my-community.cobuntu.com"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Stable subscription identifier. | |
segmentId | uuid | The segment (tier) the user joined. | |
segmentName | string | Human-readable segment name. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
billingCycle | `ONE_TIME | MONTHLY | YEARLY` |
lockedPrice | number | Price locked in for this subscription in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
communityUrl | url | Community home URL for welcome links. |
Related: subscription.purchase_receipt, subscription.renewed, subscription.cancelled
subscription.renewed — Subscription renewed
A recurring subscription was successfully billed for a new period.
When it fires. Stripe successfully charged a recurring subscription for a new billing period. Fires once per renewal; failures fire subscription.payment_failed instead.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
currentPeriodStart: "2027-05-18T00:00:00Z",
currentPeriodEnd: "2028-05-18T00:00:00Z",
amount: 9900,
tierName: "Founding Members",
nextRenewalDate: "2028-05-18T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Matches the original activation. | |
segmentId | uuid | Segment (tier) being renewed. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
currentPeriodStart | iso-8601 | Start of the new period that was just paid. | |
currentPeriodEnd | iso-8601 | End of the new period. | |
amount | number | Amount charged in minor currency units. | |
tierName | string | Pre-formatted tier name for receipts. | |
nextRenewalDate | iso-8601 | When the next renewal attempt will run. |
Related: subscription.activated, subscription.payment_failed
subscription.payment_failed — Subscription payment failed
A recurring subscription billing failed (Stripe invoice.payment_failed). Buyer keeps access until period end.
When it fires. Stripe failed to charge a recurring subscription (card declined, expired, insufficient funds, etc.). The user keeps access until the current period ends; Stripe retries on its standard cadence. If retries succeed, subscription.renewed fires; if not, subscription.expired eventually fires.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
failureReason: "Your card was declined.",
tierName: "Founding Members",
retryDate: "2027-05-21T00:00:00Z",
updateCardUrl: "https://billing.stripe.com/p/session/test_xyz"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Subscription that failed to bill. | |
segmentId | uuid | Segment (tier) being billed. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
failureReason | string | ✓ | Stripe's failure description for end-user display. |
tierName | string | Pre-formatted tier name. | |
retryDate | iso-8601 | ✓ | When Stripe will retry the charge; null when Stripe has stopped retrying. |
updateCardUrl | url | Stripe Customer Portal link for updating payment method. |
Related: subscription.renewed, subscription.expired
subscription.cancelled — Subscription cancelled
A subscription was cancelled by the user (e.g. via billing portal). Buyer keeps access until period end; expiry fires subscription.expired separately. For admin-initiated cancellations see subscription.cancelled_by_admin.
When it fires. The subscriber cancelled their own subscription — typically via the Stripe Customer Portal or in-app cancel button. The user keeps access until the current period ends; subscription.expired fires at that point.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
cancelledBy: "user",
currentPeriodEnd: "2028-05-18T00:00:00Z",
tierName: "Founding Members",
accessUntilDate: "2028-05-18T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Subscription cancelled. | |
segmentId | uuid | Segment (tier) being cancelled. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
cancelledBy | user | Always user for this event; admin cancellations fire subscription.cancelled_by_admin. | |
currentPeriodEnd | iso-8601 | Effective access end — same as accessUntilDate. | |
tierName | string | Pre-formatted tier name. | |
accessUntilDate | iso-8601 | When the user loses access (period end). |
Related: subscription.cancelled_by_admin, subscription.expired
subscription.cancelled_by_admin — Subscription cancelled by admin
An admin cancelled a member's subscription (vs the user cancelling themselves). Distinct from subscription.cancelled so leaders can apologize / explain / offer reactivation in their cancellation flow.
When it fires. An admin (not the subscriber) cancelled a member's subscription from the admin app. Distinct from subscription.cancelled so admin cancellation flows can apologize, explain, or offer reactivation without the wrong tone.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
segmentName: "Founding Members",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
cancelledBy: "admin",
adminUserId: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
reason: "Member requested cancellation via support.",
currentPeriodEnd: "2028-05-18T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Subscription cancelled. | |
segmentId | uuid | Segment (tier) being cancelled. | |
segmentName | string | Human-readable segment name. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
cancelledBy | admin | Always admin for this event. | |
adminUserId | uuid | The admin who performed the cancellation. | |
reason | string | ✓ | Optional reason recorded by the admin. |
currentPeriodEnd | iso-8601 | ✓ | Period end if access continues until then; null if immediate. |
Related: subscription.cancelled, subscription.expired
subscription.expired — Subscription expired
A subscription's access period ended — natural end-of-period OR forced expiry from a FULL refund.
When it fires. A subscription's access period ended — either the user cancelled and the period naturally elapsed, or a FULL refund was issued and access was revoked. The user no longer has tier access from this point.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
expiredAt: "2028-05-18T00:00:00Z",
reason: "period_end",
tierName: "Founding Members",
resubscribeUrl: "https://my-community.cobuntu.com/membership/founding"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Expired subscription. | |
segmentId | uuid | Segment (tier) access was lost from. | |
userId | uuid | Former subscriber user id. | |
email | string | Former subscriber email. | |
expiredAt | iso-8601 | When access ended. | |
reason | `period_end | refund` | |
tierName | string | Pre-formatted tier name. | |
resubscribeUrl | url | ✓ | Deep link to re-subscribe; null when the tier is no longer for sale. |
Related: subscription.cancelled, subscription.cancelled_by_admin
subscription.purchase_receipt — Subscription purchase receipt
First successful payment on a paid subscription. Distinct from subscription.activated (welcome) so leaders can ship a clean receipt (you paid €X for tier Y, invoice attached) without conflating with onboarding comms. Fires once per subscription on the FIRST charge — recurring renewals fire subscription.renewed instead.
When it fires. Fires once per paid subscription, on the very first successful charge. Use this for receipt-style comms; use subscription.activated for welcome/onboarding (which fires at the same dispatch tick).
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
segmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
segmentName: "Founding Members",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
amount: 9900,
currency: "eur",
billingCycle: "YEARLY",
invoiceUrl: "https://invoice.stripe.com/i/abc123",
paidAt: "2026-05-18T09:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Subscription being receipted. | |
segmentId | uuid | Segment (tier) joined. | |
segmentName | string | Human-readable segment name. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
amount | number | First-charge amount in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
billingCycle | `MONTHLY | YEARLY | ONE_TIME` |
invoiceUrl | url | ✓ | Stripe-hosted invoice URL for download. |
paidAt | iso-8601 | When the charge cleared. |
Related: subscription.activated, subscription.renewed
tier.changed — Tier changed
A member moved between tiers — upgrade or downgrade. Distinct from a fresh subscription.activated so leaders can wire dedicated tier-change comms (price-diff explanation, prorate notice).
When it fires. An existing subscriber moved between tiers — upgrade, downgrade, or sideways. Distinct from subscription.activated so tier-change comms (proration explanation, congrats / sorry-to-see-you-drop messaging) stay clean.
Example payload (data field):
{
subscriptionId: "1f3a5c7e-9b2d-4e6c-8a4d-7f2b6c9a3e5d",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
fromSegmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
fromSegmentName: "Supporter",
toSegmentId: "c6f2e8d1-3b5a-4d7e-9f1c-8a4b2d6f3e9c",
toSegmentName: "Founding Members",
direction: "upgrade",
lockedPrice: 9900,
currency: "eur"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
subscriptionId | uuid | Subscription whose tier changed. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
fromSegmentId | uuid | Previous segment (tier). | |
fromSegmentName | string | Previous tier name. | |
toSegmentId | uuid | New segment (tier). | |
toSegmentName | string | New tier name. | |
direction | `upgrade | downgrade | lateral` |
lockedPrice | number | Locked-in price on the new tier (minor currency units). | |
currency | string | ISO 4217 currency code (lowercase). |
Related: subscription.activated
member.discount.ended — Member discount ended
Member-tier-scoped pricing on a product subscription lapsed because the buyer's segment membership ended. The base price now applies.
When it fires. A buyer was paying a member-tier-discounted price on a product subscription. Their segment membership ended (cancellation, expiry, kick) — the discount is no longer valid, so the next renewal will be at the base price. Fires once per affected product subscription at lapse time. Use this to warn the buyer their effective price is about to go up, or to offer a re-enrol path.
Example payload (data field):
{
productSubscriptionId: "a7b3c5d9-1e2f-4a6c-8b0d-3f5e7a9c1b2d",
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
productId: "9c1d5b8e-4a2f-6e3d-8b7c-1f0a2d4e6c9b",
productName: "Weekly mentor sessions",
endedSegmentId: "7b3a9c1e-2d4f-4a8b-9e0c-1f5a3b7d9c2e",
endedSegmentName: "Founding Members",
previousPrice: 2000,
basePrice: 3500,
currency: "eur",
effectiveFrom: "2026-06-15T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
productSubscriptionId | uuid | Affected product subscription identifier. | |
userId | uuid | Subscriber user id. | |
email | string | Subscriber email. | |
productId | uuid | Product the subscription is for. | |
productName | string | Product name (snapshot). | |
endedSegmentId | uuid | Segment membership that lapsed and removed the discount. | |
endedSegmentName | string | Lapsed segment name. | |
previousPrice | number | Discounted price the buyer was paying (minor currency units). | |
basePrice | number | Base product price that will apply at next renewal. | |
currency | string | ISO 4217 currency code (lowercase). | |
effectiveFrom | iso-8601 | When the new base price takes effect (typically next renewal date). |
Related: subscription.renewed, tier.changed
Payouts & finance
payout.confirmed — Payout confirmed
A scheduled payout settled successfully to the connected account's bank.
When it fires. Stripe confirmed a scheduled payout settled to the connected community's bank account. The funds are out of Stripe and on their way (or already arrived) at the bank.
Example payload (data field):
{
payoutId: "4d6c8a2e-3f5b-4e7c-9a1d-8b3f5c9a7e2d",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
recipientUserId: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
recipientEmail: "founder@example.com",
amount: 60240,
currency: "eur",
stripeAccountId: "acct_1OABCDEFGHIJKLM",
arrivalDate: "2026-05-22T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
payoutId | uuid | Cobuntu payout identifier. | |
communityId | uuid | Community whose Stripe account received the payout. | |
recipientUserId | uuid | ✓ | User id of the Stripe account owner; null for community-owned accounts. |
recipientEmail | string | Email used on the connected Stripe account. | |
amount | number | Payout amount in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
stripeAccountId | string | Stripe Connect account id (starts with acct_). | |
arrivalDate | iso-8601 | Expected arrival date at the bank — Stripe's estimate. |
Related: payout.failed, commission.held
payout.failed — Payout failed
A scheduled payout failed to settle (insufficient balance, account restricted, bank rejection).
When it fires. A scheduled payout did not settle — insufficient balance, account restricted, bank rejection, or wire failure. Surface to the recipient with the reason and prompt for a fix.
Example payload (data field):
{
payoutId: "4d6c8a2e-3f5b-4e7c-9a1d-8b3f5c9a7e2d",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
recipientUserId: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
recipientEmail: "founder@example.com",
amount: 60240,
currency: "eur",
stripeAccountId: "acct_1OABCDEFGHIJKLM",
failureReason: "Bank account closed.",
failureCode: "account_closed"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
payoutId | uuid | Payout identifier. | |
communityId | uuid | Community whose payout failed. | |
recipientUserId | uuid | ✓ | Account owner user id; null for community-owned accounts. |
recipientEmail | string | Account owner email. | |
amount | number | Attempted payout amount in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
stripeAccountId | string | Stripe Connect account id. | |
failureReason | string | Human-readable failure description from Stripe. | |
failureCode | string | ✓ | Stripe machine-readable failure code (e.g. account_closed); null when Stripe didn't provide one. |
Related: payout.confirmed
commission.held — Commission held
A commission was held back from a payout (escrow, dispute reserve, refund offset).
When it fires. Cobuntu held back commission from a community's payout — typical reasons are escrow until event delivery, dispute reserve held during a chargeback investigation, or refund offset against prior sales.
Example payload (data field):
{
commissionId: "9a1c3e5b-2d4f-4a7c-8e6b-1f5a3c7d9b2e",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
recipientUserId: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
recipientEmail: "founder@example.com",
amount: 5000,
currency: "eur",
holdReason: "ESCROW",
expectedReleaseDate: "2026-05-25T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
commissionId | uuid | ✓ | Commission identifier when backed by a commissions_held row; null when the event represents an intended-but-skipped payout (e.g. community has no Stripe Connect account configured) with no DB-backed hold entity yet. |
communityId | uuid | Community whose commission is held. | |
recipientUserId | uuid | ✓ | Recipient user id; null for community-owned accounts. |
recipientEmail | string | Recipient email. | |
amount | number | Held amount in minor currency units. | |
currency | string | ISO 4217 currency code (lowercase). | |
holdReason | ESCROW | DISPUTE_RESERVE | REFUND_OFFSET | NO_STRIPE_ACCOUNT | OTHER | Why the commission is held. NO_STRIPE_ACCOUNT fires when payout was skipped because the community hasn't connected Stripe. | |
expectedReleaseDate | iso-8601 | ✓ | When the hold is expected to release; null when indeterminate (e.g. requires admin action to set up Stripe). |
Related: payout.confirmed, dispute.created
stripe.account.requires_verification — Stripe verification required
Stripe Connect account needs additional verification or documents — leader risks losing payouts if not addressed by the deadline.
When it fires. Stripe flagged the connected account as needing additional verification — documents, ownership, or business info. If not addressed by the deadline, Stripe may pause charges or payouts. Time-sensitive — surface in-product within minutes.
Example payload (data field):
{
stripeAccountId: "acct_1OABCDEFGHIJKLM",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
ownerEmail: "founder@acme.example.com",
requirements: ["individual.id_number", "individual.verification.document"],
currentDeadline: "2026-06-01T23:59:59Z",
verificationUrl: "https://connect.stripe.com/express/onboarding/acct_1OABCDEFGHIJKLM"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
stripeAccountId | string | Stripe Connect account id (starts with acct_). | |
communityId | uuid | Community owning the connected account. | |
ownerEmail | string | Account owner email. | |
requirements | string[] | Stripe requirement keys (e.g. individual.id_number) needing action. | |
currentDeadline | iso-8601 | ✓ | Deadline before Stripe restricts the account; null when no deadline yet. |
verificationUrl | url | Deep link to Stripe's hosted onboarding to complete verification. |
Related: stripe.account.payouts_disabled
stripe.account.payouts_disabled — Stripe payouts disabled
Stripe disabled payouts on the connected account. Sales still complete; funds accumulate until the account is restored.
When it fires. Stripe disabled payouts on the connected account — typically a follow-on of stripe.account.requires_verification going past deadline, or a Stripe risk-team action. Sales can still complete; funds accumulate in the Stripe balance until the account is restored.
Example payload (data field):
{
stripeAccountId: "acct_1OABCDEFGHIJKLM",
communityId: "5ea4e9c6-d3f1-4b2e-8a7d-9c1e4f6a2b8d",
ownerEmail: "founder@acme.example.com",
reason: "verification_overdue",
currentlyDueRequirements: ["individual.verification.document"]
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
stripeAccountId | string | Stripe Connect account id. | |
communityId | uuid | Community owning the account. | |
ownerEmail | string | Account owner email. | |
reason | string | Stripe-provided reason payouts were disabled. | |
currentlyDueRequirements | string[] | Outstanding requirement keys blocking payouts. |
Related: stripe.account.requires_verification, payout.failed
Communications
broadcast.sent — Broadcast sent
A mass email broadcast (newsletter campaign) finished sending to its recipient list. Useful for tracking send velocity in third-party analytics + triggering follow-up automations.
When it fires. A newsletter broadcast finished dispatching to its full recipient list. Fires once per campaign at end-of-send, regardless of per-recipient delivery outcomes.
Example payload (data field):
{
campaignId: "5d9c3a7e-1b4f-4d2c-8a6b-2f9e5c7d3a1b",
subject: "Weekly digest — 18 May",
audience: "segment",
recipientCount: 1247,
senderUserId: "8d3c5a9e-1f4b-4c2d-9a7e-3b5d8f1c6e2a",
sentAt: "2026-05-18T09:00:00Z",
source: "admin_ui"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
campaignId | uuid | Campaign identifier. | |
subject | string | Email subject line sent. | |
audience | `all | segment | json-config` |
recipientCount | number | Number of recipients the broadcast was sent to. | |
senderUserId | uuid | ✓ | Admin user who initiated the send; null for API-triggered sends. |
sentAt | iso-8601 | When the broadcast finished sending. | |
source | `admin_ui | public_api` |
digest.scheduled — Engagement digest scheduled
Fires when the scheduled engagement-digest cron triggers a send. Per-recipient, so flows can filter on member preferences.
When it fires. The engagement-digest cron fires per-recipient on the configured cadence (daily or weekly). Each delivery dispatches its own event so leaders can filter on member preferences or skip dates.
Example payload (data field):
{
userId: "2c8f4b1d-9a5e-4c3b-8d7f-1e6a9b2c5d4f",
email: "alice@example.com",
cadence: "WEEKLY",
windowStart: "2026-05-11T00:00:00Z",
windowEnd: "2026-05-18T00:00:00Z"
}Payload schema:
| Field | Type | Nullable | Description |
|---|---|---|---|
userId | uuid | Recipient user id. | |
email | string | Recipient email. | |
cadence | `DAILY | WEEKLY` | |
windowStart | iso-8601 | Start of the activity window the digest summarizes. | |
windowEnd | iso-8601 | End of the activity window. |
Source of truth
This page is the canonical reference for every event Cobuntu emits. Any drift between the documented payload and what your receiver sees on the wire is a bug — file an issue and we'll fix it.
The same data is also surfaced inside cobuntu-admin → Integrations →
Documentation (per-community, searchable). The public OpenAPI spec
at api.cobuntu.com/openapi.json ships in the next docs release.