Webhooks
Event catalog

Event catalog

Cobuntu emits 36 webhook events across 8 domains. Subscribe to whichever ones you need in admin → Integrations → Webhooks.

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

DomainEventsJump
Memberships8member.requested, member.invited, member.approved, member.rejected, form.submitted, member.kicked, member.banned, application.withdrawn
Articles2article.published, article.deleted
Events2event.published, event.cancelled
Marketplace (listings)5listing.requested, listing.approved, listing.rejected, listing.deactivated, listing.withdrawn
Sales (orders + refunds)6sale.completed, refund.completed, refund.issued, product.purchased, product.refunded, dispute.created
Subscriptions8subscription.activated, subscription.renewed, subscription.payment_failed, subscription.cancelled, subscription.cancelled_by_admin, subscription.expired, subscription.purchase_receipt, tier.changed
Payouts & finance3payout.confirmed, payout.failed, commission.held
Communications2broadcast.sent, digest.scheduled

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:

FieldTypeNullableDescription
requestIduuidStable identifier for this membership request.
segmentIduuidSegment the request targets; null when applying without a specific tier.
segmentNamestringHuman-readable segment name, mirrors segmentId.
emailstringEmail submitted on the form.
namestringDisplay name; null when the form didn't collect one.
sourcestringWhere the request came from — e.g. tally, jotform, landing_page, csv_import, admin_added.
answersobjectRaw 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://orbis.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:

FieldTypeNullableDescription
emailstringInvitee's email address.
inviterNamestringDisplay name of the admin who sent the invitation.
inviterAvatarstringProfile-image URL of the inviter; null when no avatar set.
customMessagestringOptional personal message the admin attached to the invitation.
acceptUrlstringTokenised URL the invitee opens to accept. Points at the community subdomain's /join (or admin.cobuntu.com/join for role-gated invites).
segmentNamestringHuman-readable name of the tier the invite targets; null when no tier was specified.
segmentIduuidTier (segment) the invite targets; null when none was specified.
communityDescriptionstringShort 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:

FieldTypeNullableDescription
requestIduuidMatches the requestId from the original member.requested event.
emailstringApproved member's email.
namestringDisplay 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:

FieldTypeNullableDescription
requestIduuidMatches the requestId from the original member.requested event.
emailstringRejected applicant's email.
namestringDisplay 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:

FieldTypeNullableDescription
segmentIduuidSegment the form was scoped to; null when unscoped.
segmentNamestringHuman-readable segment name.
emailstringSubmitted email.
namestringDisplay name.
sourcestringForm provider — tally, jotform, landing_page.
answersobjectRaw 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:

FieldTypeNullableDescription
userIduuidThe kicked member's user id.
usertagstringTheir @usertag (handle).
emailstringEmail at the time of kick.
namestringDisplay name; null when unknown.
kickedByuuidThe 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:

FieldTypeNullableDescription
userIduuidThe banned member's user id.
usertagstringTheir @usertag (handle).
emailstringEmail at the time of ban.
namestringDisplay name; null when unknown.
bannedByuuidThe admin user id who issued the ban.
reasonstringFree-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:

FieldTypeNullableDescription
requestIduuidMatches the original member.requested event.
userIduuidUser id if the applicant had an account; null for anonymous form submissions.
emailstringApplicant email.
namestringDisplay name; null when unknown.
segmentIduuidSegment 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:

FieldTypeNullableDescription
iduuidStable article identifier.
titlestringArticle title at the time of publish.
slugstringURL-safe slug used in the article's public URL.
excerptstringShort summary intended for cards and previews.
bannerUrlurlHero image; null when no banner was uploaded.
categorystringEditorial category; null when uncategorized.
publishedAtiso-8601When 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:

FieldTypeNullableDescription
iduuidThe article's stable identifier — useful for cleaning up external mirrors.
titlestringArticle title at the time of deletion (snapshot for logging).
slugstringSlug 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:

