Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# Webhook route schemas
## Purpose
The webhook-route schemas describe the OpenAPI contract for the
`/webhooks` route tree, plus a `decodeUnsubscribeToken` helper. The
delivery webhook accepts a normalised `DeliveryEvent` payload (the
provider-specific parser lives in `@suaveplan/delivery-tracking`'s
`WebhookEventParser`; this plugin only handles already-normalised
events). The unsubscribe endpoint decodes a base64-JSON token carrying
`{ contactId, campaignId? }` and falls through to the suppression
store.
The token format is deliberately lightweight — base64-encoded JSON
with no signature. Production deployments SHOULD wrap this endpoint in
a per-tenant HMAC verification layer (e.g. via the rate-limit or
auth-plugin middleware chain) before the token decode happens. This
module is the parsing primitive; identity verification is layered on
above.
## Features
- `DeliveryEventTypeSchema` — 10-member enum.
- `DeliveryWebhookSchema` — body validator with `channel: "email"`
default and ISO 8601 timestamp.
- `UnsubscribeTokenParamSchema` — `:token` path validator.
- `DeliveryWebhookResponseSchema`, `UnsubscribeResponseSchema` —
response shapes.
- `decodeUnsubscribeToken(token)` — base64 → JSON → Zod-validated
payload, with three distinct error messages so the route handler
can map all to `INVALID_UNSUBSCRIBE_TOKEN`.
## Usage: Basic
```ts
import { DeliveryWebhookSchema } from "@suaveplan/api-plugin-campaigns";
const event = DeliveryWebhookSchema.parse(await c.req.json());
await store.record({ ...event, tenantId, id: crypto.randomUUID() });
```
## Usage: Advanced
```ts
import {
CAMPAIGNS_API_CODES,
CampaignsApiError,
decodeUnsubscribeToken,
} from "@suaveplan/api-plugin-campaigns";
let payload;
try {
payload = decodeUnsubscribeToken(c.req.param("token"));
} catch (cause) {
throw new CampaignsApiError(
CAMPAIGNS_API_CODES.INVALID_UNSUBSCRIBE_TOKEN,
"could not decode unsubscribe token",
{ cause, context: { statusCode: 400 } }
);
}
```
## API Reference
- `DeliveryEventTypeSchema: ZodEnum<10>`
- `DeliveryWebhookSchema: ZodObject<...>`
- `UnsubscribeTokenParamSchema: ZodObject<{ token: ZodString }>`
- `DeliveryWebhookResponseSchema`, `UnsubscribeResponseSchema` —
response shapes.
- `decodeUnsubscribeToken(token: string): UnsubscribeTokenPayload` —
throws `Error` on any failure mode.
- `type DeliveryWebhookBody`, `type UnsubscribeTokenPayload`.
## Implementation Notes
`decodeUnsubscribeToken` throws plain `Error` (not
`CampaignsApiError`) because the helper is consumed from a single
route-handler site that wraps every throw in
`CampaignsApiError(INVALID_UNSUBSCRIBE_TOKEN)`. Centralising the wrap
at the call site keeps the helper portable for future consumers that
may wish to map the failure mode differently (e.g. a CLI tool issuing
test tokens). The three distinct error messages — base64,
JSON, contactId — are preserved so server-side logs can distinguish
between a tampered token (likely malicious), a malformed token (likely
truncated by a mail-client URL-shortener), and a structurally-valid
but content-invalid token (likely a tenant-config bug).