@samveys/webhooks
Verified incoming deliveries and signed outgoing events
import { sendWebhook, standardSignature, verifyDelivery, webhookEvent, webhookReceiver } from '@samveys/webhooks'
Function (5)
sendWebhook
Function
Signs an event per Standard Webhooks and posts it with `@samveys/http-client` in `untrusted` mode: an endpoint URL that points at a loopback, private or metadata address (or redirects there) is refused with SVS_E106 before anything is sent. The body is `{"type":…,"timestamp":"<ISO>","data":…}`; the headers are `webhook-id`, `webhook-timestamp` (epoch seconds) and `webhook-signature` (`v1,<base64 HMAC-SHA256 of id.timestamp.body>`). A non-2xx answer is returned as `ok: false`; queue retries by calling this inside a job.
function sendWebhook(endpoint: WebhookEndpoint, event: OutgoingEvent, options?: SendWebhookOptions): Promise<SendResult>;
Exported from packages/webhooks/src/sender.ts
standardSignature
Function
`v1,<base64 HMAC-SHA256 of "<id>.<timestamp>.<body>">` for one secret.
function standardSignature(secret: string, id: string, timestamp: number | string, body: Uint8Array | string): string;
Exported from packages/webhooks/src/verify.ts
verifyDelivery
Function
Checks a delivery's signature (and, where the scheme signs one, its timestamp) against the raw body. Returns the delivery id. Throws SVS_E108 for any problem, always with the same public message.
function verifyDelivery(input: VerifyInput): WebhookEvent;
Exported from packages/webhooks/src/verify.ts
webhookEvent
Function
The verified delivery stored by {@link webhookReceiver} when no `process` callback is configured.
function webhookEvent(ctx: Pick<HttpContext, 'state'>): WebhookEvent;
Exported from packages/webhooks/src/receiver.ts
webhookReceiver
Function
Route middleware that accepts a webhook only when it is genuine and new. 1. Reads the raw body from a clone (before the kernel parses it) and verifies the signature with a constant-time comparison, plus the 5-minute timestamp window where the scheme signs one. 2. Remembers the delivery id in the cache (`add(id, …, ttl)`); a second delivery with the same id is rejected. 3. Hands the event to `process`, or stores it for the handler (read it with {@link webhookEvent}). Anything wrong answers `401 SVS_E108` with a fixed body. The reason goes to `error.meta` only. If `process` throws, the id is forgotten again so the sender's retry is accepted.
function webhookReceiver(options: WebhookReceiverOptions): Middleware;
Exported from packages/webhooks/src/receiver.ts
Interface (7)
OutgoingEvent
Interface
No documentation comment.
interface OutgoingEvent {
/** Event id, used as `webhook-id`. Default: a random UUID. */
readonly id?: string;
readonly type: string;
readonly data: unknown;
}
Exported from packages/webhooks/src/contracts.ts
SendResult
Interface
No documentation comment.
interface SendResult {
readonly id: string;
readonly status: number;
readonly ok: boolean;
}
Exported from packages/webhooks/src/contracts.ts
SendWebhookOptions
Interface
No documentation comment.
interface SendWebhookOptions {
/** Client used for delivery; it is always called in `untrusted` mode. Default: a fresh client. */
readonly client?: HttpClient;
readonly now?: () => Date;
Exported from packages/webhooks/src/contracts.ts
VerifyInput
Interface
No documentation comment.
interface VerifyInput {
readonly driver: WebhookDriver;
/** `whsec_<base64>` (standard), shared secret (github, stripe), server key (midtrans). A list allows rotation. */
readonly secret: string | readonly string[];
readonly headers: Headers;
/** The raw request body: the exact bytes that were signed. */
readonly rawBody: Uint8Array | string;
/** Largest accepted clock difference in seconds for drivers that sign a timestamp. Default 300. */
readonly toleranceSeconds?: number;
/** Clock override (tests). */
readonly now?: () => Date;
Exported from packages/webhooks/src/contracts.ts
WebhookEndpoint
Interface
Where an outgoing event goes.
interface WebhookEndpoint {
readonly url: string;
/** Signing secret, `whsec_<base64>`. */
readonly secret: string;
}
Exported from packages/webhooks/src/contracts.ts
WebhookEvent
Interface
A delivery whose signature, age and uniqueness were checked.
interface WebhookEvent {
/** Delivery id: `webhook-id`, `X-GitHub-Delivery`, the Stripe event id, or the Midtrans transaction key. */
readonly id: string;
readonly driver: WebhookDriver;
/** The parsed JSON body, or `undefined` when the body is not JSON. */
readonly payload: unknown;
/** The body exactly as received (the bytes that were signed). */
readonly rawBody: string;
readonly headers: Headers;
}
Exported from packages/webhooks/src/contracts.ts
WebhookReceiverOptions
Interface
No documentation comment.
interface WebhookReceiverOptions {
readonly driver: WebhookDriver;
readonly secret: string | readonly string[];
/** Where seen delivery ids are remembered (`add(id, …, ttl)`); omit to disable replay protection (not advised). */
readonly cache?: Pick<CacheStore, 'add' | 'forget'>;
/** How long a delivery id is remembered. Default 24 hours. */
readonly replayTtlMs?: number;
readonly toleranceSeconds?: number;
/**
* Called with each verified, first-seen delivery. Use `queue.dispatch(...)` inside it so the
…
Show the whole declaration (14 lines)
interface WebhookReceiverOptions {
readonly driver: WebhookDriver;
readonly secret: string | readonly string[];
/** Where seen delivery ids are remembered (`add(id, …, ttl)`); omit to disable replay protection (not advised). */
readonly cache?: Pick<CacheStore, 'add' | 'forget'>;
/** How long a delivery id is remembered. Default 24 hours. */
readonly replayTtlMs?: number;
readonly toleranceSeconds?: number;
/**
* Called with each verified, first-seen delivery. Use `queue.dispatch(...)` inside it so the
…interface WebhookReceiverOptions {
readonly driver: WebhookDriver;
readonly secret: string | readonly string[];
/** Where seen delivery ids are remembered (`add(id, …, ttl)`); omit to disable replay protection (not advised). */
readonly cache?: Pick<CacheStore, 'add' | 'forget'>;
/** How long a delivery id is remembered. Default 24 hours. */
readonly replayTtlMs?: number;
readonly toleranceSeconds?: number;
/**
* Called with each verified, first-seen delivery. Use `queue.dispatch(...)` inside it so the
* HTTP response is not held up. When set, the middleware answers `200 {"received":true}` itself;
* when omitted the route handler runs and reads the event with {@link webhookEvent}.
*/
readonly process?: (event: WebhookEvent) => void | Promise<void>;
Exported from packages/webhooks/src/contracts.ts
Type (1)
WebhookDriver
Type
Built-in verification drivers.
type WebhookDriver = 'standard' | 'github' | 'stripe' | 'midtrans';
Exported from packages/webhooks/src/contracts.ts