Skip to content

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.

StepWhat happensIf it fails
VerifyThe sender's signature and timestamp are checked against the secret the platform holdsRefused with 401; it never becomes an Event
DeduplicateThe sender's delivery id is looked upSeen before: acknowledged, nothing written
WriteThe payload becomes the record it describes and that record's Event; the raw body is kept beside itNot acknowledged, so the sender retries
Acknowledge200 goes back only after the Event is writtenNone

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 sendsArrives asThen
payment_intent.succeededOrder.paidBooks wakes on the first one
invoice.paidInvoice.collectedThe Subscription it bills turns active
invoice.payment_failedInvoice.overdue, with its attemptBilling wakes, and Dunning tells the Customer
charge.dispute.createdDispute.openedAnswer a chargeback starts
payout.paidTransaction.settledThe Transaction gets its settledAt
outbound_payment.failedTransaction.failedThe 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:

TypeScript
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 sendsArrives asThen
push to a branch, when the repository is git-firstChange.created on that branchChange verdict runs its checks
issues, openedIssue.created, reported by a Contact matched by emailIt joins triage with every other Issue
issue_comment, createdComment.created on that IssueThe Issue's owning Role sees it
An edit to a mirrored fieldIssue.updated from that ContactThe 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://api.sb/<namespace>/integrations/<integration>/inbound. 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:

Terminal
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.