Webhook Reliability: Admin Access Webhook#
Overview#
The admin access webhook (adminAccessWebhook.ts) fires an HTTP notification whenever a Langfuse admin user accesses a project or trace they are not a member of. It is configured via the LANGFUSE_ADMIN_ACCESS_WEBHOOK environment variable. The webhook call is fire-and-forget: errors are logged but never surfaced to the caller.
Key Files#
| File | Purpose |
|---|---|
web/src/server/adminAccessWebhook.ts | Core send logic + dedupe cache |
web/src/server/api/trpc.ts | tRPC middleware that calls the webhook |
web/src/__tests__/async/admin-access-webhook.servertest.ts | Unit tests for sendAdminAccessWebhook |
web/src/__tests__/server/admin-access-webhook-middleware.servertest.ts | Integration tests for middleware |
How It Works#
sendAdminAccessWebhook is called from three tRPC authorization middlewares in trpc.ts:
enforceUserIsAuthedAndProjectMember— fires when an admin accesses a project they are not a session member of , and also when the admin is a session member .enforceTraceAccess— fires when an admin accesses any trace .enforceSessionAccess— fires when an admin accesses any trace session .
These map to the exported procedures protectedProjectProcedure, protectedGetTraceProcedure, and protectedGetSessionProcedure .
Known Reliability Issues#
1. Deduplication Timing Bug#
The dedupe key is recorded in lastWebhookByKey before the fetch call completes . If the POST request fails (network error, timeout, non-2xx response), the dedup window is still consumed — subsequent calls within the 5-minute window are silently dropped without retrying. The dedup window is hardcoded to 5 minutes . The correct fix is to record the key only after a successful response.
2. Missing Fetch Timeout#
fetch is called with no signal / AbortController timeout . If the webhook endpoint is unresponsive, the await fetch(...) hangs indefinitely. Because sendAdminAccessWebhook is await-ed inside the tRPC authorization middleware before next() is called, a hung fetch will stall every middleware execution and block the request for the full lifecycle of the TCP connection — potentially exhausting the Node.js event loop under load.
3. Test Coverage Gaps#
The existing test suite covers normal flow and basic error paths , but does not exercise:
- The dedupe timing bug: no test verifies that a failed fetch does not consume the dedupe window.
- Fetch timeout behavior: no test simulates a hanging
fetchor asserts that the middleware resolves within a bounded time. - The
enforceIsAuthedAndOrgMemberpath (org-level admin access webhook) is not covered by the middleware integration tests .
Deduplication Details#
The dedup key is email:project:org . The cache (lastWebhookByKey) is a module-level Map — it is process-local and does not survive restarts or span multiple server instances. resetAdminAccessWebhookCacheForTests() is exposed for test teardown .
Webhook Payload Shape#
type AdminAccessWebhookPayload = {
email: string;
timestamp: string; // ISO 8601
project: string | null;
org: string | null;
region: string; // NEXT_PUBLIC_LANGFUSE_CLOUD_REGION or "self-hosted"
};
Suggested Fixes#
- Dedupe after success: Move
lastWebhookByKey.set(...)inside thetryblock, after confirmingresponse.ok. - Add a fetch timeout: Wrap the
fetchcall with anAbortSignal.timeout(...)(Node 18+) to bound the maximum wait. - Add regression tests: Test that a failed fetch does not block re-sends within the dedupe window, and that the middleware does not hang when
fetchnever resolves.