Files
skills/plugins/document-navigator/docs/api-stack/api/api-plugin-webhook/README.md
admin 9bf56a806b Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:10:00 +01:00

8.7 KiB

@suaveplan/api-plugin-webhook - Signed outbound webhook delivery plugin for @suaveplan/api. Wraps @suaveplan/webhook-client behind a definePlugin descriptor, registers a typed webhookService in ServiceRegistry, mounts tenant-scoped CRUD + test routes through @suaveplan/api-openapi, drives HMAC-SHA256 delivery with exponential-backoff retry + dead-letter sink, and dedups concurrent deliveries via @suaveplan/idempotency.

Signed outbound webhook delivery plugin for @suaveplan/api. Wraps @suaveplan/webhook-client behind a definePlugin descriptor, registers a typed webhookService in the host ServiceRegistry, mounts tenant-scoped CRUD + test routes through @suaveplan/api-openapi, drives HMAC-SHA256 delivery with exponential-backoff retry and a dead-letter sink, and dedups concurrent deliveries via @suaveplan/idempotency.

Documentation tree

  • dead-letter — Defines the DeadLetterSink interface and DeadLetterEntry shape used by WebhookService.send to persist delivery at…
    • dead-letter — Defines the DeadLetterSink interface and DeadLetterEntry shape used by WebhookService.send to persist delivery at…
    • memory — Provides the default DeadLetterSink implementation shipped with the plugin: an unbounded Array<DeadLetterEntry> bac…
  • error-codes — Defines the ten stable WEBHOOK_* error codes raised by the api-plugin-webhook package and the concrete WebhookError…
    • error-codes — Defines the ten stable WEBHOOK_* error codes raised by the api-plugin-webhook package and the concrete WebhookError…
  • otel — Defines the four OpenTelemetry instruments emitted by the webhook plugin: two counters and two histograms covering ever…
    • instruments — Defines the four OpenTelemetry instruments emitted by the webhook plugin: two counters and two histograms covering ever…
  • plugin — Top-level definePlugin consumer for the webhook plugin. Returns a typed PluginDescriptor<WebhookPluginConfig> whose…
    • plugin — Top-level definePlugin consumer for the webhook plugin. Returns a typed PluginDescriptor<WebhookPluginConfig> whose…
  • routes — Builds the six CRUD + test route entries the webhook plugin mounts on the host's Hono app. Each entry pairs a Zod-typed…
    • build-routes — Builds the six CRUD + test route entries the webhook plugin mounts on the host's Hono app. Each entry pairs a Zod-typed…
  • routes — Bundles the Zod schemas for every CRUD + test route the webhook plugin mounts: list response, create body, create/read…
    • routes — Bundles the Zod schemas for every CRUD + test route the webhook plugin mounts: list response, create body, create/read…
  • endpoint — Zod schemas for the webhook endpoint shapes — the persistent WebhookEndpoint row, the POST /webhooks create body, a…
  • options — Zod schema gating createWebhookPlugin(options). Validates every primitive option (route prefix, retry caps, attempt c…
  • registry-augmentation — Declares the webhookService slot on @suaveplan/api-plugin/service-registry's ServiceRegistry interface via TypeSc…
  • webhook-service — Wraps @suaveplan/webhook-client's deliver function with the plugin's orchestration layer: endpoint lookup, tenant s…
  • endpoint-store — Defines the pluggable persistence seam for webhook endpoints. Every method is async-only so downstream packages can plu…
  • memory — Provides the default EndpointStore shipped with the plugin: a Map-backed implementation suitable for tests, single-…
  • types — Defines the cross-feature data shapes used by every module in @suaveplan/api-plugin-webhook: the persistent `WebhookE…
    • types — Defines the cross-feature data shapes used by every module in @suaveplan/api-plugin-webhook: the persistent `WebhookE…

Install

bun add @suaveplan/api-plugin-webhook

Quick start

import { defineApp } from "@suaveplan/api";
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
import { createWebhookPlugin } from "@suaveplan/api-plugin-webhook";

const app = defineApp()
    .use(createAuthPlugin({ betterAuth: { /* ... */ } }))
    .use(createWebhookPlugin());

// Register and fire from anywhere with access to the ServiceRegistry:
const webhookService = registry.getService("webhookService");
const endpoint = await webhookService.register({
    tenantId: null,
    url: "https://partner.example.com/hooks",
    secret: crypto.randomUUID(),
    events: ["order.created"],
});
await webhookService.send(endpoint.id, "order.created", { orderId: 42 });

Surface

Six CRUD + test routes mounted under routePrefix (default /webhooks), every route gated by requireAuth() from @suaveplan/api-plugin-auth and scoped by the request principal's tenantId when present:

Method Path Purpose
GET /webhooks List endpoints visible to the principal
POST /webhooks Create an endpoint (caller supplies the HMAC secret)
GET /webhooks/:id Read one endpoint
PATCH /webhooks/:id Partial update
DELETE /webhooks/:id Hard delete (idempotent)
POST /webhooks/:id/test Fire a synthetic webhook.ping

The same surface is available programmatically via the webhookService slot in ServiceRegistry:

  • send(endpointId, event, data)
  • sendAll(event, data, filter?)
  • register(input) / update(id, patch) / unregister(id)
  • listEndpoints(filter?) / getEndpoint(id)
  • test(endpointId)

Delivery semantics

  • HMAC-SHA256 default (SHA-512 opt-in per endpoint), header naming follows webhook-client (X-Hub-Signature-256 / X-Hub-Signature-512).
  • Standard outbound headers: Content-Type: application/json, X-Webhook-Id, X-Webhook-Event, X-Webhook-Timestamp.
  • Exponential backoff with full jitter, doubling per attempt, capped at maxRetryDelayMs.
  • maxAttempts defaults to 5; 4xx (except 429) terminates immediately; 429 and 5xx retry; network errors and timeouts retry.
  • Exhausted deliveries land in the DeadLetterSink with the full attempt history before surfacing WEBHOOK_DELIVERY_EXHAUSTED to the caller.
  • Per-delivery dedup via (endpointId, payload.id) — concurrent same-id sends collapse to a single delivery, either through the injected DistributedIdempotencyGuard or an in-process Map.

Persistence

The plugin ships MemoryEndpointStore (default) and MemoryDeadLetterSink (default) so it boots zero-config. Production deployments configure durable siblings against the EndpointStore / DeadLetterSink interfaces — a SurrealDB-backed store lands as @suaveplan/api-plugin-webhook-surreal.

Modules

OpenTelemetry

  • Counter webhook_plugin.deliveries_total{event,status,endpoint_id}
  • Counter webhook_plugin.dead_letters_total{event,endpoint_id}
  • Histogram webhook_plugin.delivery_attempts{event,status}
  • Histogram webhook_plugin.send_duration_ms{status}

Status label values: delivered, exhausted, not_found, inactive, invalid.

License

MIT