Browser Relay Setup
Route browser SDK events through your backend for credential isolation and origin validation.
The browser relay lets your Browser SDK send events through your own backend instead of directly to the DebugBundle ingestion API. This keeps your project token on the server, adds origin validation, and gives server SDKs the same local-only and connected delivery behavior for browser-origin events.
Why Use a Relay?
| Concern | Direct Transport | Relay Transport |
|---|---|---|
| Project token exposure | Token in client-side code | Token stays on server |
| Origin validation | Optional token allowlist, spoofable outside browsers | Server validates request origin before credentialed forwarding |
| Rate limiting | Per-client IP | Centralized per-IP limiting |
| Durable writes | Browser-only (beacon) | Server-side spool to disk |
| Works without backend | ✅ | ❌ Requires your backend |
If your app has no backend (static site, SPA hosted on CDN), use direct transport instead. The Browser SDK uses beacon/keepalive for reliable delivery on page unload.
Direct browser project tokens are public write-only ingestion keys. Add allowed browser origins in the dashboard, CLI, API, or MCP for static sites to block casual browser copy/paste abuse, but do not treat it as a secret boundary because non-browser clients can spoof Origin.
Architecture
Client
Browser SDK
Sends frontend events to your backend relay route.
Backend
Relay Handler
Validates origin, applies body limits, rate-limits by IP, and attaches the project token.
Optional
Durable Spool
Writes events to .debugbundle/local/browser-relay-spool/ before forwarding in connected mode.
DebugBundle
Ingestion API
Receives the sanitized event batch with server-side credentials.
Supported Backends
| Backend surface | Relay support |
|---|---|
| Node.js | Express, Fastify, and Next.js relay handlers from @debugbundle/sdk-node |
| Python | BrowserRelayHandler plus Django, Flask, and FastAPI helpers from debugbundle-python |
| PHP | DebugBundle\Relay\BrowserRelayHandler plus Laravel middleware and Symfony controller adapters |
| WordPress | Built-in WordPress REST relay route through the DebugBundle WordPress plugin |
| Java | Spring Boot route plus servlet/JAX-RS relay adapters from the Java SDK |
| .NET | ASP.NET Core endpoint mapping and middleware from DebugBundle.AspNetCore |
| Go | net/http relay handler plus Gin and Echo mounting paths from the Go SDK |
| Ruby | Rack middleware plus Rails route mounting from the Ruby SDK |
Planned server SDKs, such as Kotlin server and Rust, must implement the same relay contract before public docs describe them as full relay-handler compatible. Client/mobile SDKs such as Android, iOS, and React Native do not host browser relay routes.
The Browser SDK is the relay client path. It sends browser events to one of the backend handlers above; it is not itself a backend relay handler.
Relay Configuration
Full relay handlers accept language-idiomatic equivalents of these options. JavaScript uses camelCase, Python uses snake_case, and PHP uses array keys.
| Option | Type | Default | Description |
|---|---|---|---|
projectMode | string | connected | connected forwards with the server token; local-only writes accepted events for local CLI processing. |
projectToken | string | — | Project token for ingestion auth. |
allowedOrigins | string[] | Auto from Host | Origins permitted to send events. If empty, validates against Host/X-Forwarded-Host. |
rateLimitPerMinute | number | 60 | Max requests per IP per minute. |
rateLimitStore | implementation | In-memory | Optional shared/persistent limiter store for runtimes where process memory is not reliable. |
maxBodyBytes | number | 262144 (256 KB) | Max request body size. Requests exceeding this are rejected. |
durableWrite | boolean | true | Persist events to a local spool before forwarding to DebugBundle cloud. |
localEventsDir | string | .debugbundle/local/events/ | Directory for local-only relay event files. |
spoolDir | string | .debugbundle/local/browser-relay-spool/ | Directory for durable spool files. Use persistent storage in containers or pods if you need the spool to survive pod replacement. |
endpoint | string | https://api.debugbundle.com/v1/events | Override ingestion endpoint (for self-hosted). |
environment | string | — | Override service.environment on relayed events. |
service | string | — | Override service.name on relayed events. |
Accepted Event Types
The relay only accepts browser-origin event types:
frontend_exceptionerror_suppressedfrontend_breadcrumbrequest_eventprobe_event
Server-side-only event types (backend_exception, log_event) are rejected with 400 Bad Request.
Node.js
Express
const express = require('express');
const { debugBundleRelay } = require('@debugbundle/sdk-node');
const app = express();
app.use('/debugbundle/browser', debugBundleRelay({
projectToken: process.env.DEBUGBUNDLE_PROJECT_TOKEN,
allowedOrigins: ['https://myapp.com', 'https://staging.myapp.com'],
}));
app.listen(3000);The Express relay handler parses the request body, validates the origin, applies rate limiting, forwards accepted events to the ingestion API, and answers allowed OPTIONS preflight requests when mounted with app.use.
Fastify
const fastify = require('fastify')();
const { debugBundleRelayPlugin } = require('@debugbundle/sdk-node');
await fastify.register(debugBundleRelayPlugin, {
projectToken: process.env.DEBUGBUNDLE_PROJECT_TOKEN,
routePath: '/debugbundle/browser',
allowedOrigins: ['https://myapp.com'],
});
await fastify.listen({ port: 3000 });The Fastify plugin registers POST and OPTIONS routes. Pass routePath to customize the endpoint path (default: /debugbundle/browser).
Next.js (App Router)
// app/api/debugbundle/browser/route.ts
import { createNextjsRelayHandler } from '@debugbundle/sdk-node';
const handler = createNextjsRelayHandler({
projectToken: process.env.DEBUGBUNDLE_PROJECT_TOKEN,
allowedOrigins: ['https://myapp.com'],
});
export async function POST(request: Request): Promise<Response> {
return handler(request);
}
export async function OPTIONS(request: Request): Promise<Response> {
return handler(request);
}The Next.js handler uses the Web Request/Response APIs. It extracts the client IP from X-Forwarded-For or X-Real-IP headers and can be exported for both POST and OPTIONS.
Python
The Python SDK exposes a framework-agnostic BrowserRelayHandler plus helpers for Django, Flask, and FastAPI.
Flask
import os
from debugbundle.integrations import create_flask_relay_handler
register_relay = create_flask_relay_handler(
allowed_origins=["https://myapp.com"],
project_mode="connected",
project_token=os.environ["DEBUGBUNDLE_TOKEN"],
endpoint="https://api.debugbundle.com/v1/events",
)
register_relay(app)FastAPI
import os
from debugbundle.integrations import create_fastapi_relay_handler
register_relay = create_fastapi_relay_handler(
allowed_origins=["https://myapp.com"],
project_mode="connected",
project_token=os.environ["DEBUGBUNDLE_TOKEN"],
endpoint="https://api.debugbundle.com/v1/events",
)
register_relay(app)For Django, use create_django_relay_view(...) and mount the returned view at /debugbundle/browser in your URL configuration.
PHP
The PHP SDK exposes DebugBundle\Relay\BrowserRelayHandler plus Laravel and Symfony adapters.
use DebugBundle\Relay\BrowserRelayHandler;
$relay = new BrowserRelayHandler([
'allowedOrigins' => ['https://myapp.com'],
'projectMode' => 'connected',
'projectToken' => $_ENV['DEBUGBUNDLE_TOKEN'],
'endpoint' => 'https://api.debugbundle.com/v1/events',
]);Laravel applications can use DebugBundle\Framework\Laravel\DebugBundleRelayMiddleware; Symfony applications can use DebugBundle\Framework\Symfony\DebugBundleRelayController.
.NET
ASP.NET Core applications can host the relay through endpoint routing:
using DebugBundle.AspNetCore.Relay;
builder.Services.Configure<DebugBundleRelayOptions>(relay =>
{
relay.AllowedOrigins.Add("https://myapp.com");
});
var app = builder.Build();
app.MapDebugBundleBrowserRelay("/debugbundle/browser");If authentication protects all routes, leave the relay endpoint unauthenticated and rely on the relay's origin validation, content type, body-size limit, schema validation, credential stripping, and rate limit controls:
app.MapDebugBundleBrowserRelay("/debugbundle/browser")
.AllowAnonymous();WordPress
The DebugBundle WordPress plugin registers the WordPress REST relay route for you. Use the plugin settings screen to configure backend/frontend capture, relay delivery, and test events instead of wiring the PHP relay handler manually.
Browser SDK Configuration
Point the Browser SDK at your relay endpoint:
import debugbundle from '@debugbundle/sdk-browser';
debugbundle.init({
// No projectToken needed on the client — the relay adds it
transportMode: 'relay',
endpoint: '/debugbundle/browser',
});For split frontend/backend deployments, keep transportMode: 'relay' and use the backend relay URL:
debugbundle.init({
transportMode: 'relay',
endpoint: 'https://api.example.com/debugbundle/browser',
});When using relay transport, do not set projectToken in the Browser SDK config. The relay handler adds the token server-side.
Cross-origin relay URLs require the relay route to answer browser CORS preflight for OPTIONS /debugbundle/browser. Keep the relay's allowedOrigins allowlist aligned with the frontend origins that may submit events.
Static-Only Direct Transport
Use direct transport only when there is no backend route that can host a relay. Create a dedicated project token with an origin allowlist. In the dashboard, use Project → Tokens → Create project token → Allowed browser origins and enter one origin per line or comma-separated. From the CLI:
debugbundle token project create proj_01HXYZ... \
--label "static-site-browser" \
--allowed-origin https://www.example.comThen initialize the Browser SDK with that token:
import debugbundle from '@debugbundle/sdk-browser';
debugbundle.init({
projectToken: 'dbundle_proj_...',
environment: 'production',
service: 'static-site',
});When allowed_origins is set, DebugBundle requires matching Origin headers for direct POST /v1/events and GET /v1/sdk/config calls. Requests from other browser origins, or requests without an Origin header, receive 403 origin_not_allowed. Do not reuse an allowlisted direct-browser token for server SDKs or relay forwarding; those paths should use a project token with no allowed_origins.
Origin Validation
The relay validates the request origin to prevent unauthorized event submission:
-
With
allowedOrigins: The relay extracts the origin from theOriginheader (or falls back toReferer). The origin is normalized and compared against the allowlist. -
Without
allowedOrigins: The relay checks that the request origin matches theHostorX-Forwarded-Hostheader (same-origin validation).
Requests that fail origin validation receive 403 Forbidden.
Rate Limiting
The relay includes per-IP rate limiting:
- Default: 60 requests per minute per IP address
- Tracking: In-memory sliding window
- Exceeded:
429 Too Many Requestsresponse
For PHP shared-nothing runtimes, pass a rateLimitStore implementation backed by shared or persistent storage. WordPress uses platform storage for persistent relay limiting.
Configure with rateLimitPerMinute:
debugBundleRelay({
projectToken: process.env.DEBUGBUNDLE_PROJECT_TOKEN,
rateLimitPerMinute: 120, // 2 requests per second
});Durable Writes
In connected mode, durable writes are enabled by default. Use a dedicated spoolDir when you want the relay spool on a persistent volume:
debugBundleRelay({
projectToken: process.env.DEBUGBUNDLE_PROJECT_TOKEN,
durableWrite: true,
spoolDir: '/var/spool/debugbundle',
});With durable writes enabled:
- Events are written to the spool directory immediately
- The relay forwards the accepted batch to the ingestion API
- If forwarding fails, the spool file remains on disk for inspection and manual recovery
Run debugbundle doctor --check-relay to report undelivered spool files and their age.
If you do not want a local relay spool, set durableWrite: false. In ephemeral containers or pods, that removes the persistent handoff entirely and the relay depends on immediate forwarding only.
Production Checklist
- Use a shared or persistent
rateLimitStorefor PHP deployments where each request can run in a fresh process. WordPress already uses platform storage for persistent relay limiting. - Put
spoolDiron persistent storage for connected durable mode in containers, pods, or autoscaled hosts. If the directory is ephemeral, retained relay events disappear when the instance is replaced. - Run
debugbundle doctor --check-relayfrom the same filesystem context that owns.debugbundle/local/browser-relay-spool/so operators can see undelivered relay backlog before cleanup. - Run
debugbundle cleanon the normal local maintenance cadence; delivered relay spool entries are pruned after 24 hours and undelivered entries after 7 days. - Prefer explicit
allowedOriginsin production, especially behind reverse proxies or custom domains. If relying on same-origin defaults, make sure the framework's trusted host/proxy configuration is correct before trusting forwarded host headers. - Mount the relay route before generic body-parser, auth, or CSRF middleware that could consume the raw body, reject browser session cookies, or require non-relay credentials. DebugBundle's own API keeps
POST /debugbundle/browserexempt from browser-session CSRF and relies on relay origin validation for that route.
Trace Correlation
When using relay transport, the Browser SDK still injects X-DebugBundle-Trace-Id headers into same-origin fetch() and XMLHttpRequest calls, plus cross-origin first-party requests that match tracePropagationTargets. Your backend SDK middleware reads this header and attaches the trace ID to server-side events, linking frontend breadcrumbs to backend exceptions in the same bundle.
Relay handlers preserve browser correlation fields (request_id, trace_id, session_id, and user_id_hash) when they are strings or null; they never regenerate or overwrite correlation.trace_id.
Next Steps
- Browser SDK — Full Browser SDK configuration
- Node.js SDK — Backend SDK with framework integrations
- Python SDK — Django, Flask, FastAPI, and relay helpers
- PHP SDK — Laravel, Symfony, Monolog, and relay setup
- .NET SDK — ASP.NET Core relay setup
- WordPress Plugin — WordPress REST relay integration
- API Authentication — Token types and scope separation
- Security — Token security and redaction