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 type | Use it for | Where it appears in Hud | API |
|---|---|---|---|
| HTTP | Unsupported HTTP endpoints | Endpoints page | setHttpFlow |
| Queue | Unsupported queues | Queues page | startFlow / endFlow or wrapFlow, both with { type: "queue" } |
| Custom | Everything else (e.g. scripts, scheduled tasks, etc.) | Custom Flows page | startFlow / endFlow, wrapFlow |
All of these APIs are exported from hud-sdk/api and work together with setContext and setFailure.
Requirements
| SDK | Minimum version | Supported flow types |
|---|---|---|
| Node.js | 1.8.12 (HTTP & Custom), 1.8.14 (Queue) | HTTP, Custom, Queue |
| Python | 0.4.10 | Custom, 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
setContextinstead. The number of distinct custom flow names is capped.- Manual flows cannot be nested: calling
startFlow/wrapFlowinside 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);
setHttpFlowmust 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
wrapFlowwrapFlow(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
startFlow / endFlowWhen 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
startFlowwith anendFlow(including on the error path). AnendFlowwithout a precedingstartFlowis 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— aDateof 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 aDate, 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)
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
wrapFlowimport { 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) => TUpdated 9 days ago

