# `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](/docs-markdown/durable-execution/primitives/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.

```typescript {{ title: "TypeScript" }}
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" };
  }
);
```

Use [`ctx.step.wait_for_event`](/docs-markdown/durable-execution/primitives/step-waitforevent) with a match on the request ID instead.

```go {{ title: "Go" }}
import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/inngest/inngestgo"
	"github.com/inngest/inngestgo/step"
)

type ApprovalRequested struct {
	RequestID string `json:"requestId"`
}

type Approval struct {
	Approved bool `json:"approved"`
}

func ApproveRequest(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "approve-request"},
		inngestgo.EventTrigger("app/approval.requested", nil),
		func(ctx context.Context, input inngestgo.Input[ApprovalRequested]) (any, error) {
			signal := fmt.Sprintf("approval/%s", input.Event.Data.RequestID)

			approval, err := step.WaitForSignal[Approval](ctx, "wait-for-approval", step.WaitForSignalOpts{
				Signal:     signal,
				Timeout:    3 * 24 * time.Hour,
				OnConflict: step.SignalConflictFail,
			})
			if errors.Is(err, step.ErrSignalNotReceived) {
				return map[string]string{"status": "timed-out"}, nil
			}
			if err != nil {
				return nil, err
			}
			if !approval.Data.Approved {
				return map[string]string{"status": "rejected"}, nil
			}

			return map[string]string{"status": "approved"}, nil
		},
	)
}
```

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

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

## API

```ts {{ title: "TypeScript" }}
const result = await step.waitForSignal(id, { signal, timeout });
```

Use [`ctx.step.wait_for_event`](/docs-markdown/durable-execution/primitives/step-waitforevent) with a match on the request ID instead.

```go {{ title: "Go" }}
result, err := step.WaitForSignal[map[string]any](ctx, id, step.WaitForSignalOpts{
	Signal:  signal,
	Timeout: 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](/docs-markdown/reference/typescript/v4/functions/step-wait-for-signal) 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.