AP-T11-008 — Webhook Observability (Prometheus + Alertmanager)
Status: OPEN — to be executed before production launch
Owner: Channel-service team
Audit code: AP-T11-008
Branch convention: audit/AP-T11-008-webhook-observability
Spawned by: AP-T11-007 (ResAvenue cert single endpoint, 2026-04-30)
Background
AP-T11-007 shipped the single inbound endpoint POST /api/webhooks/resavenue/booking and proved end-to-end on QA. During that work we discovered four latent bugs (JwtAuthGuard 401, missing XML parser, SanitizeInterceptor stripping XML, missing tenantId on inserts) — all of which had been silently broken on the legacy /reservation and /cancellation routes for over a month because no inbound webhook traffic had ever reached the service from outside. We had no way to know the routes were broken because we had no observability on webhook_events.
This ticket addresses that gap permanently.
Goal
Make every inbound webhook event observable as a Prometheus counter, and surface the high-signal failure modes (auth rejection rate, parse errors, kafka-publish failures) on Alertmanager → Slack/PagerDuty using the existing routing in hmp-infrastructure/monitoring/alerts/.
This is not ResAvenue-specific — the same observability covers STAAH, eZee, ChannelRush, UrbanPiper, and any future intermediary plugged into AbstractIntermediaryWebhookController or WebhookEvent-writing controller.
Deliverables
1. Counter (channel-service)
Add a Prometheus counter to @hmp/observability (or to hmp-channel-service directly if @hmp/observability is too generic):
const webhookEventsTotal = new Counter({
name: 'channel_webhook_events_total',
help: 'Inbound webhook events received by channel-service, partitioned by provider, eventType, and status',
labelNames: ['provider', 'eventType', 'status', 'tenantId'] as const,
});
Increment in AbstractIntermediaryWebhookController.persistWebhookEvent() after the DB insert. The existing tenantId fallback (sentinel UUID for unmatched payloads) flows through naturally.
2. Alertmanager rules (hmp-infrastructure)
New file hmp-infrastructure/monitoring/alerts/hmp-channel-webhook-alerts.yaml:
| Alert | Condition | Severity | Routing |
|---|---|---|---|
WebhookAuthRejectionRate | rate(channel_webhook_events_total{status=~"REJECTED_AUTH|REJECTED_IP|REJECTED_NO_SECRET"}[5m]) > 0.5 | warning | Slack #ops |
WebhookParseErrorRate | rate(channel_webhook_events_total{status="PARSE_ERROR"}[5m]) > 0.1 | warning | Slack #ops |
WebhookKafkaPublishFailure | rate(channel_webhook_events_total{status="RETRY_PENDING"}[10m]) > 0 | critical | PagerDuty |
WebhookSilenceFromActivePartner | absent(rate(channel_webhook_events_total{provider!=""}[6h])) and on() time() - <last_known_partner_activity_time> > 6h | warning | Slack #ops |
WebhookFirstInboundFromPartner | increase(channel_webhook_events_total{provider="<X>", status="ACCEPTED"}[1h]) > 0 and last value 0 | info | Slack #ops |
The last rule (WebhookFirstInboundFromPartner) is the cert-time alert — parameterised so we add a one-line entry per partner during cert and silence/remove after sign-off.
3. Grafana dashboard panel
Add to hmp-infrastructure/monitoring/dashboards/hmp-channel-service.json:
- Webhook events / 5min (stacked by status) — line chart
- Webhook auth rejection rate by provider — line chart, last 24h
- First-time provider seen — table, last 7 days
4. Documentation
Update hmp-channel-service/CLAUDE.md "Inbound Webhooks" table with the metric name + alert names. Update hmp-docs/docs/integrations/resavenue-integration-guide.md Part 9 with the new WebhookFirstInboundFromPartner rule the cert team should silence after sign-off.
Effort estimate
- Counter + persist hook wiring: ~30 min
- Alertmanager YAML + Grafana JSON: ~30 min
- Doc updates: ~15 min
- QA smoke (synthetic events through
/bookingshould bump the counter): ~15 min
Total: ~90 min, single PR.
When to execute
Before production launch. Tracking signal: any go/no-go review for production where ResAvenue or another intermediary is critical-path. The same review that flips CHANNEL_MODE=live should require this ticket green.
Definition of done
- Counter shipped + visible at
:9464/metrics(channel-service) - Alertmanager rules merged + loaded by Prometheus
- One synthetic test through
/api/webhooks/resavenue/bookingbumps the counter as expected - One synthetic auth-fail bumps
REJECTED_AUTHand (if rate threshold reached) fires the alert - CLAUDE.md + integration guide updated
- Cert-time
WebhookFirstInboundFromPartnerrule silenced/removed for ResAvenue