A run represents one execution of a flow — a single request, background job, or webhook invocation. A step represents one operation within that run. Together they give Failpath everything it needs to reconstruct the shape and outcome of your flow in the dashboard. This page covers all four methods you use to record that data: run(), step(), skip(), and recordStep().
failpath.run(flowKey, options)
Call failpath.run() at the start of your handler to create a run object. Pass the flow key and, optionally, a runId and per-run metadata.
const run = failpath.run("checkout", {
runId: requestId,
metadata: { route: "/checkout" },
});
The runId is the most important option to get right. Failpath uses it to group all steps that belong to the same execution together in the dashboard. Choose a value that is already unique per request in your system — a request ID, job ID, or webhook delivery ID all work well.
Use flow.slug from .failpath/flows.json as the flow key, and each node’s sdkStepKey as the step key. These values are generated by the Failpath CLI and guaranteed to match the flow definition in your dashboard.
Options
| Option | Type | Description |
|---|
runId | string | Correlates every step in one request, job, or webhook. Reuse the same ID for all steps in the same execution. |
metadata | object | Arbitrary key/value data attached to the run. Merged with defaultMetadata from the client. |
run.step(stepKey, fn, options)
Wrap each business operation with run.step(). The SDK sends a running event before calling your function, then sends success with the return value or error with the thrown exception when your function finishes.
const cart = await run.step("validate-cart", async () => {
return validateCart();
}, {
metadata: { cartId: "cart_123" },
});
step() returns the value your function returns, so you can assign it and use it in subsequent steps exactly as you would without instrumentation. If your function throws, the SDK records the error and rethrows the original exception — your existing error handling continues to work without modification.
Options
| Option | Type | Description |
|---|
metadata | object | Arbitrary key/value data attached to this step event. |
Never include secrets, tokens, authentication headers, full request bodies, payment card data, or private customer information in step metadata. Metadata is transmitted to and stored by Failpath.
run.skip(stepKey)
Use run.skip() when your flow reaches a branch where a step is intentionally not executed. Skipping a step records a skipped event so your flow trace in the dashboard remains complete even when a path is not taken.
await run.skip("send-receipt-email");
Skipping is preferable to simply omitting a step call. When a step is never recorded, Failpath cannot distinguish between a skipped branch and a step that failed silently before it could be instrumented.
failpath.recordStep(options)
Use failpath.recordStep() to record a step event outside the normal run.step() wrapper — for example, when integrating with a third-party queue, recording the result of a webhook callback, or bridging an event from another service.
await failpath.recordStep({
flowKey: "checkout",
runId: "req_123",
stepKey: "manual-step",
status: "success",
});
Parameters
| Parameter | Type | Required | Description |
|---|
flowKey | string | ✓ | The slug of the flow this step belongs to. |
runId | string | ✓ | The run ID that groups this step with others in the same execution. |
stepKey | string | ✓ | The key identifying this step within the flow. |
status | "running" | "success" | "error" | ✓ | The status to record for this step. |
Putting it all together
The example below shows all four methods working together in a single checkout handler.
import { createFailpathClient } from "@failpath/sdk";
const failpath = createFailpathClient({
projectKey: process.env.FAILPATH_PROJECT_KEY!,
});
export async function handleCheckout(requestId: string) {
const run = failpath.run("checkout", {
runId: requestId,
metadata: { route: "/checkout" },
});
const cart = await run.step("validate-cart", async () => {
return validateCart();
});
await run.step("charge-card", async () => {
return chargeCard(cart);
}, {
metadata: { cartId: cart.id },
});
// Skip the receipt if the customer opted out
if (!cart.receiptOptIn) {
await run.skip("send-receipt-email");
} else {
await run.step("send-receipt-email", async () => {
return sendReceipt(cart);
});
}
}