Exact wire contracts for the Webhooks module. Base path /api/v1 (URI
versioning, main.ts); endpoints carry @Permissions decorators
(webhooks.controller.ts:22, 28, 34, 40, 46, 53, 59, 66, 73, 79, 86) backed by
permissions.constants.ts:89-92 (guards on endpoints not yet wired -
AGENTS.md). Tenant-scoped via BaseRepository (base.repository.ts:20-30).
Envelopes per 00-shared/07 §2-3. All paths below are prefixed
/api/v1/webhooks unless noted.
| Method | Path | Permission | DTO / source | Description |
| POST | /api/v1/webhooks | webhook.create | CreateWebhookDto (create-webhook.dto.ts:11-34) | Create subscription; returns full doc incl. plaintext secret |
| GET | /api/v1/webhooks | webhook.read | - | List all, sort createdAt: -1, unpaginated (webhooks.service.ts:35-37) |
| GET | /api/v1/webhooks/:id | webhook.read | - | Get by id; 404 'Webhook not found' (webhooks.service.ts:39-43) |
| PATCH | /api/v1/webhooks/:id | webhook.update | UpdateWebhookDto (PartialType - all optional, update-webhook.dto.ts:4) | Update any subset; 404 if missing (webhooks.service.ts:45-48) |
| DELETE | /api/v1/webhooks/:id | webhook.delete | - | Soft delete; 200 { message: 'Webhook deleted' } (webhooks.controller.ts:45-50, webhooks.service.ts:50-53) |
| Method | Path | Permission | Source | Description |
| GET | /api/v1/webhooks/:id/logs | webhook.read | webhooks.service.ts:88-93 | Delivery attempts, newest first, hard limit 50; no pagination params |
| POST | /api/v1/webhooks/:id/retry | webhook.update | webhooks.service.ts:95-120 | Re-queue latest failed attempt (404 'No failed deliveries to retry' if none, :107-108); 200 { message: 'Retry queued' } |
| POST | /api/v1/webhooks/:id/test | webhook.update | webhooks.service.ts:122-140 | Enqueue synthetic delivery (eventType: 'WebhookTested', payload: { test: true, webhookId }); 200 { message: 'Test delivery queued' } |
| GET | /api/v1/webhooks/:id/metrics | webhook.read | webhooks.service.ts:142-156 | { total, success, failed, pending } counts; 404 if webhook missing |
| POST | /api/v1/webhooks/:id/pause | webhook.update | webhooks.service.ts:158-161 | setEnabled(false); 200 { message: 'Webhook paused' } |
| POST | /api/v1/webhooks/:id/resume | webhook.update | webhooks.service.ts:158-161 | setEnabled(true); 200 { message: 'Webhook resumed' } |
Worker POST (not a client API, but part of the contract consumers must handle):
| Field | Value | Source |
| Method | POST | webhook-delivery.worker.ts:73 |
Content-Type | application/json | :76 |
X-Webhook-Signature | HMAC-SHA256 hex of raw body using webhook.secret | :77, 103-105 |
X-Webhook-Event | eventType | :78 |
| Body | JSON.stringify(payload) (the DomainEvent.payload, domain-event.interface.ts:7) | :70, 80 |
| Timeout | AbortSignal.timeout(10000) - 10 s | :81 |
| Retries | 3 attempts, exponential backoff 5 s (job options) | webhooks.service.ts:80-83 |
| Success | 2xx → log success with responseCode + response body | :84-89 |
POST /api/v1/webhooks
{ "name": "SMS Gateway", "url": "https://vendor.example.com/hooks/sms",
"events": ["AttendanceMarked", "HomeworkSubmitted"],
"secret": "whsec_9f2c…", "enabled": true }
PATCH /api/v1/webhooks/64f…
{ "events": ["AttendanceMarked", "PaymentCompleted"], "enabled": false }
POST /api/v1/webhooks/64f…/retry → { "message": "Retry queued" }
POST /api/v1/webhooks/64f…/test → { "message": "Test delivery queued" }
GET /api/v1/webhooks/64f…/metrics → { "data": { "total": 9, "success": 7, "failed": 1, "pending": 1 } }
- List/single/created/updated:
{ data: <Doc> } (envelope interceptor,
00-shared/07). Webhook doc fields: _id, name, url, events, secret, enabled, lastTriggeredAt?, failureCount, tenantId, isDeleted, version, createdAt, updatedAt (webhook.schema.ts:8-30, base.schema.ts:10-34).
- Logs:
{ data: DeliveryLog[] }; fields _id, webhookId, eventType, payload, status, responseCode?, responseBody?, attemptCount, attemptedAt, completedAt?
(webhook-delivery-log.schema.ts:7-39).
- Action endpoints:
{ message: string }.
| Code | Meaning | Source |
| 401 | unauthenticated (JWT guard pending wiring - AGENTS.md) | webhooks.controller.ts:21-90 |
| 403 | missing webhook.* permission | permissions.constants.ts:89-92 |
| 400 | DTO validation (class-validator) | create-webhook.dto.ts:11-34 |
| 404 | webhook missing | webhooks.service.ts:41, 46, 51 |
| 404 | retry with no failed delivery | webhooks.service.ts:107-108 |
| 500 | queue enqueue / DB failure | - |
| Path / feature | Status | Source |
| Inbound public webhook receiver | (planned) | IMPLEMENTATION_PLAN.md:48 ("(webhooks, health)", 30 req/min, 1 min window) |
Logs pagination/filtering (?page&limit&status) | (planned) - hard cap 50 today | webhooks.service.ts:88-93 |
| Secret rotation endpoint (generate + rotate in one call) | (planned) - PATCH only | webhooks.controller.ts:39-43 |
| Replay/backfill of missed events | (planned) - only latest-failure retry | webhooks.service.ts:95-120 |
X-Webhook-Timestamp / replay protection | (planned) - body-only signature | webhook-delivery.worker.ts:103-105 |
| Test-series vendor integration via webhooks | (planned) | IMPLEMENTATION_PLAN.md:856 |
Event-type registry endpoint (GET /events) | (planned) - picker vocabulary is static | event-queue-map.ts:6-43 |