Skip to content

fix(webhooks): emit domain events from auth, module, and reward flows - #18

Merged
merlik787-droi merged 1 commit into
Kqirox:mainfrom
kilodesodiq-arch:feat/issue-8-webhook-event-producers
Aug 19, 2026
Merged

fix(webhooks): emit domain events from auth, module, and reward flows#18
merlik787-droi merged 1 commit into
Kqirox:mainfrom
kilodesodiq-arch:feat/issue-8-webhook-event-producers

Conversation

@kilodesodiq-arch

Copy link
Copy Markdown

Summary

Closes #8

WebhookService.queueEvent existed but was never invoked by any request path, so endpoints registered for module.completed, reward.issued, or user.registered received nothing. This PR wires the three producer sites (registration, module completion, reward issuance) to enqueue events through the service, reconciles employer.contact_attempt into the WebhookEventType contract, corrects the documented event list, and adds a producer test for each site. Delivery stays on the existing on-demand processQueue model: every producer enqueues fire-and-forget, so the HTTP response is never blocked on subscriber latency.

Why

Before this change the only webhookDelivery writes outside tests/seed came from employer.controller.ts's contactCandidate, which wrote directly via Prisma and bypassed the service entirely. queueEvent had no caller in src/, so the documented outbound-webhook feature never fired, and employer.contact_attempt was emitted off-contract (absent from the WebhookEventType union). The producers now reuse the existing queueEvent + async processQueue path instead of introducing a second delivery mechanism. The employer outreach write is kept direct and documented in-code: its system endpoint URL is internal-only and must never trigger an outbound HMAC-signed HTTP delivery.

What was built

File What it contains
src/controllers/auth.controller.ts Emits user.registered after a successful registration (fire-and-forget, before the 201 response).
src/controllers/module.controller.ts Emits module.completed after a completion is recorded (fire-and-forget).
src/controllers/reward.controller.ts Emits reward.issued when a withdrawal completes (status === 'completed'), after the response is sent.
src/types/webhook.types.ts Adds employer.contact_attempt to WebhookEventType so the union lists every emitted event.
src/controllers/employer.controller.ts Comment only: documents why outreach logs directly to WebhookDelivery instead of queueEvent.
docs/API.md Replaces the stale webhook list (user.completed_module, credential.verified) with the actually-emitted events.

Tests (each producer asserted, with a mirror under integrations/ per docs/DEVELOPING.md):

File What it asserts
tests/auth.controller.test.ts user.registered is enqueued with userId/email/username/role.
tests/unit/module.controller.test.ts module.completed is enqueued with userId/moduleId/moduleTitle/score/reward.
tests/unit/reward.controller.test.ts reward.issued is enqueued on a completed withdrawal, and not enqueued on withdrawal failure.

Integration changes outside the producers

  • src/controllers/employer.controller.ts — comment only; no behavior change.
  • src/types/webhook.types.tsWebhookEventType gained one member.
  • docs/API.md — webhook event list corrected.

Acceptance criteria coverage

Producers

  • A completed module produces a module.completed delivery for every active endpoint subscribed to it. (src/controllers/module.controller.ts completeModulequeueEvent; tests/unit/module.controller.test.ts)
  • A successful registration produces a user.registered delivery. (src/controllers/auth.controller.ts register; tests/auth.controller.test.ts)
  • A reward grant produces a reward.issued delivery. (src/controllers/reward.controller.ts withdraw — the only controller-reachable on-chain reward issuance path; tests/unit/reward.controller.test.ts)

Contract

  • WebhookEventType includes every event type the application emits, including employer.contact_attempt. (src/types/webhook.types.ts)

Delivery

  • The HTTP response to the producer is not delayed by subscriber delivery; processQueue runs asynchronously. (each producer calls queueEvent(...).catch(...) without await; queueEvent fires processQueue() fire-and-forget)
  • checkEndpointHealth still deactivates an endpoint after 10 consecutive failures. (src/services/webhook.service.ts is unmodified in this PR)

Tests

  • A test asserts each producer invokes queueEvent with the expected event type and payload, without requiring a live subscriber. (tests/auth.controller.test.ts, tests/unit/module.controller.test.ts, tests/unit/reward.controller.test.tsWebhookService is mocked, no network)
  • Existing tests/services/webhook.service.spec.ts still passes. (279/279 in pnpm test:ci)

Documentation

  • docs/API.md's webhook section lists the actually-emitted events. (user.registered, module.completed, reward.issued, employer.contact_attempt)

Test plan

  • pnpm test:ci — 279/279 passing (1 new test file for the module.completed producer)
  • pnpm exec tsc --noEmit — no type errors
  • pnpm lint — no errors or warnings
  • pnpm build — succeeds

Env vars / Notes

No new environment variables or config keys. employer.contact_attempt remains a direct WebhookDelivery write (an audit log) rather than an outbound delivery; this is intentional and documented in src/controllers/employer.controller.ts. reward.issued is emitted from the withdrawal-completion path because RewardService.claimReward is not wired to any controller — withdrawal is the only request path that actually issues rewards on-chain today.

Wire WebhookService.queueEvent into the three request paths that were
documented but never produced events: user.registered on registration,
module.completed on quiz completion, and reward.issued on completed
withdrawals. Add employer.contact_attempt to the WebhookEventType union,
document the emitted events in docs/API.md, and assert each producer
invokes queueEvent in controller tests.

@merlik787-droi merlik787-droi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@merlik787-droi
merlik787-droi merged commit da0d9bf into Kqirox:main Aug 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Webhook delivery pipeline is unreachable: queueEvent is never invoked by any request path

2 participants