Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d8a38d9
docs: add web callback docs
nimarb May 19, 2026
822ef7d
bump
nimarb May 22, 2026
e8ea427
Merge branch 'main' into feature/lfe-9876-web-callbacks-docs-page
nimarb May 22, 2026
18402da
feat(launch-week-5): add teaser landing page (#2980)
jannikmaierhoefer May 22, 2026
6251be4
docs: update website banner for launch week
clemra May 22, 2026
049e044
ci(deps): bump the github-actions group with 2 updates (#2983)
dependabot[bot] May 22, 2026
796cba7
docs(claude-code): add YouTube video embed to integration page (#2999)
CodeCLS May 24, 2026
7eb5881
docs: correct annotation queues typo (#3001)
maxdeichmann May 25, 2026
d96d227
feat(launch-week-5): day 1 — experiments in CI/CD (#3000)
jannikmaierhoefer May 25, 2026
a56ba5f
docs(guides): add guide on calibrating LLM-as-a-judge with the Langfu…
Lotte-Verheyden May 26, 2026
83d65a9
[codex] Add workshop section (#3003)
marcklingen May 26, 2026
13a8b2e
docs(claude-code): add NEW callout for plugin marketplace install (#3…
CodeCLS May 26, 2026
9805fa5
feat(launch-week-5): day 2 — Langfuse agent skill (#2974)
marliessophie May 26, 2026
6e5c134
Add Hermes Agent integration page (#2887)
jannikmaierhoefer May 26, 2026
c183da9
docs(security): credit alcls01111 for SSRF blob storage disclosure (#…
Steffen911 May 27, 2026
dac5ded
feat(launch-week-5): day 3 — Faster search in Fast Mode (#3010)
sumerman May 27, 2026
6865b16
docs: update launch page
jannikmaierhoefer May 27, 2026
4222af9
docs: update launch message
jannikmaierhoefer May 27, 2026
393cff0
docs: update FTS message
jannikmaierhoefer May 27, 2026
573ba12
chore: bump langfuse sdk
hassiebp May 27, 2026
fbca7f2
[codex] Fix formatting on main (#3014)
wochinge May 28, 2026
3fb3c5f
docs(evaluation): restructure evaluation workflow (#2939)
wochinge May 28, 2026
50a99b0
Enable CI on main and manual trigger + add Slack notification (#3015)
bezbac May 28, 2026
5b642ab
fix: Do not initialize PostHog if the key is missing (#3016)
bezbac May 28, 2026
a5cbe50
fix: Disable inkeep if no API key is provided (#3017)
bezbac May 28, 2026
b869255
docs(self-hosting): document AWS SES email transport (#3019)
Steffen911 May 28, 2026
13dd821
feat(launch-week-5): day 4 — Code evaluators (#3006)
wochinge May 28, 2026
4b7ed7e
Added growth hiring process (#3013)
leonardwolters May 28, 2026
546b40a
docs: update code evaluator changelog
jannikmaierhoefer May 28, 2026
9ba473e
fix(notebook-banner): label JS cookbook notebooks as TypeScript (#3021)
Lotte-Verheyden May 28, 2026
7debb0f
docs(agents): add CI format check guidance to AGENTS and cursor rules…
Lotte-Verheyden May 28, 2026
37b1399
chore: move code evaluator creation gif to CDN (#3023)
wochinge May 29, 2026
9d4b72f
docs: update code evals video thumbnail
jannikmaierhoefer May 29, 2026
49b2ae3
feat(launch-week-5): day 5 — Langfuse MCP, expanded (#2988)
bezbac May 29, 2026
5d73841
ci(deps): bump zizmorcore/zizmor-action from 0.5.5 to 0.5.6 in the gi…
dependabot[bot] May 29, 2026
a5318ca
Update Exa integration examples to use search() (#2963)
10ishq Jun 1, 2026
aee1b14
feat(analytics): add Google Ads conversion tracking (#3028)
jannikmaierhoefer Jun 1, 2026
dd45764
docs: update README banner
jannikmaierhoefer Jun 1, 2026
96e7cf9
docs(handbook/support): expand how-to-answer guide as decision tree (…
CodeCLS Jun 1, 2026
e48994f
fix
nimarb Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions content/docs/observability/features/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"comments",
"corrections",
"user-feedback",
"web-callbacks",
"log-levels",
"agent-graphs",
"masking",
Expand Down
165 changes: 165 additions & 0 deletions content/docs/observability/features/web-callbacks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
title: Web callbacks
description: Trigger a browser-side HTTP callback from a trace or observation in Langfuse.
sidebarTitle: Web Callbacks
---

# Web callbacks

Check warning on line 7 in content/docs/observability/features/web-callbacks.mdx

View check run for this annotation

Claude / Claude Code Review

Inconsistent casing between title, sidebarTitle, and H1

The frontmatter `title` is 'Web callbacks' (lowercase c) and the H1 matches, but `sidebarTitle` is 'Web Callbacks' (uppercase C) — an internal inconsistency on a single page. All 19 sibling feature docs in this folder keep `title` and `sidebarTitle` in matching casing, and the configuration steps later in this file refer to 'Web Callbacks' (Title Case), so aligning all three (title, sidebarTitle, H1) to 'Web Callbacks' would match the established convention.
Comment thread
claude[bot] marked this conversation as resolved.

Web callbacks let project members trigger an HTTP `POST` request from a trace or observation in Langfuse. Use them to connect trace debugging to your own tools, for example opening an internal investigation workflow, notifying an external system, or sending a selected trace ID to a local helper service.

Unlike [prompt webhooks](/docs/prompt-management/features/webhooks-slack-integrations), web callbacks are not event-driven automations. A project member manually triggers them from the trace detail menu.

<Callout type="info">
Web callbacks send only identifiers. Trace input, output, metadata, scores,
and user data are not included in the callback payload.
</Callout>

## Configure a web callback

1. Open your project in Langfuse.
2. Go to `Project Settings` > `Integrations` > `Web Callbacks`.
3. Create or edit the callback endpoint.
4. Configure the endpoint name, URL, toast message, request timeout, and optional browser-visible headers.
5. Save the endpoint and make sure it is enabled.

Only one web callback endpoint can be enabled per project.

## Trigger a callback

Open a trace or observation and click the three-dot actions menu in the detail header. If an enabled endpoint exists, the menu shows `Call <callback name>`.

When you click the action, Langfuse immediately shows the configured toast message and sends the request from your browser to the configured endpoint.

## Request payload

Langfuse sends a JSON `POST` request with the following payload:

```json filename="web-callback-payload.json"
{
"version": 1,
"items": [
{
"projectId": "project-id",
"traceId": "trace-id",
"observationId": null
}
]
}
```

Payload fields:

- `version`: Payload contract version. The current version is `1`.
- `items`: List of selected objects. V1 sends one item.
- `projectId`: Langfuse project ID.
- `traceId`: Trace ID for the selected trace or observation.
- `observationId`: `null` for trace-level callbacks, or the selected observation ID for observation-level callbacks.

If your receiver needs the full trace, observation, session, or score data, use the IDs from the payload to query the [Langfuse API](/docs/api-and-data-platform/features/public-api) from your own backend.

## Endpoint requirements

Your endpoint must:

- Accept `POST` requests with a JSON body.
- 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."


Langfuse treats non-2xx responses, timeouts, network errors, and CORS failures as failed callbacks and shows an error toast.

## Browser-side delivery

Web callbacks are delivered directly from the user's browser with `fetch`.

This has a few practical consequences:

- The receiver sees the user's browser as the client, not Langfuse servers.
- The receiver must allow CORS from the Langfuse origin.
- Custom headers are visible in browser developer tools.
- Headers are not suitable for secrets or private API keys.
- Langfuse does not retry failed callback requests in the background.

<Callout type="warn">
Do not put secrets into web callback headers. If the receiver needs privileged
access to Langfuse data, let the receiver call the Langfuse API with credentials
stored on the receiver side.
</Callout>

## Minimal receiver

The receiver can be any HTTP server. This example validates the payload shape and acknowledges the callback:

Check warning on line 93 in content/docs/observability/features/web-callbacks.mdx

View check run for this annotation

Claude / Claude Code Review

Minimal receiver example does not actually validate the payload

The prose on line 93 says "This example validates the payload shape and acknowledges the callback", but the receiver code does not actually validate anything — `const payload = JSON.parse(body) as WebCallbackPayload` is a TypeScript type assertion with zero runtime effect (no `version === 1` check, no `items` array check, no field/type checks). This is reinforced by line 145 which then advises "For production... validate the request body", implying the example does not. Either soften the prose t
Comment thread
claude[bot] marked this conversation as resolved.
Outdated

```ts filename="server.ts"
import http from "node:http";

type WebCallbackPayload = {
version: 1;
items: Array<{
projectId: string;
traceId: string;
observationId: string | null;
}>;
};

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.


if (req.method === "OPTIONS") {
res.writeHead(204);
res.end();
return;
}

if (req.method !== "POST") {
res.writeHead(405);
res.end("Method not allowed");
return;
}

let body = "";

req.on("data", (chunk: Buffer) => {
body += chunk.toString("utf8");
});

req.on("end", () => {
const payload = JSON.parse(body) as WebCallbackPayload;

console.log("Received Langfuse web callback", payload);

res.writeHead(202, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
});
});

server.listen(4047, "127.0.0.1", () => {
console.log("Listening on http://127.0.0.1:4047");
});
```

For production, set `Access-Control-Allow-Origin` to your Langfuse deployment URL, validate the request body, and keep any Langfuse API credentials on the server.

## Troubleshooting

### Callback endpoint returned HTTP 404

The request reached your endpoint, but the path does not exist. Check the configured URL path and make sure the receiver handles `POST` requests at that route.

### CORS error

Because the request is sent from the browser, the receiver must allow the Langfuse origin. If you use custom headers, the browser may send an `OPTIONS` preflight request before the `POST`; your server must handle that request too.

### Callback request timed out

Increase the timeout in the endpoint settings or make the receiver return a `2xx` response faster. For longer jobs, acknowledge the callback quickly and process the work asynchronously.

## Related

- [Trace URLs](/docs/observability/features/url)
- [Sessions](/docs/observability/features/sessions)
- [Prompt webhooks](/docs/prompt-management/features/webhooks-slack-integrations)
2 changes: 1 addition & 1 deletion src/github-stars.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const GITHUB_STARS = 23619;
export const GITHUB_STARS = 27501;
Loading