Carbon captures events into an in-memory buffer and delivers them in the background, so instrumentation never blocks your request path. On a long-running server that buffer drains on its own. On serverless platforms — Next.js route handlers, Vercel Functions, AWS Lambda, Cloudflare Workers — the runtime freezes or tears down the moment your handler returns, so any events still in the buffer never get sent.
The fix is one line: drain the buffer before the function exits.
await carbon.flushPendingEvents();
flushPendingEvents resolves once every buffered event is either delivered or deliberately dropped (and reported through onError). It rejects only when a transient delivery failure leaves events buffered, so you can decide how to handle loss at shutdown:
try {
await carbon.flushPendingEvents();
} catch (error) {
console.error("Carbon flush failed", error);
}
Create the Carbon instance and wrap your client once at module scope, not
per request — the buffer and its background delivery are shared across the
warm invocations that reuse the same instance.
Flush after the response
Awaiting the flush before you return delays the response by however long delivery takes. On platforms that can keep the function alive past the response, schedule the flush as post-response work instead — the user gets their response immediately and events still drain before the runtime freezes.
Next.js
Vercel Functions
AWS Lambda
Cloudflare Workers
Next.js after runs a callback once the response has finished streaming, without blocking it:import { after } from "next/server";
export async function POST(req: Request) {
const completion = await openai.chat.completions.create({
model: "gpt-5.4-nano",
messages: [{ role: "user", content: await req.text() }],
});
after(() => carbon.flushPendingEvents());
return Response.json(completion);
}
waitUntil extends the function’s lifetime until the promise settles:import { waitUntil } from "@vercel/functions";
export async function POST(req: Request) {
const completion = await openai.chat.completions.create({
model: "gpt-5.4-nano",
messages: [{ role: "user", content: await req.text() }],
});
waitUntil(carbon.flushPendingEvents());
return Response.json(completion);
}
Await the flush before returning — Lambda freezes the execution environment as soon as the handler resolves:export const handler = async (event) => {
const completion = await openai.chat.completions.create({
model: "gpt-5.4-nano",
messages: [{ role: "user", content: event.body }],
});
await carbon.flushPendingEvents();
return { statusCode: 200, body: JSON.stringify(completion) };
};
Hand the flush to ctx.waitUntil so it runs after the response is returned:export default {
async fetch(req, env, ctx) {
const completion = await openai.chat.completions.create({
model: "gpt-5.4-nano",
messages: [{ role: "user", content: await req.text() }],
});
ctx.waitUntil(carbon.flushPendingEvents());
return Response.json(completion);
},
};
Background work scheduled with after, waitUntil, or ctx.waitUntil is
best-effort. If the platform terminates the instance early — a hard timeout,
for example — the flush may not finish. Await the flush directly when you
cannot tolerate any loss.
Why an explicit flush is needed
By default a buffered batch is sent when it reaches 50 events or after it has waited 5 seconds, whichever comes first. A serverless invocation usually handles a single request and returns in well under 5 seconds, so neither threshold is reached before the runtime freezes — the explicit flush is what forces delivery.
You can tune these thresholds — bufferBatchSize, bufferMaxTimeMs, and the rest — but in a short-lived function flushing before exit is the reliable guarantee. See Configuration for the full set of buffering options and delivery semantics.
Long-running servers
Persistent processes — a Node server, a container — drain the buffer continuously as it fills, so no per-request flush is needed. Call flushPendingEvents only on graceful shutdown, so events captured just before exit are delivered:
process.on("SIGTERM", async () => {
await carbon.flushPendingEvents();
process.exit(0);
});