Files
suaveplan/packages/communication/messaging/notification-channel/src/channels/webhook-channel.md
2026-05-27 23:29:53 +01:00

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 — 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

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.