Webhooks
Declare webhooks in your spec and every package gets typed events, signature verification, a fake-event command, and a local relay. The full loop, end to end.
Webhooks are half of most APIs and usually the half SDKs ignore. typeship generates the receiving side from your spec: typed payloads, a verifying parser, a command that sends signed sample events, and a relay that brings real events to a laptop.
1. Declare webhooks in the spec
OpenAPI 3.1 has a top-level webhooks section. On 3.0 specs, typeship reads the established x-webhooks convention. Each entry needs a JSON request body schema:
webhooks:
account.updated:
post:
requestBody:
content:
application/json:
schema:
type: object
required: [type, account]
properties:
type: { type: string, enum: [account.updated] }
account: { $ref: "#/components/schemas/Account" }A property pinned to a single value (enum with one entry, or const) becomes the discriminator, so consumers can switch on event.type.
2. Sign events the standard way
The generated verifier follows the Standard Webhooks convention:
- Headers
webhook-id,webhook-timestamp, andwebhook-signature. - Signed content is
id.timestamp.payload, HMAC-SHA256, base64, sent asv1,<signature>. Several space-separated signatures are accepted, so keys can rotate. - Secrets are
whsec_followed by base64. Any other string is used as raw bytes. - Timestamps older or newer than five minutes are rejected.
If your API already signs this way, nothing changes. If it does not, adopting the convention is what makes the generated unwrap work.
3. Consumers verify with the SDK
const client = new AcmeClient({ webhookKey: process.env.ACME_WEBHOOK_KEY });
export async function handler(req: Request) {
const event = await client.webhooks.unwrap(await req.text(), req.headers);
switch (event.type) {
case "account.updated": return onUpdated(event.account);
case "account.closed": return onClosed(event.account_id);
}
}unwrap throws WebhookVerificationError on a bad signature, a stale timestamp, or a missing key. unwrapUnsafe parses without verifying. Verification uses WebCrypto, so the same code runs on Node, browsers, edge runtimes, and Workers.
All three SDKs sign byte-identically. A payload signed by one verifies in the others.
4. Test before any real event exists
The generated CLI builds a signed sample event from the spec's schemas:
acme webhooks fake # list declared events
acme webhooks fake account.updated --forward-to localhost:3000/webhooksThe key is --key, then ACME_WEBHOOK_KEY, then a throwaway. Set the same key in the handler under test and the signature verifies.
5. Bring real events to localhost
ProAvailable on Pro and Enterprise
With the webhook relay enabled on the project, acme webhooks listen --forward-to localhost:3000/webhooks mints a private URL and replays every event sent there with its original headers. Signature verification works unchanged because nothing is re-signed. Register the printed URL as a webhook endpoint in your API and events start arriving.
Coding agents
Use typeship from Claude Code, Codex, VS Code, and the other agents: the one-line prompt, the runbook, the CLI's agent contract, the hosted MCP server, and the skills.
Generate from GraphQL
A GraphQL schema generates the same package as an OpenAPI spec, on the same runtime: queries on client.query, mutations on client.mutation, typed selections, connections that auto-paginate.