# Tasks — Webhook receiver > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick. ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## Audit Note (2026-05-02) **Minimal progress** — DEFERRED, do not archive. - `packages/platform/webhook-client/src/` has only the outbound surface: `core/`, `errors/`, `types/`, `utils/`. No `parser/`, `verifier/`, `receiver/`, `hooks/`, or `schemas/` directories. - `grep -rln "WebhookParser\|WebhookVerifier\|WebhookReceiver\|webhookHooks\|ParsedWebhookEvent" packages/platform/webhook-client/src/` returns zero hits. - `bunx turbo lint typecheck test --filter=@suaveplan/webhook-client` is GREEN — but the entire inbound surface is unimplemented. - All 64 tasks remain: port `WebhookParser` (130 L), `WebhookVerifier` (127 L) with stripe/github/slack/generic schemes, `WebhookReceiver` (135 L) orchestrator, `webhookHooks` event bus over `@suaveplan/events`, six new error codes with `metadata.retryHint`, `WebhookSchema` Zod builder, all types (`ParsedWebhookEvent`, `SigningScheme`, `WebhookHandler`, etc.), four subpath exports, OTel histogram + counter, sibling `.md`s. ## 1. Spec authoring - [ ] 1.1 Read `../suaveplan/enterprise/webhook-client.md` end-to-end - [ ] 1.2 Read `openspec/specs/webhook/spec.md` (canonical capability spec; this change introduces it if absent) - [ ] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14 - [ ] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md` - [ ] 1.5 Fill `## Source mapping` table with upstream line ranges - [ ] 1.6 Draft `specs/webhook/spec.md` from `openspec/templates/spec-delta.md` - [ ] 1.7 Every requirement carries a `**Verified by:**` line - [ ] 1.8 `openspec change validate webhook-receiver` exits with no errors ## 2. Implementation — `WebhookParser` - [ ] 2.1 Port `WebhookParser` from `packages/universal/webhook-client/src/parser/parser.ts` (L1-L130) into `packages/platform/webhook-client/src/parser/parser/parser.ts` - [ ] 2.2 Implement `parseJsonBody(request)`, `parseFormUrlencodedBody(request)`, `parseRequest(request)` (auto-detects from `content-type` header) - [ ] 2.3 Validate parsed payload through caller-supplied Zod schema (or default `z.unknown()`); reject with `WebhookClientError` code `PARSER_INVALID_BODY` / `PARSER_UNSUPPORTED_CONTENT_TYPE` - [ ] 2.4 Return `ParsedWebhookEvent` carrying `{ id, type, payload, receivedAt, signature?, headers }` - [ ] 2.5 Sibling `.md` (≥200 words, basic + advanced examples) ## 3. Implementation — `WebhookVerifier` - [ ] 3.1 Port `WebhookVerifier` from `packages/universal/webhook-client/src/verifier/verifier.ts` (L1-L127) into `packages/platform/webhook-client/src/verifier/verifier/verifier.ts` - [ ] 3.2 Reuse existing `generateSignature` / `verifySignature` from `packages/platform/webhook-client/src/utils/signature.ts` for the `generic` scheme - [ ] 3.3 Implement `signingScheme: 'stripe' | 'github' | 'slack' | 'generic'` — Stripe (HMAC-SHA256 of `.`), GitHub (HMAC-SHA256 of body, prefix `sha256=`), Slack (HMAC-SHA256 of `v0::`), generic (raw HMAC-SHA256 of body) - [ ] 3.4 Enforce `timestampToleranceMs` (default 5 minutes) for schemes that include a timestamp; reject with `VERIFIER_TIMESTAMP_OUT_OF_TOLERANCE` - [ ] 3.5 Reject bad signatures with `VERIFIER_BAD_SIGNATURE` - [ ] 3.6 Sibling `.md` ## 4. Implementation — `WebhookReceiver` - [ ] 4.1 Port `WebhookReceiver` from `packages/universal/webhook-client/src/receiver/receiver.ts` (L1-L135) into `packages/platform/webhook-client/src/receiver/receiver/receiver.ts` - [ ] 4.2 Implement `registerHandler(eventType, handler)`, `unregisterHandler(eventType)`, `handle(request)` orchestrator - [ ] 4.3 Pipeline: emit `onWebhookReceived` → verify (emit `onWebhookVerified`) → parse → dispatch to handler (emit `onWebhookHandled` or `onWebhookFailed`) - [ ] 4.4 Throw `RECEIVER_HANDLER_NOT_FOUND` when no handler is registered for the event type - [ ] 4.5 Throw `RECEIVER_HANDLER_FAILED` when the handler itself throws; preserve `cause` - [ ] 4.6 Return `HandleResult` carrying `{ status, parsedEvent, dispatchResult }` - [ ] 4.7 Sibling `.md` ## 5. Implementation — `webhookHooks` event bus - [ ] 5.1 Port `webhookHooks` from `packages/universal/webhook-client/src/hooks.ts` (L1-L58) into `packages/platform/webhook-client/src/hooks/hooks/hooks.ts` - [ ] 5.2 Back the bus with `@suaveplan/events` (not upstream's bespoke `Hooks` class) — `webhookHooks` is an `EventBus` - [ ] 5.3 Export `onWebhookDelivering`, `onWebhookReceived`, `onWebhookVerified`, `onWebhookHandled`, `onWebhookFailed` typed-handler registration helpers - [ ] 5.4 Sibling `.md` ## 6. Error codes - [ ] 6.1 Extend `WEBHOOK_CLIENT_CODES` in `packages/platform/webhook-client/src/errors/webhook-client-error.ts` with: `PARSER_INVALID_BODY`, `PARSER_UNSUPPORTED_CONTENT_TYPE`, `VERIFIER_BAD_SIGNATURE`, `VERIFIER_TIMESTAMP_OUT_OF_TOLERANCE`, `RECEIVER_HANDLER_NOT_FOUND`, `RECEIVER_HANDLER_FAILED` - [ ] 6.2 Each new code carries `metadata.retryHint: 'transient' | 'permanent'` — `transient` for handler-failure, `permanent` for bad-signature / unsupported-content-type / parser-invalid-body / handler-not-found / timestamp-out-of-tolerance - [ ] 6.3 Update `error-codes.md` doc to document each code's `retryHint` ## 7. Types and schemas - [ ] 7.1 Add `ParsedWebhookEvent`, `SigningScheme`, `WebhookHandler`, `WebhookHandlerRegistry`, `WebhookHookMap`, `HandleResult`, `WebhookReceiverOptions`, `WebhookVerifierOptions`, `WebhookParserOptions` to `packages/platform/webhook-client/src/types/webhook-client.types.ts` - [ ] 7.2 Add `WebhookSchema(payloadSchema)` Zod-builder helper to `packages/platform/webhook-client/src/schemas/webhook.schema.ts` - [ ] 7.3 Sibling `.md` for the schema module ## 8. Wiring - [ ] 8.1 Update `packages/platform/webhook-client/src/index.ts` to re-export the parser, verifier, receiver, hooks, schema-builder, and new types - [ ] 8.2 Add subpath exports `./parser/parser/parser`, `./verifier/verifier/verifier`, `./receiver/receiver/receiver`, `./hooks/hooks/hooks`, `./schemas/webhook.schema` to `packages/platform/webhook-client/package.json` - [ ] 8.3 Update root `README.md` `## Modules` section to link the new co-located `.md`s ## 9. Tests - [ ] 9.1 `packages/platform/webhook-client/src/parser/parser/parser.test.ts` — JSON round-trip, form-urlencoded round-trip, unsupported content-type rejection, malformed-body rejection - [ ] 9.2 `packages/platform/webhook-client/src/verifier/verifier/verifier.test.ts` — each scheme (stripe, github, slack, generic) round-trip; tampered signature rejected; out-of-tolerance timestamp rejected - [ ] 9.3 `packages/platform/webhook-client/src/receiver/receiver/receiver.test.ts` — full pipeline with handler dispatch; handler-not-found path; handler-throws path - [ ] 9.4 `packages/platform/webhook-client/src/receiver/receiver/receiver.spec-conformance.test.ts` — `webhookHooks` events fire in order (`onWebhookReceived` before `onWebhookVerified` before `onWebhookHandled`); OTel `webhook.receiver.handle.duration_ms` histogram; error codes carry `metadata.retryHint` - [ ] 9.5 `packages/platform/webhook-client/src/hooks/hooks/hooks.test.ts` — `webhookHooks.on/off/emit` round-trip - [ ] 9.6 `packages/platform/webhook-client/src/errors/error-codes.spec-conformance.test.ts` — every new code is registered; every new code carries `metadata.retryHint` - [ ] 9.7 Every `it(...)` carries `{ timeout: 5000 }` ## 10. Documentation - [ ] 10.1 Sibling `.md` for every new implementation `.ts` (≥200 words, basic + advanced examples, full API reference) - [ ] 10.2 README links the new `.md`s under `## Modules` - [ ] 10.3 Update `error-codes.md` to enumerate each new code with its `retryHint` and a one-line description ## 11. Pre-archive verification (the 5-gate triple) - [ ] 11.1 `bun run check` — Biome 0/0/0 across the monorepo - [ ] 11.2 `bunx turbo lint typecheck test --filter=@suaveplan/webhook-client` — green - [ ] 11.3 `bunx turbo test:coverage --filter=@suaveplan/webhook-client` — 100/100/100/100 - [ ] 11.4 `bun scripts/openspec-conformance.ts @suaveplan/webhook-client` — exit 0, no `error` findings - [ ] 11.5 `bunx turbo build --filter=@suaveplan/webhook-client` — green ## 12. Pre-archive 7-row checklist (AGENTS.md Section 13) - [ ] 12.1 Generics threaded; `expectTypeOf` assertions on `ParsedWebhookEvent` and `WebhookHandler` - [ ] 12.2 Zod schemas wired at every public boundary (`WebhookSchema`, `WebhookReceiverOptionsSchema`, `WebhookVerifierOptionsSchema`) - [ ] 12.3 Every new error code thrown from at least one production path - [ ] 12.4 Strict-mode tested: schemes with vs without timestamp tolerance - [ ] 12.5 OTel histogram `webhook.receiver.handle.duration_ms` and counter `webhook.receiver.handle_total` (tagged by status) emitted from production paths and asserted with a real in-memory `MeterProvider` - [ ] 12.6 Lifecycle dedup: handler registry rejects duplicate registrations for the same event type unless `replace: true` is set - [ ] 12.7 No retroactive tick — every box reflects work done in this change ## 13. Archive - [ ] 13.1 Fold spec delta into `openspec/specs/webhook/spec.md` (creating it if absent) - [ ] 13.2 Move `openspec/changes/webhook-receiver/` → `openspec/archive/-webhook-receiver/` - [ ] 13.3 Refresh `MANIFEST.md`