# `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` — no `globalThis.fetch` patching. - Configurable timeout (default 10_000 ms) using `AbortController`. - Custom headers via `WebhookChannelConfig.headers`. - `canSend` enforces `http://` or `https://` recipient prefix. - Returns `DeliveryResult { success: false }` on non-2xx, network error, abort, or unexpected throw. - Injectable `Clock` for deterministic `timestamp` in tests. ## Usage: Basic ```ts import { WebhookChannel } from "@suaveplan/notification-channel"; const channel = new WebhookChannel({ headers: { Authorization: "Bearer secret" }, }); const r = await channel.send(notification); ``` ## Usage: Advanced ```ts 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`. ## 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 : "`. Both `Error` and non-`Error` throws inside the fetch callback are stringified into `error` so the `DeliveryResult` contract holds.