Engineering

One webhook schema. Six SDKs that never drift.

Hand-maintained clients drift the week you ship. Generate all six from one schema file, and let CI fail the pull request that forgets one.

Quentin Mousset2 min read
A schema.repost box lists id String, amount Float and currency String, with currency circled as the field just added. Six dashed arrows fan out to six generated clients: typescript, go, java, kotlin and csharp each carry a check mark, while python carries a coral cross annotated still on the old shape. A brace gathers all six toward repost schema generate --check, where the note reads build fails here, not in production.

You declare an order.created event once, and ship. A quarter later the TypeScript client carries a currency field, the Python one still doesn't, and the docs your customers integrate against describe a third shape. Nobody wrote bad code. Everybody edited their own copy of the same contract.

A contract lives in four places at once: the publisher's types, every consumer SDK, the bytes on the wire, and the docs page a customer reads before writing their handler. Change a field and you owe all four an edit, by hand. Miss one and nothing breaks loudly, which is why you hear about it from a customer. More discipline won't fix that. Removing the copies will.

One file, six clients#

The schema is small enough to learn in a sitting. A model describes a payload, a type names your events, an event ties the two together, and a generator picks a language to emit.

One source of truthrepost/schema.repost
generator api {
  language = "typescript"
}

generator payments {
  language = "go"
  output   = "../services/payments/internal/repostclient"
}

type Order {
  /// An order was created
  created
}

model Order {
  id     String
  amount Float
}

event OrderCreated {
  type      @type(Order.created)
  data      Order
  timestamp DateTime
}
ts

repost schema generate runs every generator, and the write is all-or-nothing: if one output fails, the ones already written roll back. Two services can't come out of the same run on different versions of the schema.

Each language gets a native client: typed models, one method per event type, and a compile error the moment you misspell a field or pass a string where the schema says Float.

Every client reads the same declarations, so fields come out in the same order everywhere. Six languages, one set of bytes. Which matters more than it sounds: a signature covers bytes, not intentions.

No SDK for your language? The HTTP endpoint underneath all six is one POST. And tell us which one is missing: [email protected]. Adding a language is our favorite kind of feature request.

Your customers read generated pages too#

The /// comment above an event member is not decoration. It becomes the generated method's documentation, the event's description in your environment, and the text on your public event docs, which Repost rebuilds on every deploy.

So the page your customers integrate against cannot fall behind the payload. Nobody writes it, so nobody forgets it.

Let CI do the remembering#

Terminalrepost CLI
$repost schema languages
typescript
go
python
java
kotlin
csharp
$repost schema generate --check
Generated outputs are up to date.

--check recomputes every generator's output and exits non-zero on drift, listing the changed and stale files per output (all the flags). Put it in your pull request job next to repost schema migrate status.

Committed clients then land in git diff beside the migration that caused them, so a reviewer sees the contract change and its blast radius on one screen.

Anything you can hand-edit will drift. So don't hand-edit the client. Edit the schema, and let CI diff the rest.

Share