FieldTypeNullableDescription
eventIduuidStable event identifier.
slugstringURL-safe slug used in the event's public URL.
namestringEvent title.
startDateiso-8601Event start; null for date-less events.
endDateiso-8601Event end; null when not set or when same-day with implicit length.
accessibility`PUBLICMEMBERS_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:

FieldTypeNullableDescription
eventIduuidMatches the eventId from event.published.
slugstringSlug that was in use.
namestringEvent title.
action`unpublisheddeleted`
attendeeCountnumberNumber of registered attendees at cancellation time — useful for sizing follow-up comms.

Related: event.published, refund.completed


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:

FieldTypeNullableDescription
listingIduuidStable listing identifier.
communityIduuidCommunity the listing was submitted to.
sellerUserIduuidSeller user id.
sellerEmailstringSeller email.
titlestringSubmitted listing title.
listingType`PRODUCTSERVICE`
pricenumberListed price in minor currency units.
currencystringISO 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:

FieldTypeNullableDescription
listingIduuidListing identifier.
communityIduuidCommunity that approved.
sellerUserIduuidSeller user id.
sellerEmailstringSeller email.
titlestringListing title.
reviewedByuuidAdmin 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:

FieldTypeNullableDescription
listingIduuidListing identifier.
communityIduuidCommunity that rejected.
sellerUserIduuidSeller user id.
sellerEmailstringSeller email.
titlestringListing title.
reviewedByuuidAdmin user id who rejected.
reasonstringOptional 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:

FieldTypeNullableDescription
listingIduuidDeactivated listing.
communityIduuidCommunity owning the listing.
sellerUserIduuidSeller user id.
sellerEmailstringSeller email.
titlestringListing title.
reasonstringOptional 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:

FieldTypeNullableDescription
listingIduuidWithdrawn listing.
communityIduuidCommunity owning the listing.
sellerUserIduuidSeller user id.
sellerEmailstringSeller email.
titlestringListing 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:

FieldTypeNullableDescription
saleIduuidStable sale identifier.
transactionIduuidStripe-side transaction reference stored in Cobuntu.
eventIduuidSet when the sale was for an event ticket.
productSnapshotIduuidSet when the sale was for a marketplace product (snapshot of price/title at purchase).
buyerUserIduuidBuyer user id; null for magic-link guest purchases.
buyerEmailstringBuyer email used at checkout.
grossAmountnumberGross amount in minor currency units (e.g. 1500 = €15.00).
currencystringISO 4217 currency code in lowercase (e.g. eur, usd).
productType`EVENTDIGITALSUBSCRIPTION`

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. The [System] Refund processed — seller flow installed by M4 listens on this event.

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:

FieldTypeNullableDescription
refundIduuidStable identifier for the refund.
saleIduuidThe sale being refunded — matches sale.completed.saleId.
eventIduuidSet when the underlying sale was for an event ticket.
buyerUserIduuidBuyer user id; null for guest purchases.
buyerEmailstringBuyer email.
amountnumberRefund amount in minor currency units.
refundType`FULLPARTIAL`
reason`BUYER_REQUESTHOST_CANCELLATIONCHARGEBACK`

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:

FieldTypeNullableDescription
refundIduuidMatches refund.completed.refundId.
saleIduuidThe sale being refunded.
buyerUserIduuidBuyer user id; null for guest purchases.
buyerEmailstringBuyer email.
amountnumberRefund amount in minor currency units.
refundType`FULLPARTIAL`
reason`BUYER_REQUESTHOST_CANCELLATIONCHARGEBACK`
productNamestringPre-formatted product/event title for the buyer-facing email.

Related: refund.completed

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:

FieldTypeNullableDescription
saleIduuidUnderlying sale id.
transactionIduuidStripe-side transaction reference.
productIduuidProduct id (canonical); null if the product was deleted before this event was processed.
productSnapshotIduuidSnapshot at purchase time — use this for invoices since the canonical product may change.
productNamestringProduct title at purchase time.
buyerUserIduuidBuyer user id; null for guest purchases.
buyerEmailstringBuyer email.
buyerNamestringBuyer display name.
grossAmountnumberPrice in minor currency units.
currencystringISO 4217 currency code (lowercase).
productType`DIGITALEVENT`

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:

FieldTypeNullableDescription
refundIduuidStable refund identifier.
saleIduuidOriginal sale being refunded.
productIduuidProduct id; null if deleted.
productNamestringProduct title (snapshot).
buyerUserIduuidBuyer user id; null for guest purchases.
buyerEmailstringBuyer email.
amountnumberRefund amount in minor currency units.
refundType`FULLPARTIAL`
reason`BUYER_REQUESTHOST_CANCELLATIONCHARGEBACK`

Related: product.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:

FieldTypeNullableDescription
disputeIdstringStripe dispute id (starts with dp_).
saleIduuidThe Cobuntu sale the dispute relates to.
chargeIdstringStripe charge id (starts with ch_).
sellerCommunityIduuidCommunity that received the original sale.
buyerEmailstringBuyer email used at checkout.
orderNamestringPre-formatted product/event title for display.
disputeAmountnumberDisputed amount in minor currency units.
currencystringISO 4217 currency code (lowercase).
reasonstringStripe's machine-readable reason code (e.g. fraudulent, product_not_received).
evidenceDeadlineiso-8601Deadline by which evidence must be submitted.
stripeDashboardUrlurlDeep link to manage the dispute on Stripe Dashboard.

Related: sale.completed, refund.completed


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://orbis.cobuntu.com"
}

Payload schema:

FieldTypeNullableDescription
subscriptionIduuidStable subscription identifier.
segmentIduuidThe segment (tier) the user joined.
segmentNamestringHuman-readable segment name.
userIduuidSubscriber user id.
emailstringSubscriber email.
billingCycle`ONE_TIMEMONTHLYYEARLY`
lockedPricenumberPrice locked in for this subscription in minor currency units.
currencystringISO 4217 currency code (lowercase).
communityUrlurlCommunity 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:

FieldTypeNullableDescription
subscriptionIduuidMatches the original activation.
segmentIduuidSegment (tier) being renewed.
userIduuidSubscriber user id.
emailstringSubscriber email.
currentPeriodStartiso-8601Start of the new period that was just paid.
currentPeriodEndiso-8601End of the new period.
amountnumberAmount charged in minor currency units.
tierNamestringPre-formatted tier name for receipts.
nextRenewalDateiso-8601When 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:

FieldTypeNullableDescription
subscriptionIduuidSubscription that failed to bill.
segmentIduuidSegment (tier) being billed.
userIduuidSubscriber user id.
emailstringSubscriber email.
failureReasonstringStripe's failure description for end-user display.
tierNamestringPre-formatted tier name.
retryDateiso-8601When Stripe will retry the charge; null when Stripe has stopped retrying.
updateCardUrlurlStripe 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:

FieldTypeNullableDescription
subscriptionIduuidSubscription cancelled.
segmentIduuidSegment (tier) being cancelled.
userIduuidSubscriber user id.
emailstringSubscriber email.
cancelledByuserAlways user for this event; admin cancellations fire subscription.cancelled_by_admin.
currentPeriodEndiso-8601Effective access end — same as accessUntilDate.
tierNamestringPre-formatted tier name.
accessUntilDateiso-8601When 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:

FieldTypeNullableDescription
subscriptionIduuidSubscription cancelled.
segmentIduuidSegment (tier) being cancelled.
segmentNamestringHuman-readable segment name.
userIduuidSubscriber user id.
emailstringSubscriber email.
cancelledByadminAlways admin for this event.
adminUserIduuidThe admin who performed the cancellation.
reasonstringOptional reason recorded by the admin.
currentPeriodEndiso-8601Period 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://orbis.cobuntu.com/membership/founding"
}

Payload schema:

FieldTypeNullableDescription
subscriptionIduuidExpired subscription.
segmentIduuidSegment (tier) access was lost from.
userIduuidFormer subscriber user id.
emailstringFormer subscriber email.
expiredAtiso-8601When access ended.
reason`period_endrefund`
tierNamestringPre-formatted tier name.
resubscribeUrlurlDeep 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:

FieldTypeNullableDescription
subscriptionIduuidSubscription being receipted.
segmentIduuidSegment (tier) joined.
segmentNamestringHuman-readable segment name.
userIduuidSubscriber user id.
emailstringSubscriber email.
amountnumberFirst-charge amount in minor currency units.
currencystringISO 4217 currency code (lowercase).
billingCycle`MONTHLYYEARLYONE_TIME`
invoiceUrlurlStripe-hosted invoice URL for download.
paidAtiso-8601When 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:

FieldTypeNullableDescription
subscriptionIduuidSubscription whose tier changed.
userIduuidSubscriber user id.
emailstringSubscriber email.
fromSegmentIduuidPrevious segment (tier).
fromSegmentNamestringPrevious tier name.
toSegmentIduuidNew segment (tier).
toSegmentNamestringNew tier name.
direction`upgradedowngradelateral`
lockedPricenumberLocked-in price on the new tier (minor currency units).
currencystringISO 4217 currency code (lowercase).

Related: subscription.activated


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@orbis.example.com",
  amount: 60240,
  currency: "eur",
  stripeAccountId: "acct_1OABCDEFGHIJKLM",
  arrivalDate: "2026-05-22T00:00:00Z"
}

Payload schema:

FieldTypeNullableDescription
payoutIduuidCobuntu payout identifier.
communityIduuidCommunity whose Stripe account received the payout.
recipientUserIduuidUser id of the Stripe account owner; null for community-owned accounts.
recipientEmailstringEmail used on the connected Stripe account.
amountnumberPayout amount in minor currency units.
currencystringISO 4217 currency code (lowercase).
stripeAccountIdstringStripe Connect account id (starts with acct_).
arrivalDateiso-8601Expected 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@orbis.example.com",
  amount: 60240,
  currency: "eur",
  stripeAccountId: "acct_1OABCDEFGHIJKLM",
  failureReason: "Bank account closed.",
  failureCode: "account_closed"
}

Payload schema:

FieldTypeNullableDescription
payoutIduuidPayout identifier.
communityIduuidCommunity whose payout failed.
recipientUserIduuidAccount owner user id; null for community-owned accounts.
recipientEmailstringAccount owner email.
amountnumberAttempted payout amount in minor currency units.
currencystringISO 4217 currency code (lowercase).
stripeAccountIdstringStripe Connect account id.
failureReasonstringHuman-readable failure description from Stripe.
failureCodestringStripe 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@orbis.example.com",
  amount: 5000,
  currency: "eur",
  holdReason: "ESCROW",
  expectedReleaseDate: "2026-05-25T00:00:00Z"
}

Payload schema:

FieldTypeNullableDescription
commissionIduuidCommission identifier.
communityIduuidCommunity whose commission is held.
recipientUserIduuidRecipient user id; null for community-owned accounts.
recipientEmailstringRecipient email.
amountnumberHeld amount in minor currency units.
currencystringISO 4217 currency code (lowercase).
holdReason`ESCROWDISPUTE_RESERVEREFUND_OFFSET
expectedReleaseDateiso-8601When the hold is expected to release; null when indeterminate.

Related: payout.confirmed, dispute.created


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:

FieldTypeNullableDescription
campaignIduuidCampaign identifier.
subjectstringEmail subject line sent.
audience`allsegmentjson-config`
recipientCountnumberNumber of recipients the broadcast was sent to.
senderUserIduuidAdmin user who initiated the send; null for API-triggered sends.
sentAtiso-8601When the broadcast finished sending.
source`admin_uipublic_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. Replaces the legacy direct-fire path in DigestService once M10 migrates the cron.

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:

FieldTypeNullableDescription
userIduuidRecipient user id.
emailstringRecipient email.
cadence`DAILYWEEKLY`
windowStartiso-8601Start of the activity window the digest summarizes.
windowEndiso-8601End of the activity window.

Source of truth

This page is generated from services/core/src/shared/lifecycle/lifecycleCatalog.ts (opens in a new tab) in the backend monorepo. Any drift is a bug — file an issue if you spot one.

The same data also renders inside cobuntu-admin → Integrations → Documentation (per-community, searchable). The public OpenAPI spec at api.cobuntu.com/openapi.json ships in the next docs release.