Skip to content

Stripe

How Offers become Stripe objects, which side owns each money record, and how Stripe's webhooks reach your code as Events.

Each Startup sells through its own Stripe connected account. Your code never calls Stripe: Offers compile to Stripe objects, Verbs move the money, and Stripe's webhooks arrive as Events.

What an Offer compiles to

leaseline.app sells one plan at $29 a month:

offers/pro.mdx
---
price: { amount: 29, currency: USD }
interval: month
---

On merge, the SalesBuilder writes a Stripe product for the Offer and a Stripe price for its Price. The first checkout writes a Stripe customer for the Customer. The first charge opens the connected account and hands onboarding to the owners as a Task.

A new Price mints a new Stripe price. Each Subscription on the old one keeps it until it switches. Merchant account covers onboarding and who carries the losses.

A refund is one Verb. The platform turns it into a Stripe refund on the original charge:

Text
Refund $29 on order_31. The renewal notice went out late.

Every mapped Noun

Each Noun Stripe holds, the Stripe object it maps to, and which side is the source of truth. Where db.sb leads, the platform writes Stripe. Where Stripe leads, db.sb mirrors it, written only from Stripe's webhooks, and the record reconciles to the Ledger.

NounStripe objectWhenSource of truth
AccountTreasury FinancialAccount (treasury.financial_account)heldAt is treasuryStripe
CardIssuing Card (issuing.card)Alwaysdb.sb
CustomerCustomer (customer)Alwaysdb.sb
DisputeDispute (dispute)kind is chargebackStripe
InvoiceInvoice (invoice)Alwaysdb.sb
OfferProduct (product)Alwaysdb.sb
PricePrice (price)Alwaysdb.sb
SubscriptionSubscription (subscription)Alwaysdb.sb
TransactionPaymentIntent (payment_intent)kind is chargeStripe
TransactionPayout (payout)kind is payoutStripe
TransactionRefund (refund)kind is refundStripe

Each Noun page lists its field-by-field mapping under Stripe.

Webhooks arrive as Events

The platform receives every Stripe webhook for every connected account, verifies its signature, updates the mirrored record and emits one Event: Order.paid, Subscription.renewed, Order.refunded. Your code subscribes to those Events, never to Stripe, so a retried or reordered webhook never reaches it twice.

To test a receiver locally, stream the Event to it, then fire one in test:

Terminal
npx startup-builder listen Order.paid --forward-to http://localhost:3000/hooks
npx startup-builder trigger Order.paid

Webhooks covers signing, retries and replay.

Names and casing

The API, the SDK and the records are camelCase, and Stripe's snake_case names appear only in the mapping tables. Pass billingEmail, never email, and taxIds, never tax_ids. A Stripe field with no Noun property stays on Stripe's side, and no Verb accepts it.

Test and live

The test environment runs on Stripe's test keys, so test cards, test clocks and declines behave as Stripe documents them. 4242 4242 4242 4242 succeeds, 4000 0000 0000 0002 declines, and errors and testing lists every magic value.

Live charges start once the owners finish onboarding. Stripe's own guides cover the rail underneath: connected accounts, billing and testing.