Skip to main content
Push-based complement to polling. A <WaitForEvent> durably suspends the workflow until a matching external event arrives (or the timeout expires). The event payload is written to the configured output.

Import

import { WaitForEvent } from "smithers-orchestrator";

Props

PropTypeDefaultDescription
idstring(required)Unique node id within the workflow.
eventstring(required)Event name/type to wait for (e.g. "deploy.completed").
correlationIdstringundefinedCorrelation key to match the right event instance.
outputz.ZodObject | Table | string(required)Where to store the event payload.
outputSchemaz.ZodObjectundefinedZod schema for validating the event payload.
timeoutMsnumberundefinedMax wait time in ms before timeout behavior triggers.
onTimeout"fail" | "skip" | "continue""fail"What happens when the timeout expires.
asyncbooleanfalseWhen true, unrelated downstream flow can continue while the event is still pending. Explicit dependencies still wait for the payload.
skipIfbooleanfalseSkip this node entirely.
dependsOnstring[]undefinedTask IDs that must complete first.
needsRecord<string, string>undefinedNamed deps. Keys become context keys, values are task IDs.
labelstringwait:<event>Display label override.
metaRecord<string, unknown>undefinedExtra metadata.

Basic Usage

import {
  Workflow,
  Sequence,
  Task,
  WaitForEvent,
  createSmithers,
} from "smithers-orchestrator";
import { z } from "zod";

const deployPayload = z.object({
  environment: z.string(),
  sha: z.string(),
  status: z.enum(["success", "failure"]),
});

const { smithers, outputs } = createSmithers({
  deployEvent: deployPayload,
  summary: z.object({ message: z.string() }),
});

export default smithers((ctx) => (
  <Workflow name="deploy-watcher">
    <Sequence>
      <WaitForEvent
        id="wait-deploy"
        event="deploy.completed"
        correlationId={ctx.input.deployId}
        output={outputs.deployEvent}
        outputSchema={deployPayload}
        timeoutMs={600_000}
        onTimeout="fail"
      />

      <Task id="notify" output={outputs.summary} agent={notifier}>
        The deploy finished. Summarize the result.
      </Task>
    </Sequence>
  </Workflow>
));

Timeout Behaviors

onTimeoutEffect
"fail"Node fails. Workflow stops (unless continueOnFail is set on a parent).
"skip"Node is skipped. Downstream nodes that depend on it see skipped status.
"continue"Node completes with a null/empty payload. Downstream nodes proceed.

Correlated Events

Use correlationId to match specific event instances when multiple events of the same type may arrive:
<WaitForEvent
  id="wait-pr-merged"
  event="github.pull_request.merged"
  correlationId={`pr-${ctx.input.prNumber}`}
  output={outputs.mergeEvent}
  timeoutMs={86_400_000}
/>

Durable Deferred Resolution

When async is true, the wait node becomes a durable deferred: the workflow records the subscription durably and allows unrelated downstream work to proceed immediately. The deferred resolves when the event arrives. Any task that explicitly depends on this node (via dependsOn or needs) still blocks until the payload is available. This pattern is useful when you want to kick off a long-running external process and continue with independent work while waiting:
<Workflow name="async-deploy">
  <Sequence>
    <Task id="trigger-build" output={outputs.trigger} agent={ciAgent}>
      Trigger a CI build and return the build ID.
    </Task>

    <WaitForEvent
      id="build-done"
      event="ci.build.completed"
      correlationId={ctx.outputMaybe(outputs.trigger, { nodeId: "trigger-build" })?.buildId}
      output={outputs.buildResult}
      timeoutMs={1_800_000}
      onTimeout="fail"
      async
    />

    {/* This runs immediately — it does not depend on the build result */}
    <Task id="notify-started" output={outputs.notifyStarted}>
      {{ message: "Build triggered, waiting for completion." }}
    </Task>

    {/* This blocks until the build event payload is available */}
    <Task id="deploy" output={outputs.deploy} dependsOn={["build-done"]} agent={deployAgent}>
      Deploy the completed build.
    </Task>
  </Sequence>
</Workflow>
The subscription survives worker restarts — the Temporal runtime checkpoints the event wait durably. When the matching event arrives, the deferred resolves and any blocked dependents resume.

Behavior

  • When the scheduler reaches this node, it enters waiting-event status.
  • The engine durably records the event subscription (event name + correlation ID).
  • When a matching event arrives (via webhook, API call, or event bus), the node resumes and writes the payload to output.
  • If timeoutMs elapses first, the onTimeout behavior applies.
  • With async, later unrelated nodes can continue before the event arrives. Use explicit dependsOn / needs when later work really requires the payload.

Rendering

<WaitForEvent> renders as a smithers:wait-for-event host element. The scheduler treats it like a task node that enters waiting-event status instead of in-progress.

Notes

  • This is a push-based primitive. For poll-based external checks, use a <Task> with a compute function.
  • The event payload is validated against outputSchema when provided.
  • Combine with <Sequence> to gate downstream work on external events.
  • Multiple <WaitForEvent> nodes can wait for different events in a <Parallel> block.
  • Async event waits contribute to the Prometheus gauge smithers_external_wait_async_pending{kind="event"} while unresolved.