Mark as Flow (Manual Flows)

Manually mark code as a flow to track entry points that aren't covered by Hud's automatic instrumentation.

Overview

Hud automatically instruments your application's entry points — HTTP frameworks, message queues, GraphQL, and more — with no code changes. Manual flows let you extend this to entry points that automatic instrumentation doesn't cover.

When you mark a block of code as a flow, the Hud SDK collects flow metrics and forensics for it, and Hud treats it as a first-class entry point: it appears in the UI and MCP, and gets issue detection, error rates, duration graphs, and related issues — just like an auto-detected flow.

There are three kinds of manual flows:

Flow typeUse it forWhere it appears in HudAPI
HTTPUnsupported HTTP endpointsEndpoints pagesetHttpFlow
QueueUnsupported queuesQueues pagestartFlow / endFlow or wrapFlow, both with { type: "queue" }
CustomEverything else (e.g. scripts, scheduled tasks, etc.)Custom Flows pagestartFlow / endFlow, wrapFlow

All of these APIs are exported from hud-sdk/api and work together with setContext and setFailure.



Requirements

SDKMinimum versionSupported flow types
Node.js1.8.12 (HTTP & Custom), 1.8.14 (Queue)HTTP, Custom, Queue
Python0.4.10Custom, Queue (see Python guide)
🚧

Important Notes

  • All manual flow APIs must be called after register() — calling them during startup before Hud is initialized has no effect.
  • Flow names must be stable, non-empty strings. Avoid high-cardinality names (e.g. embedding user IDs or timestamps) — put variable data in setContext instead. The number of distinct custom flow names is capped.
  • Manual flows cannot be nested: calling startFlow / wrapFlow inside an already-active flow (or inside an HTTP request) is skipped.

Mark HTTP flows

Use setHttpFlow(route, method) to define the route and method of the current HTTP request. Call it from inside the request handler. This is useful when the route can't be inferred automatically.

The flow appears on the Endpoints page, named by the route and method you provide.

import { setHttpFlow } from 'hud-sdk/api';

// Inside your request handler, while processing the request:
const server = http.createServer((req, res) => {
  setHttpFlow('/hello-world', 'GET');
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!\n');
});

server.listen(3000);
const { setHttpFlow } = require('hud-sdk/api');

const server = http.createServer((req, res) => {
  setHttpFlow('/hello-world', 'GET');
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!\n');
});

server.listen(3000);
👉

setHttpFlow must be called during an active HTTP request that Hud is already tracking. Calling it outside of an HTTP context is skipped.


Mark Custom flows

Use custom flows for code that isn't an HTTP endpoint or a queue — scripts, main functions, scheduled tasks, cron jobs, background loops, and so on.

Custom flows appear on the Custom Flows page, which includes a table of all custom flows plus a dedicated page per flow with detailed graphs, functions, forensics, and related issues.

There are two ways to define a custom flow.

Option A: wrapFlow

wrapFlow(name, handler) returns a wrapped version of your function. Every time the wrapped function is called, that invocation is tracked as a flow. It works for both sync and async functions, and the return value (and arguments) are preserved.

If the function throws an exception, it is considered a flow failure and will be counted toward the flow error rate.

import { wrapFlow } from 'hud-sdk/api';

const generateNightlyReport = wrapFlow('nightly-report', async (date: string) => {
  const data = await collectData(date);
  return buildReport(data);
});

// Each call is tracked as the "nightly-report" flow:
await generateNightlyReport('2026-06-17');
const { wrapFlow } = require('hud-sdk/api');

const generateNightlyReport = wrapFlow('nightly-report', async (date) => {
  const data = await collectData(date);
  return buildReport(data);
});

// Each call is tracked as the "nightly-report" flow:
await generateNightlyReport('2026-06-17');

Option B: startFlow / endFlow

When you can't wrap a single function, bracket the code manually with startFlow(name) and endFlow(failureReason?). Pass an optional failure reason to endFlow to mark the flow as failed.

import { startFlow, endFlow, setFailure } from 'hud-sdk/api';

