> ## Documentation Index
> Fetch the complete documentation index at: https://docs.failpath.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Step Events: running, success, and error in Failpath

> Failpath sends running, success, and error events for every step. Understand the event lifecycle and how they appear on your dashboard.

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:

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

## 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.

```typescript theme={"dark"}
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.

```typescript theme={"dark"}
await failpath.recordStep({
  flowKey: "checkout",
  runId: "req_123",
  stepKey: "manual-step",
  status: "success",
});
```

`recordStep()` accepts `running`, `success`, `error`, or `skipped`. Use it to fill in parts of a flow that the SDK cannot instrument automatically, so your dashboard graph reflects the full picture.

## Background delivery

By default, Failpath sends events asynchronously. Sending does not block your application code — the SDK queues the event, drains the queue 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 swallows the error by default. You can configure an `onError` callback on the client to observe telemetry send failures. If you set `throwOnSendError: true`, call `failpath.flush()` to surface queued delivery failures.

```typescript theme={"dark"}
const failpath = createFailpathClient({
  projectKey: process.env.FAILPATH_PROJECT_KEY!,
  onError: (err) => console.error("Failpath send error:", err),
});
```

Call `flush()` before a short-lived process exits, or at the end of a serverless request if your runtime does not provide a `waitUntil` hook:

```typescript theme={"dark"}
await failpath.flush();
```

<Warning>
  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.
</Warning>
