Skip to main content
Every step you instrument with run.step() produces a stream of status events that Failpath records and displays on your dashboard. These events tell you not just whether a step succeeded or failed, but when it started, how long it took, and — when it failed — what went wrong. Understanding the event lifecycle helps you instrument your flows correctly and interpret what you see in the dashboard.

The three event statuses

Each step produces up to two events during its lifetime: a running event when it starts, and either a success or error event when it finishes. These map to three possible statuses:
1

running

Sent immediately before your wrapped function is called. A running event marks the moment the step began executing. If you see a step stuck in running on the dashboard, the process likely crashed or timed out before the step could complete.
2

success

Sent when your wrapped function resolves without throwing. A success event confirms the step completed normally. The step’s duration is calculated from the running timestamp to this event.
3

error

Sent when your wrapped function throws. The error event captures the exception message and, if captureStack is enabled on the client, the stack trace. After recording the error event, the SDK rethrows the original error so your application’s own error handling continues to work as normal.

How step() handles errors

When the function inside run.step() throws, the SDK does two things: it records an error event for that step, then rethrows the original error. Your application receives exactly the same error it would have received without the SDK in place. You do not need to add any special error handling around step() calls — just let errors propagate normally.
await run.step("charge-card", async () => {
  // If chargeCard() throws, Failpath records an error event,
  // then the error propagates to your caller as usual.
  return chargeCard(cart);
});
This design means that adding Failpath instrumentation to an existing function is purely additive — it does not change your application’s error behavior.

Recording steps manually

For cases where you cannot wrap a function directly — such as an out-of-band process, a third-party callback, or a step that runs in a separate service — use failpath.recordStep() to send an event manually.
await failpath.recordStep({
  flowKey: "checkout",
  runId: "req_123",
  stepKey: "manual-step",
  status: "success",
});
recordStep() accepts any of the three statuses. Use it to fill in parts of a flow that the SDK cannot instrument automatically, so your dashboard graph reflects the full picture.

Async and fire-and-forget sending

By default, Failpath sends events asynchronously. Sending does not block your application code — the SDK dispatches the event in the background and your function continues executing immediately. This means that in most cases, Failpath adds no measurable latency to your request path. If a send fails, the SDK silently swallows the error by default. You can configure an onError callback on the client to observe telemetry send failures, or set throwOnSendError: true if you want send errors to surface as exceptions.
const failpath = createFailpathClient({
  projectKey: process.env.FAILPATH_PROJECT_KEY!,
  onError: (err) => console.error("Failpath send error:", err),
});
Do not include secrets, tokens, auth headers, full request bodies, payment data, or private customer data in step metadata. Metadata is sent to the Failpath API and displayed on the dashboard. Keep metadata limited to non-sensitive identifiers and diagnostic values such as IDs, counts, and status codes.