9.9 KiB
9.9 KiB
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 statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. 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/. Noparser/,verifier/,receiver/,hooks/, orschemas/directories.grep -rln "WebhookParser\|WebhookVerifier\|WebhookReceiver\|webhookHooks\|ParsedWebhookEvent" packages/platform/webhook-client/src/returns zero hits.bunx turbo lint typecheck test --filter=@suaveplan/webhook-clientis 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,webhookHooksevent bus over@suaveplan/events, six new error codes withmetadata.retryHint,WebhookSchema<T>Zod builder, all types (ParsedWebhookEvent<T>,SigningScheme,WebhookHandler<T>, etc.), four subpath exports, OTel histogram + counter, sibling.mds.
1. Spec authoring
- 1.1 Read
../suaveplan/enterprise/webhook-client.mdend-to-end - 1.2 Read
openspec/specs/webhook/spec.md(canonical capability spec; this change introduces it if absent) - 1.3 Read
openspec/AGENTS.mdSections 13 and 14 - 1.4 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.5 Fill
## Source mappingtable with upstream line ranges - 1.6 Draft
specs/webhook/spec.mdfromopenspec/templates/spec-delta.md - 1.7 Every requirement carries a
**Verified by:**line - 1.8
openspec change validate webhook-receiverexits with no errors
2. Implementation — WebhookParser
- 2.1 Port
WebhookParserfrompackages/universal/webhook-client/src/parser/parser.ts(L1-L130) intopackages/platform/webhook-client/src/parser/parser/parser.ts - 2.2 Implement
parseJsonBody(request),parseFormUrlencodedBody(request),parseRequest(request)(auto-detects fromcontent-typeheader) - 2.3 Validate parsed payload through caller-supplied Zod schema (or default
z.unknown()); reject withWebhookClientErrorcodePARSER_INVALID_BODY/PARSER_UNSUPPORTED_CONTENT_TYPE - 2.4 Return
ParsedWebhookEvent<T>carrying{ id, type, payload, receivedAt, signature?, headers } - 2.5 Sibling
.md(≥200 words, basic + advanced examples)
3. Implementation — WebhookVerifier
- 3.1 Port
WebhookVerifierfrompackages/universal/webhook-client/src/verifier/verifier.ts(L1-L127) intopackages/platform/webhook-client/src/verifier/verifier/verifier.ts - 3.2 Reuse existing
generateSignature/verifySignaturefrompackages/platform/webhook-client/src/utils/signature.tsfor thegenericscheme - 3.3 Implement
signingScheme: 'stripe' | 'github' | 'slack' | 'generic'— Stripe (HMAC-SHA256 of<timestamp>.<body>), GitHub (HMAC-SHA256 of body, prefixsha256=), Slack (HMAC-SHA256 ofv0:<timestamp>:<body>), generic (raw HMAC-SHA256 of body) - 3.4 Enforce
timestampToleranceMs(default 5 minutes) for schemes that include a timestamp; reject withVERIFIER_TIMESTAMP_OUT_OF_TOLERANCE - 3.5 Reject bad signatures with
VERIFIER_BAD_SIGNATURE - 3.6 Sibling
.md
4. Implementation — WebhookReceiver
- 4.1 Port
WebhookReceiverfrompackages/universal/webhook-client/src/receiver/receiver.ts(L1-L135) intopackages/platform/webhook-client/src/receiver/receiver/receiver.ts - 4.2 Implement
registerHandler<T>(eventType, handler),unregisterHandler(eventType),handle(request)orchestrator - 4.3 Pipeline: emit
onWebhookReceived→ verify (emitonWebhookVerified) → parse → dispatch to handler (emitonWebhookHandledoronWebhookFailed) - 4.4 Throw
RECEIVER_HANDLER_NOT_FOUNDwhen no handler is registered for the event type - 4.5 Throw
RECEIVER_HANDLER_FAILEDwhen the handler itself throws; preservecause - 4.6 Return
HandleResultcarrying{ status, parsedEvent, dispatchResult } - 4.7 Sibling
.md
5. Implementation — webhookHooks event bus
- 5.1 Port
webhookHooksfrompackages/universal/webhook-client/src/hooks.ts(L1-L58) intopackages/platform/webhook-client/src/hooks/hooks/hooks.ts - 5.2 Back the bus with
@suaveplan/events(not upstream's bespokeHooksclass) —webhookHooksis anEventBus<WebhookHookMap> - 5.3 Export
onWebhookDelivering,onWebhookReceived,onWebhookVerified,onWebhookHandled,onWebhookFailedtyped-handler registration helpers - 5.4 Sibling
.md
6. Error codes
- 6.1 Extend
WEBHOOK_CLIENT_CODESinpackages/platform/webhook-client/src/errors/webhook-client-error.tswith: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'—transientfor handler-failure,permanentfor bad-signature / unsupported-content-type / parser-invalid-body / handler-not-found / timestamp-out-of-tolerance - 6.3 Update
error-codes.mddoc to document each code'sretryHint
7. Types and schemas
- 7.1 Add
ParsedWebhookEvent<T>,SigningScheme,WebhookHandler<T>,WebhookHandlerRegistry,WebhookHookMap,HandleResult,WebhookReceiverOptions,WebhookVerifierOptions,WebhookParserOptionstopackages/platform/webhook-client/src/types/webhook-client.types.ts - 7.2 Add
WebhookSchema<T>(payloadSchema)Zod-builder helper topackages/platform/webhook-client/src/schemas/webhook.schema.ts - 7.3 Sibling
.mdfor the schema module
8. Wiring
- 8.1 Update
packages/platform/webhook-client/src/index.tsto 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.schematopackages/platform/webhook-client/package.json - 8.3 Update root
README.md## Modulessection to link the new co-located.mds
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—webhookHooksevents fire in order (onWebhookReceivedbeforeonWebhookVerifiedbeforeonWebhookHandled); OTelwebhook.receiver.handle.duration_mshistogram; error codes carrymetadata.retryHint - 9.5
packages/platform/webhook-client/src/hooks/hooks/hooks.test.ts—webhookHooks.on/off/emitround-trip - 9.6
packages/platform/webhook-client/src/errors/error-codes.spec-conformance.test.ts— every new code is registered; every new code carriesmetadata.retryHint - 9.7 Every
it(...)carries{ timeout: 5000 }
10. Documentation
- 10.1 Sibling
.mdfor every new implementation.ts(≥200 words, basic + advanced examples, full API reference) - 10.2 README links the new
.mds under## Modules - 10.3 Update
error-codes.mdto enumerate each new code with itsretryHintand 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, noerrorfindings - 11.5
bunx turbo build --filter=@suaveplan/webhook-client— green
12. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 12.1 Generics threaded;
expectTypeOfassertions onParsedWebhookEvent<T>andWebhookHandler<T> - 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_msand counterwebhook.receiver.handle_total(tagged by status) emitted from production paths and asserted with a real in-memoryMeterProvider - 12.6 Lifecycle dedup: handler registry rejects duplicate registrations for the same event type unless
replace: trueis 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/<yyyy-mm-dd>-webhook-receiver/ - 13.3 Refresh
MANIFEST.md