async function runCronJob() {
  startFlow('cleanup-expired-sessions');
  try {
    await deleteExpiredSessions();
  } catch (err) {
    setFailure('CleanupFailed');
    throw err;
  } finally {
    endFlow();
  }
}
const { startFlow, endFlow, setFailure } = require('hud-sdk/api');

async function runCronJob() {
  startFlow('cleanup-expired-sessions');
  try {
    await deleteExpiredSessions();
  } catch (err) {
    setFailure('CleanupFailed');
    throw err;
  } finally {
    endFlow();
  }
}
👉

Always pair every startFlow with an endFlow (including on the error path). An endFlow without a preceding startFlow is skipped.


Mark Queue flows

To instrument a custom or in-house queue consumer, pass { type: "queue" } in the options of startFlow or wrapFlow.

Set framework to a short string identifying your queue (e.g. "kafka", "rabbitmq"). It defaults to "custom".

For queue flows using startFlow you can also pass two extra options:

  • enqueueTime — a Date of when the message was enqueued/produced. Hud uses it to record the end-to-end duration (how long the message waited in the queue before processing finished). A value that isn't a Date, is in the future, or is more than a year in the past is ignored.
  • messageCount — the number of messages handled in this invocation. Must be a positive integer; any other value is ignored.

Option A: startFlow / endFlow (per-message metadata)

import { startFlow, endFlow } from 'hud-sdk/api';

// In your consumer loop, for each message:
startFlow('my-kafka-topic', {
  type: 'queue',
  framework: 'kafka',
  enqueueTime: message.timestamp, // a Date of when the message was produced
  messageCount: 1,
});
try {
  await processMessage(message);
} finally {
  endFlow();
}
const { startFlow, endFlow } = require('hud-sdk/api');

// In your consumer loop, for each message:
startFlow('my-kafka-topic', {
  type: 'queue',
  framework: 'kafka',
  enqueueTime: message.timestamp, // a Date of when the message was produced
  messageCount: 1,
});
try {
  await processMessage(message);
} finally {
  endFlow();
}

Option B: wrapFlow

import { wrapFlow } from 'hud-sdk/api';

const handleMessage = wrapFlow(
  'my-kafka-topic',
  async (message) => {
    await processMessage(message);
  },
  { type: 'queue', framework: 'kafka' },
);

// Each call is tracked as the "my-kafka-topic" queue flow:
await handleMessage(message);
const { wrapFlow } = require('hud-sdk/api');

const handleMessage = wrapFlow(
  'my-kafka-topic',
  async (message) => {
    await processMessage(message);
  },
  { type: 'queue', framework: 'kafka' },
);

// Each call is tracked as the "my-kafka-topic" queue flow:
await handleMessage(message);

Combining with context and failures

Manual flows work with the same helpers as auto-detected flows:

  • setContext — attach metadata (e.g. orderId, tenant) to the current flow's forensics.
  • setFailure — explicitly mark the current flow as failed even when no exception was thrown.
import { wrapFlow, setContext, setFailure } from 'hud-sdk/api';

const processBatch = wrapFlow('process-batch', async (batchId: string) => {
  setContext({ batchId });

  const result = await runBatch(batchId);
  if (result.rejected) {
    setFailure('BatchRejected', { reason: result.reason });
  }
  return result;
});

API reference

// Options shared by startFlow and wrapFlow (all fields optional)
interface FlowOptions {
  type?: 'custom' | 'queue'; // defaults to 'custom'
  framework?: string;        // defaults to 'custom'
  enqueueTime?: Date;        // queue flows only; sets the end-to-end duration
  messageCount?: number;     // queue flows only; positive integer
}

// HTTP flows
setHttpFlow(route: string, method: string): void

// Custom and queue flows
startFlow(flowName: string, options?: FlowOptions): void
endFlow(failureReason?: string): void
wrapFlow<Args extends unknown[], T>(
  flowName: string,
  handler: (...args: Args) => T,
  options?: FlowOptions,
): (...args: Args) => T


Did this page help you?