2.5 KiB
webhook-channel — HTTP webhook delivery channel
Purpose
webhook-channel.ts ships the WebhookChannel implementation that POSTs notifications to an arbitrary HTTPS endpoint. The notification's recipient field IS the destination URL, the body carries the serialised notification payload, and the channel respects a per-request timeout via AbortController. The fetch surface is injectable via FetchLike from @suaveplan/types, so tests run deterministically against FakeFetch and downstream consumers can plug their own observability-instrumented fetch.
Features
- Injectable
FetchLike— noglobalThis.fetchpatching. - Configurable timeout (default 10_000 ms) using
AbortController. - Custom headers via
WebhookChannelConfig.headers. canSendenforceshttp://orhttps://recipient prefix.- Returns
DeliveryResult { success: false }on non-2xx, network error, abort, or unexpected throw. - Injectable
Clockfor deterministictimestampin tests.
Usage: Basic
import { WebhookChannel } from "@suaveplan/notification-channel";
const channel = new WebhookChannel({
headers: { Authorization: "Bearer secret" },
});
const r = await channel.send(notification);
Usage: Advanced
import { FakeFetch, WebhookChannel } from "@suaveplan/notification-channel";
// In tests:
const fake = new FakeFetch();
fake.push({ status: 200 });
const channel = new WebhookChannel({
fetchFn: fake.fetch,
timeoutMs: 500,
clock: () => testNow,
});
// In prod, wire a real instrumented fetch:
const channel2 = new WebhookChannel({
fetchFn: (url, init) => instrumented.fetch(url, init),
headers: { "X-Signed-By": "suaveplan" },
});
API Reference
WebhookChannel(config?: WebhookChannelConfig)— constructor.WebhookChannelConfig—{ headers?, fetchFn?, timeoutMs?, clock? }.canSend(n): boolean,send(n): Promise<DeliveryResult>.
Implementation Notes
The POST body is built from { id, subject, body, priority, metadata, createdAt } — a stable shape so webhook consumers can lock their parsers. Content-Type: application/json is always set; consumer headers override the default. Aborted fetches surface error: "aborted" (matching the AbortError.name-derived message from the polyfill). Non-2xx responses encode the upstream status into error: "HTTP <status>: <statusText>". Both Error and non-Error throws inside the fetch callback are stringified into error so the DeliveryResult contract holds.