step.waitForSignal

step.waitForSignal() pauses one run until a signal with its name arrives or the wait times out. You can resume that run directly when its specific answer is ready.

They are useful when:

  • A callback belongs to one waiting run.
  • You need to resume a run as soon as its response arrives.
  • You want to address a wait by a unique signal name.

Why choose a signal?

  • Resume one run directly. A unique signal name addresses one active wait.
  • Resume sooner. The signal API is transactional; event delivery is eventually consistent.

Use step.waitForEvent for most waits. An event can resume multiple runs, trigger other functions, and provide a stored record for audit and Insights. Choose a signal when one run needs a direct, latency-sensitive response.

Wait for a signal

Give each waiting run a distinct signal name. The sender must use the same name. The example uses a request ID to connect an approval to its waiting run.

const approveRequest = inngest.createFunction(
  {
    id: "approve-request",
    triggers: { event: "app/approval.requested" },
  },
  async ({ event, step }) => {
    const signal = `approval/${event.data.requestId}`;

    const approval = await step.waitForSignal<{ approved: boolean }>(
      "wait-for-approval",
      {
        signal,
        timeout: "3d",
        onConflict: "fail",
      }
    );

    if (approval === null) return { status: "timed-out" };
    if (!approval.data.approved) return { status: "rejected" };

    return { status: "approved" };
  }
);

From the code that receives the approval, send the matching signal:

await inngest.sendSignal({
  signal: `approval/${requestId}`,
  data: { approved: true },
});

API

const result = await step.waitForSignal(id, { signal, timeout });
  • id: a stable step ID.
  • signal: a unique name for this wait, such as `approval/${requestId}`.
  • timeout: how long to wait, such as "1h".
  • Returns an object with the signal's data, or null if the timeout expires.

Send a signal with inngest.sendSignal({ signal, data }) from your app, or step.sendSignal() from another function. Signals are currently experimental. See the step.waitForSignal reference for every option.

Handle duplicate names, timeouts, and cancellation

onConflict: "fail" fails this run if another active wait uses the same signal name. onConflict: "replace" gives the name to the new wait; the earlier wait stays pending until its timeout. Use a request or run identifier in the signal name so unrelated runs do not compete.

A signal timeout resumes the function with null. Check that result before reading data. Cancelling the function run is a separate action: it stops subsequent work rather than returning a timeout value to this code. Use cancellation when you need to stop the run instead of returning a timeout value.