Webhooks
Stripe, GitHub and any connected service push into a Startup; each delivery is checked, written once as an Event, and starts Workflows.
Stripe, GitHub and every other connected service push their news into a Startup as webhooks. Each delivery is checked, written once as an Event, and starts every Workflow whose Trigger names it.
From delivery to Action
Every delivery takes the same four steps before any Workflow acts.
| Step | What happens | If it fails |
|---|---|---|
| Verify | The sender's signature and timestamp are checked against the secret the platform holds | Refused with 401; it never becomes an Event |
| Deduplicate | The sender's delivery id is looked up | Seen before: acknowledged, nothing written |
| Write | The payload becomes the record it describes and that record's Event; the raw body is kept beside it | Not acknowledged, so the sender retries |
| Acknowledge | 200 goes back only after the Event is written | None |
Then each Trigger that names the Event starts one Action. One Event never starts the same Workflow twice.
Stripe
Every Startup runs its money on its own Stripe connected account, so there is no endpoint to register and no secret to store. Stripe's events reach the platform, which routes each one to its Startup. The left column uses Stripe's own names.
| Stripe sends | Arrives as | Then |
|---|---|---|
payment_intent.succeeded | Order.paid | Books wakes on the first one |
invoice.paid | Invoice.collected | The Subscription it bills turns active |
invoice.payment_failed | Invoice.overdue, with its attempt | Billing wakes, and Dunning tells the Customer |
charge.dispute.created | Dispute.opened | Answer a chargeback starts |
payout.paid | Transaction.settled | The Transaction gets its settledAt |
outbound_payment.failed | Transaction.failed | The Bill it was paying reopens |
Add a Workflow of your own on any of them. This one tells the founder about every chargeback the moment it opens:
on.Dispute.opened(async (dispute, $) =>
$.Message.create({ medium: 'email', direction: 'outbound', to: 'founder', body: `Chargeback opened: ${dispute.reason}, ${dispute.held.amount} ${dispute.held.currency} held` }),
)GitHub
Connect GitHub once with Integration.connect and pick the repositories. The platform installs its GitHub app there and holds the signing secret; GitHub signs each delivery with X-Hub-Signature-256.
| GitHub sends | Arrives as | Then |
|---|---|---|
push to a branch, when the repository is git-first | Change.created on that branch | Change verdict runs its checks |
issues, opened | Issue.created, reported by a Contact matched by email | It joins triage with every other Issue |
issue_comment, created | Comment.created on that Issue | The Issue's owning Role sees it |
| An edit to a mirrored field | Issue.updated from that Contact | The Issue's Permissions decide whether it lands |
A db-first repository is a mirror: its pushes are refused, because the record lives in db.sb. GitHub covers both directions.
Any other service
Connect it as an Integration, and it gets one inbound URL: https:. It signs each delivery with webhook-id, webhook-timestamp and webhook-signature headers, or with the scheme its Integration names.
A Mapping in mappings/ turns its payload into a Verb on your own Nouns. A form tool's submission becomes Lead.create, so Answer inbound starts on Lead.created. A payload no Verb can take goes to a route in startup.ts, with the reason recorded.
Retries, order and testing
The ingress acknowledges only after the write, so a failure on the platform's side becomes a retry on the sender's. Stripe retries for three days. GitHub never retries on its own, so the platform lists GitHub's failed deliveries every hour and redelivers each one.
Deliveries can arrive out of turn. Before writing a Stripe-led record, the ingress reads Stripe's current object, so a late delivery never rolls a record back. Once the Event is written, each Action's failed steps follow their own rules.
To test a Workflow without waiting for Stripe, produce the Event in test and see it arrive:
npx startup-builder trigger Dispute.opened
npx startup-builder listen 'Dispute.*'Triggers lists every way a Workflow starts. To send Events out to your own URL, see outbound webhooks.