docs: add web callback docs#2973
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@claude review |
There was a problem hiding this comment.
LGTM — docs-only addition for a new feature; the inline nits are cosmetic and the github-stars bump is routine.
Extended reasoning...
Overview
This PR adds a new docs page at content/docs/observability/features/web-callbacks.mdx documenting the Web Callbacks feature, registers it in the sibling meta.json sidebar config, and bumps the cached GITHUB_STARS constant. No production application code is touched.
Security risks
None substantive. The doc itself contains correct security guidance (warns against putting secrets in browser-visible headers, recommends keeping API credentials server-side, recommends validating bodies in production). The example receiver listens on 127.0.0.1 and is presented as a minimal illustration.
Level of scrutiny
Low. This is a docs-only PR plus a mechanical star-count refresh; no logic, auth, or rendering code is affected. The risk surface is limited to user-facing copy.
Other factors
Two nits were flagged inline (casing inconsistency between title/sidebarTitle/H1, and prose claiming the minimal receiver "validates" the payload when it only does a TS type assertion). Both are cosmetic and don't block approval — the author can address them in a follow-up if desired.
| - Return any HTTP `2xx` status for success. | ||
| - Respond before the configured timeout. | ||
| - Allow browser requests from your Langfuse origin via CORS. | ||
| - Respond to CORS preflight `OPTIONS` requests if you configure custom headers. |
There was a problem hiding this comment.
🔴 The prose at lines 69 and 155 says CORS preflight is needed "if you configure custom headers", but a POST with Content-Type: application/json (which Langfuse sends, per line 36) always triggers a CORS preflight regardless of any custom headers — application/json is not a CORS-safelisted Content-Type per the Fetch spec. The example code on lines 112–116 correctly handles OPTIONS unconditionally, but a reader following the prose literally could omit OPTIONS handling assuming they have no custom headers and then hit a preflight failure on the first callback. Suggested fix: reword both lines to attribute preflight to Content-Type: application/json rather than custom headers.
Extended reasoning...
What the bug is. Lines 69 and 155 of content/docs/observability/features/web-callbacks.mdx both condition CORS preflight handling on "custom headers":\n\n- Line 69 (Endpoint requirements): "Respond to CORS preflight OPTIONS requests if you configure custom headers."\n- Line 155 (Troubleshooting → CORS error): "If you use custom headers, the browser may send an OPTIONS preflight request before the POST; your server must handle that request too."\n\nBoth statements are incorrect. Per the Fetch spec, only three Content-Type values qualify a request as a CORS "simple request": application/x-www-form-urlencoded, multipart/form-data, and text/plain. application/json is not safelisted, so any POST with that Content-Type triggers a preflight unconditionally — custom headers or not.\n\nWhy this contradicts the doc itself. Three independent signals in the same file confirm preflight is always required:\n\n1. Line 36 explicitly says "Langfuse sends a JSON POST request", i.e. Content-Type: application/json.\n2. Line 110 of the example sets Access-Control-Allow-Headers: "content-type" — that header is only meaningful when Content-Type itself is a non-safelisted value and therefore subject to preflight checks.\n3. Lines 112–116 of the example handle OPTIONS unconditionally, with no "if custom headers" branch. The example correctly reflects that preflight is always needed; only the prose is wrong.\n\nImpact. This is a docs-accuracy bug, not a runtime bug — but it actively misleads implementers on their very first integration. A reader who configures no custom headers will follow line 69 literally, skip OPTIONS handling in their receiver, and the browser will reject the request before any POST body is sent. The error will surface as a CORS preflight failure, which is exactly the symptom described in the troubleshooting section the prose misexplains.\n\nStep-by-step proof. Suppose a user reads the doc and writes a minimal receiver that handles only POST (because they configured no custom headers):\n\n1. User triggers a web callback from a Langfuse trace.\n2. Browser sees outbound request: POST <endpoint> with Content-Type: application/json.\n3. Per Fetch spec §4.3, this is not a simple request (application/json is not in the safelisted Content-Type set), so the browser issues a CORS preflight: OPTIONS <endpoint> with Access-Control-Request-Method: POST and Access-Control-Request-Headers: content-type.\n4. The user's receiver returns 405 Method Not Allowed (or whatever its non-POST default is) — the preflight fails.\n5. The browser never sends the actual POST. Langfuse shows the "CORS error" toast.\n6. The user re-reads line 69, sees "if you configure custom headers", and is confused because they didn't configure any.\n\nHow to fix. Reword both lines to attribute preflight to the Content-Type, not custom headers. For example:\n\n- Line 69: "Respond to CORS preflight OPTIONS requests, because the callback uses Content-Type: application/json."\n- Line 155: "Because the request uses Content-Type: application/json, the browser sends an OPTIONS preflight before the POST; your server must handle that request too."
| const server = http.createServer((req, res) => { | ||
| res.setHeader("Access-Control-Allow-Origin", "https://cloud.langfuse.com"); | ||
| res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); | ||
| res.setHeader("Access-Control-Allow-Headers", "content-type"); |
There was a problem hiding this comment.
🟡 The example sets Access-Control-Allow-Headers: "content-type" but step 4 above invites users to configure "optional browser-visible headers" on the endpoint. Any custom header configured in the Langfuse UI (e.g. X-Trace-Source) will appear in the preflight's Access-Control-Request-Headers and be rejected by this static allowlist — causing a confusing CORS failure. Consider adding a comment noting that any custom headers configured in Langfuse must be appended to Access-Control-Allow-Headers, or calling this out in the troubleshooting CORS section (which currently mentions preflight but not the Allow-Headers requirement).
Extended reasoning...
What the bug is. In content/docs/observability/features/web-callbacks.mdx, the "Configure a web callback" section (step 4, line 23) tells the reader they can configure "optional browser-visible headers" on the endpoint. The "Browser-side delivery" section (line 81) reiterates that custom headers are visible in browser dev tools. But the minimal receiver example (line 110) hardcodes:
res.setHeader("Access-Control-Allow-Headers", "content-type");A reader who follows step 4 and adds any custom header on the Langfuse side will hit a CORS preflight failure the moment they wire up this example.
The code path that triggers it. Per the CORS spec, when the browser issues a non-simple request (here a JSON POST with a custom header), it first sends an OPTIONS preflight whose Access-Control-Request-Headers lists every non-CORS-safelisted header the actual request will send. The server's Access-Control-Allow-Headers response must be a superset of that list, or the browser rejects the preflight and never issues the POST.
Why existing code/docs don't prevent it. The "Endpoint requirements" section (line 69) acknowledges that custom headers trigger preflight. The "Troubleshooting > CORS error" section (line 155) repeats that the server must handle preflight — but neither section actually names Access-Control-Allow-Headers or tells the reader to extend it. The "For production" guidance (line 145) calls out tightening Access-Control-Allow-Origin, validating the body, and keeping credentials on the server, but never mentions Allow-Headers. So a user who diligently reads the whole page can still miss this.
Impact. Pure documentation completeness. The example works fine for the default no-custom-headers case (which is why this is a nit, not a normal-severity bug). But the doc itself invites the very configuration that breaks the example, and the failure mode — a CORS preflight error toast on first click — is exactly the kind of friction users blame the example for.
Step-by-step proof.
- User follows step 4 and adds a custom header
X-Trace-Source: uion the Langfuse endpoint config. - User copies the minimal receiver example verbatim and runs it on
http://127.0.0.1:4047. - User clicks "Call ..." on a trace. The browser prepares a
POSTwith headerscontent-type: application/jsonandx-trace-source: ui. - Because
x-trace-sourceis not on the CORS-safelisted-request-header list, the browser first sendsOPTIONS /withAccess-Control-Request-Headers: content-type, x-trace-source. - The example server responds
204withAccess-Control-Allow-Headers: content-type. - The browser sees
x-trace-sourceis missing from the allowlist and aborts the preflight — thePOSTis never sent. - Langfuse surfaces a CORS-error toast; nothing in the troubleshooting section points the reader back to the example's hardcoded Allow-Headers value.
How to fix. The lowest-friction fix is a one-line code comment in the example, e.g. // Append any custom headers configured in Langfuse (e.g. "content-type, x-trace-source"). Alternatively, extend the "CORS error" troubleshooting section to explicitly name Access-Control-Allow-Headers and tell readers to list every custom header configured in the Langfuse UI.
No description provided.