HOW IT WORKS
Magpie in 10 minutes.
No SDK. No config file. One function call.
If you have ever fought a YAML file at 11pm, you are in the right place.
Create your project
Sign up and create a project in the Magpie dashboard. You will get a Project ID and an API key.
Keep your API key in environment variables. Never commit it.
Add your environment variables
Create a .env (or .env.local) file in your app and paste this:
MAGPIE_BASE_URL=https://usemagpie.dev
MAGPIE_PROJECT_ID=proj_...
MAGPIE_API_KEY=magpie_...Use your Magpie deployment URL for MAGPIE_BASE_URL (no trailing slash).
Drop in the helper
Copy this function into your backend. Works with Express, Next.js API routes, Fastify, or anything Node.js.
import { initObserver } from './Magpie'
const { track, trackError } = initObserver(
{
projectId: process.env.MAGPIE_PROJECT_ID!,
apiKey: process.env.MAGPIE_API_KEY!,
})
Track a request
Call track() inside any route you want visibility on. Pass an action name. This is what makes your timeline readable. You can also pass captureRequest and captureResponse to capture payloads with automatic recursive redaction of sensitive fields.
// Express example
app.post('/assign-driver', async (req, res) => {
track(req, res, 'assign_driver', {
userId: req.user?.id,
captureRequest: true,
captureResponse: true
})
try {
// your logic
} catch (err) {
trackError(err, req)
res.status(500).json({ error: 'failed' })
}
})Track errors
Call trackError() in your catch blocks. Errors are linked to the request that caused them, grouped by fingerprint, and appear inline on the user timeline and the dedicated Errors view.
Error tracking costs zero tokens.
Open the timeline
In the Magpie dashboard, search any userId to see their complete request history: every action, every failure, in order.
Baselines are computed from successful requests over the last 7 days, and requests that are 2x slower than p95 are automatically flagged as slow so your team can spot regressions early.
This is the view your whole team can read. No translation layer. No "ask an engineer what this span means."
The complete helper file
Copy this into your project as Magpie.ts (or keep the filename you like. We ship the same thing as track.example.ts in this repo).
Below is the real implementation: same types, same ingest URLs, same behaviour you get in production.
import { randomUUID } from "node:crypto";
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from "node:http";
export type MagpieObserverConfig = {
/** Project id from the Magpie dashboard (included in event `meta` for your own debugging). */
projectId: string;
/** Project API key (sent as `x-api-key` to Magpie). */
apiKey: string;
};
type RequestLike = Pick<IncomingMessage, "method" | "headers" | "url"> & {
originalUrl?: string;
baseUrl?: string;
body?: unknown;
magpieStartedAt?: number;
magpieRequestId?: string;
user?: { id?: string; sub?: string };
};
type ResponseLike = (Pick<ServerResponse, "statusCode"> & {
json?: (body: unknown) => unknown;
send?: (body: unknown) => unknown;
on?: (event: string, listener: () => void) => unknown;
once?: (event: string, listener: () => void) => unknown;
off?: (event: string, listener: () => void) => unknown;
removeListener?: (event: string, listener: () => void) => unknown;
magpieResponseBody?: unknown;
magpieResponseCapturePatched?: boolean;
magpieTrackEmissionRegistered?: boolean;
magpieTrackEmitted?: boolean;
}) | { status?: number; json?: (body: unknown) => unknown; send?: (body: unknown) => unknown; on?: (event: string, listener: () => void) => unknown; once?: (event: string, listener: () => void) => unknown; off?: (event: string, listener: () => void) => unknown; removeListener?: (event: string, listener: () => void) => unknown; magpieResponseBody?: unknown; magpieResponseCapturePatched?: boolean; magpieTrackEmissionRegistered?: boolean; magpieTrackEmitted?: boolean };
type TrackOptions = {
userId?: string;
captureRequest?: boolean;
captureResponse?: boolean;
};
const REDACTED_VALUE = "[redacted]";
const SENSITIVE_KEY_NAMES = new Set([
"password",
"token",
"secret",
"authorization",
"auth",
"apikey",
"api_key",
"card",
"cvv",
"pin",
"otp",
"ssn",
"bvn",
"nin",
]);
function scrubSensitivePayload(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(scrubSensitivePayload);
}
if (value && typeof value === "object") {
const input = value as Record<string, unknown>;
const output: Record<string, unknown> = {};
for (const [key, raw] of Object.entries(input)) {
if (SENSITIVE_KEY_NAMES.has(key.toLowerCase())) {
output[key] = REDACTED_VALUE;
} else {
output[key] = scrubSensitivePayload(raw);
}
}
return output;
}
return value;
}
function normaliseBaseUrl(baseUrl: string) {
return baseUrl.replace(/\/+$/, "");
}
function headerValue(
headers: IncomingHttpHeaders | Headers,
name: string,
): string | undefined {
if (headers instanceof Headers) {
const v = headers.get(name);
return v?.trim() || undefined;
}
const lower = name.toLowerCase();
const raw = headers[lower] ?? headers[name];
if (Array.isArray(raw)) {
return raw[0]?.trim() || undefined;
}
return typeof raw === "string" ? raw.trim() || undefined : undefined;
}
function inferRoute(req: RequestLike): string | undefined {
const raw = req.originalUrl ?? req.url ?? "";
try {
const pathname = raw.startsWith("http") ? new URL(raw).pathname : raw.split("?")[0];
const path = pathname || "/";
const base = req.baseUrl ?? "";
if (base && path.startsWith(base)) {
return path || undefined;
}
return `${base}${path}` || undefined;
} catch {
return raw.split("?")[0] || undefined;
}
}
function inferStatus(res: ResponseLike): number | undefined {
if ("statusCode" in res && typeof res.statusCode === "number") {
return res.statusCode;
}
if ("status" in res && typeof (res as { status?: unknown }).status === "number") {
return (res as { status: number }).status;
}
return undefined;
}
function inferUserId(req: RequestLike): string | undefined {
const h = req.headers;
return (
headerValue(h, "x-user-id") ??
headerValue(h, "x-magpie-user-id") ??
req.user?.id ??
req.user?.sub
);
}
function inferRequestId(req: RequestLike): string {
if (req.magpieRequestId) {
return req.magpieRequestId;
}
const h = req.headers;
const requestId = headerValue(h, "x-request-id") ?? randomUUID();
req.magpieRequestId = requestId;
return requestId;
}
function inferDurationMs(req: RequestLike): number | undefined {
const start = req.magpieStartedAt;
if (typeof start !== "number" || !Number.isFinite(start)) {
return undefined;
}
const ms = Date.now() - start;
return Number.isFinite(ms) ? ms : undefined;
}
function fireAndForget(promise: Promise<unknown>) {
void promise.catch(() => {
/* ignore */
});
}
function inferSafeRequestHeaders(headers: IncomingHttpHeaders | Headers): Record<string, string> {
const blocked = new Set(["authorization", "cookie", "x-api-key"]);
const entries: Array<[string, string]> = [];
if (headers instanceof Headers) {
headers.forEach((value, key) => {
const normalisedKey = key.toLowerCase();
if (!blocked.has(normalisedKey)) {
entries.push([key, value]);
}
});
} else {
for (const [key, raw] of Object.entries(headers)) {
const normalisedKey = key.toLowerCase();
if (blocked.has(normalisedKey) || raw == null) {
continue;
}
if (Array.isArray(raw)) {
entries.push([key, raw.join(", ")]);
continue;
}
if (typeof raw === "string") {
entries.push([key, raw]);
}
}
}
return Object.fromEntries(entries);
}
function patchResponseCapture(res: ResponseLike) {
if (res.magpieResponseCapturePatched) {
return;
}
res.magpieResponseCapturePatched = true;
const originalJson = typeof res.json === "function" ? res.json.bind(res) : undefined;
const originalSend = typeof res.send === "function" ? res.send.bind(res) : undefined;
if (originalJson) {
res.json = (body: unknown) => {
res.magpieResponseBody = scrubSensitivePayload(body);
return originalJson(body);
};
}
if (originalSend) {
res.send = (body: unknown) => {
res.magpieResponseBody = scrubSensitivePayload(body);
return originalSend(body);
};
}
}
function sendTrackedEvent(eventUrl: string, apiKey: string, body: Record<string, unknown>) {
fireAndForget(
fetch(eventUrl, {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify(body),
}),
);
}
function canRegisterLifecycle(res: ResponseLike): boolean {
return typeof res.once === "function" || typeof res.on === "function";
}
function detachLifecycleListener(
res: ResponseLike,
event: string,
listener: () => void,
) {
if (typeof res.off === "function") {
res.off(event, listener);
return;
}
if (typeof res.removeListener === "function") {
res.removeListener(event, listener);
}
}
export function initObserver(config: MagpieObserverConfig) {
if (!config.projectId?.trim()) {
throw new Error("Magpie: `projectId` is required.");
}
if (!config.apiKey?.trim()) {
throw new Error("Magpie: `apiKey` is required.");
}
const base = normaliseBaseUrl(process.env.MAGPIE_BASE_URL ?? "");
if (!base) {
throw new Error(
"Magpie: set MAGPIE_BASE_URL to your Magpie deployment origin (e.g. https://app.example.com)",
);
}
const eventUrl = `${base}/api/ingest/event`;
const errorUrl = `${base}/api/ingest/error`;
function track(
req: RequestLike,
res: ResponseLike,
action?: string,
options?: TrackOptions,
) {
if (res.magpieTrackEmissionRegistered) {
return;
}
res.magpieTrackEmissionRegistered = true;
if (options?.captureResponse) {
patchResponseCapture(res);
}
const emitTrackedEvent = () => {
if (res.magpieTrackEmitted) {
return;
}
res.magpieTrackEmitted = true;
const body = {
requestId: inferRequestId(req),
route: inferRoute(req),
method: req.method,
status: inferStatus(res),
duration: inferDurationMs(req),
userId: options?.userId ?? inferUserId(req),
action,
requestHeaders: inferSafeRequestHeaders(req.headers),
requestBody: options?.captureRequest ? scrubSensitivePayload(req.body ?? null) : null,
responseBody: options?.captureResponse ? (res.magpieResponseBody ?? null) : null,
timestamp: new Date().toISOString(),
meta: { projectId: config.projectId },
};
sendTrackedEvent(eventUrl, config.apiKey, body);
};
if (!options?.captureResponse) {
emitTrackedEvent();
return;
}
if (!canRegisterLifecycle(res)) {
emitTrackedEvent();
return;
}
const onDone = () => {
detachLifecycleListener(res, "finish", onDone);
detachLifecycleListener(res, "close", onDone);
emitTrackedEvent();
};
if (typeof res.once === "function") {
res.once("finish", onDone);
res.once("close", onDone);
return;
}
if (typeof res.on === "function") {
res.on("finish", onDone);
res.on("close", onDone);
return;
}
emitTrackedEvent();
}
function trackError(error: unknown, req: RequestLike) {
const err = error instanceof Error ? error : new Error(String(error));
const body = {
requestId: inferRequestId(req),
errorMessage: err.message,
stackSummary: err.stack,
timestamp: new Date().toISOString(),
};
fireAndForget(
fetch(errorUrl, {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": config.apiKey,
},
body: JSON.stringify(body),
}),
);
}
return { track, trackError };
}
Supported environments
If it speaks HTTP and runs on Node, you are probably fine.
More runtimes coming. Bun and Deno support in progress.