@usethrottle/webhook-types
v1.0.1
Published
TypeScript discriminated unions for every Throttle outbound webhook event.
Readme
@usethrottle/webhook-types
TypeScript discriminated unions for every Throttle outbound webhook event, plus a Stripe-compatible signature verification helper.
npm install @usethrottle/webhook-typesUsage
import {
type ThrottleEvent,
type ThrottleEventEnvelope,
verifyWebhookSignature,
} from '@usethrottle/webhook-types';
export async function POST(req: Request) {
const rawBody = await req.text();
const ok = verifyWebhookSignature({
header: req.headers.get('X-Throttle-Signature')!,
rawBody,
secret: process.env.THROTTLE_WEBHOOK_SECRET!,
});
if (!ok) return new Response('bad signature', { status: 400 });
const envelope = JSON.parse(rawBody) as ThrottleEvent;
const wireEnvelope: ThrottleEventEnvelope = envelope;
await audit.record({
eventId: wireEnvelope.id,
eventType: wireEnvelope.type,
eventVersion: wireEnvelope.version,
environmentId: wireEnvelope.environmentId,
});
// envelope.data is narrowed by envelope.type — no casts.
switch (envelope.type) {
case 'cart.item_added':
// envelope.data: { cartId, sequence, itemId, name, quantity, unitPrice }
analytics.track('item_added', envelope.data);
break;
case 'order.created':
// envelope.data: { order, fromCart?, sessionId? }
await fulfill(envelope.data.order as { id: string });
break;
case 'payment.disputed':
// envelope.data: { paymentId, reason, openedAt }
await ops.alert(envelope.data.paymentId, envelope.data.reason);
break;
case 'workspace.payment_method.backup_used':
// envelope.data: { workspace, primaryPaymentMethod, backupPaymentMethod, charge }
await notifyOps('backup PM used', envelope.data);
break;
}
return new Response('ok');
}Coverage
- ThrottleEventEnvelope — top-level wire envelope with
id,type,version,workspaceId,environmentId,createdAt, and typeddata. - ThrottleEvent — discriminated union of all
ThrottleEventEnvelopevariants currently emitted by the Throttle outbound bus (cart, order, payment, fulfillment, subscription, discount, customer, invoice, shipping/tax, workspace lifecycle). - WebhookEvent — legacy nested event union retained for internal/back-compat consumers.
- WebhookEventType —
WebhookEvent['type']; use it to constrain switch statements or event-name handlers. - Typed payloads — primitive fields (ids, amounts, codes, timestamps) are fully typed. Embedded domain objects (
order,payment,subscription,workspace, etc.) are typed asRecord<string, unknown>because the canonical schemas live in the API server and shouldn't be duplicated here. Cast through the OpenAPI client (@usethrottle/api-client) for the embedded shapes. - verifyWebhookSignature — Stripe-compatible
t=…,v1=…header parsing with timing-safe HMAC-SHA256 comparison + 5-minute replay window by default.
Versioning
The event list is the contract. Adding an event in the Throttle outbound bus is a minor bump; removing one is a major bump. Typed payload field changes are minor when additive; breaking when not. See docs.usethrottle.dev/developers/webhooks for the full event reference + signature format.
