Files
suaveplan/packages/api-stack/api/api-plugin-campaigns/src/routes/webhooks/schemas.md
2026-05-27 23:29:53 +01:00

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

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