Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

03 - User Journey (Webhooks Module)

End-to-end journeys against the implemented backend. Wire contracts quoted from src/modules/webhooks/**. Journeys 1-2 are implemented; journey 4 is (planned) (inbound receiver).


Journey 1 — Register and verify a webhook (Tenant Developer)

  1. Developer signs in (web-first surface, post-Phase 1 per PRODUCT_REQUIREMENTS_DOCUMENT.md:144).
  2. Opens Webhooks list - GET /api/v1/webhooks (sort newest first, webhooks.service.ts:35-37) - sees existing subscriptions and their enabled/paused state.
  3. Taps "New webhook" - POST /api/v1/webhooks (webhooks.controller.ts:21-25) with { name, url, events[1..n], secret, enabled? } (create-webhook.dto.ts:11-34). URL must be valid (@IsUrl({ require_tld: false }), create-webhook.dto.ts:17-18); events array must have at least one entry (ArrayMinSize(1), create-webhook.dto.ts:22-23).
  4. Fires the test ping - POST /api/v1/webhooks/:id/test (webhooks.controller.ts:65-70) - the worker POSTs { test: true, webhookId } as event WebhookTested (webhooks.service.ts:130-138).
  5. Receiver gets X-Webhook-Signature: <hmac-sha256 hex> + X-Webhook-Event: WebhookTested (webhook-delivery.worker.ts:70-82); developer verifies signature with the stored secret.
  6. Watches GET /api/v1/webhooks/:id/logs (50 latest, webhooks.service.ts:88-93) and GET /api/v1/webhooks/:id/metrics (webhooks.service.ts:142-156) - total/success/failed/pending counts.
  7. Done when the log row shows status: success (recordDelivery, webhooks.service.ts:179-193).

Journey 2 — Event arrives, delivery fails, manual retry

  1. A domain event is emitted on the EventBus (event-bus.service.ts:11-14).
  2. WebhooksService intercepts via onAny (webhooks.service.ts:26-28) and queries active subscriptions for that event type - tenantId + enabled + not deleted + events: eventType (webhook.repository.ts:17-29).
  3. One job per matching webhook is enqueued: deliver on queue webhook-deliver (queue.constants.ts:14) with attempts: 3 and exponential backoff starting at 5 s (webhooks.service.ts:69-84).
  4. Worker creates a pending delivery log (webhooks.service.ts:163-177), POSTs the payload (webhook-delivery.worker.ts:73-82), and records success on 2xx else failed + rethrows (webhook-delivery.worker.ts:84-100); BullMQ retries.
  5. All attempts exhausted - developer sees failed in logs and red in metrics.
  6. Developer taps "Retry" - POST /api/v1/webhooks/:id/retry (webhooks.controller.ts:58-63) - the latest failed delivery is re-enqueued with its original payload (webhooks.service.ts:95-120).
  7. Retry succeeds - log row flips to success (new row; the failed row stays - one log row per worker attempt, webhooks.service.ts:62-67, 84-89).

Journey 3 — Pause, edit, delete (School Admin)

  1. Admin pauses a misbehaving integration - POST /api/v1/webhooks/:id/pause (webhooks.controller.ts:78-83) - fan-out now skips it (webhook.repository.ts:23-28).
  2. Later resumes - POST /api/v1/webhooks/:id/resume (webhooks.controller.ts:85-90).
  3. Developer edits URL/events/secret - PATCH /api/v1/webhooks/:id with any subset of UpdateWebhookDto (PartialType, update-webhook.dto.ts:4).
  4. Decommission - DELETE /api/v1/webhooks/:id - soft delete (webhooks.service.ts:50-53, base.repository.ts:68-74); list no longer shows it.

Journey 4 — External vendor pushes events into StudyLyon (planned)

  1. Vendor signs up for the planned unauthenticated public scope (IMPLEMENTATION_PLAN.md:48 - "(webhooks, health)", 30 req/min, 1 min window).
  2. Vendor POSTs events to the public receiver - no such endpoint exists in source; blocked by IMPLEMENTATION_PLAN.md:856 (test-series integration via webhooks). Marked (planned); excluded from all API tables in 12.
  3. Journey ends here until the receiver is implemented.

Journey map (mermaid)

flowchart LR
    A[Domain event emitted<br/>event-bus.service.ts:11-14] --> B[onAny fan-out<br/>webhooks.service.ts:26-28]
    B --> C[findActiveByEvent<br/>webhook.repository.ts:17-29]
    C -->|match| D[enqueue deliver job<br/>attempts 3, backoff 5s<br/>webhooks.service.ts:80-83]
    D --> E[worker: pending log<br/>webhooks.service.ts:163-177]
    E --> F[POST url + HMAC sig<br/>webhook-delivery.worker.ts:70-82]
    F -->|2xx| G[log success]
    F -->|non-2xx/timeout| H[log failed + rethrow<br/>:84-100]
    H -->|attempts left| D
    H -->|exhausted| I[manual retry<br/>POST /:id/retry]
    I --> D