Skip to content

docs: add web callback docs#2973

Open
nimarb wants to merge 3 commits into
mainfrom
feature/lfe-9876-web-callbacks-docs-page
Open

docs: add web callback docs#2973
nimarb wants to merge 3 commits into
mainfrom
feature/lfe-9876-web-callbacks-docs-page

Conversation

@nimarb
Copy link
Copy Markdown
Contributor

@nimarb nimarb commented May 19, 2026

No description provided.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label May 19, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
langfuse-docs Ready Ready Preview, Comment May 22, 2026 2:13pm

Request Review

@dosubot dosubot Bot added the docs label May 19, 2026
@github-actions
Copy link
Copy Markdown

@claude review

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread content/docs/observability/features/web-callbacks.mdx
Comment thread content/docs/observability/features/web-callbacks.mdx Outdated
- 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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

  1. User follows step 4 and adds a custom header X-Trace-Source: ui on the Langfuse endpoint config.
  2. User copies the minimal receiver example verbatim and runs it on http://127.0.0.1:4047.
  3. User clicks "Call ..." on a trace. The browser prepares a POST with headers content-type: application/json and x-trace-source: ui.
  4. Because x-trace-source is not on the CORS-safelisted-request-header list, the browser first sends OPTIONS / with Access-Control-Request-Headers: content-type, x-trace-source.
  5. The example server responds 204 with Access-Control-Allow-Headers: content-type.
  6. The browser sees x-trace-source is missing from the allowlist and aborts the preflight — the POST is never sent.
  7. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant