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