3.1 KiB
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 withchannel: "email"default and ISO 8601 timestamp.UnsubscribeTokenParamSchema—:tokenpath validator.DeliveryWebhookResponseSchema,UnsubscribeResponseSchema— response shapes.decodeUnsubscribeToken(token)— base64 → JSON → Zod-validated payload, with three distinct error messages so the route handler can map all toINVALID_UNSUBSCRIBE_TOKEN.
Usage: Basic
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
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— throwsErroron 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).