# Wait for an Event

You can pause a Function's run until a given event is received.

This is a useful pattern to react to specific user actions (for example, implement [Human in the loop](/docs-markdown/ai-patterns/human-in-the-loop) in AI Agent workflows), or to wait until something happens in external systems.

This is the recommended and preferred way to pause and resume a function via events or signals because:

- Events fan out to many functions:  you can resume many runs from a single event, without changing application code.
- Events are cleaner:  events decouple code from the functions they resume
- Events have audit trails:  we store events in your OLAP store, giving you audit trails and insights automatically.

Use `step.waitForEvent()` to wait for a particular event to be received before continuing. It returns a `Promise` that is resolved with the received event or `null` if the event is not received within the timeout.

```ts
export default inngest.createFunction(
  { id: "send-onboarding-nudge-email", triggers: { event: "app/account.created" } },
  async ({ event, step }) => {
    const onboardingCompleted = await step.waitForEvent(
      "wait-for-onboarding-completion",
      {
        event: "app/onboarding.completed",
        timeout: "3d",
        // "async" is the received "ai/post.topic.selected" event here:
        if: `async.data.completionId == "${generatedTopics.completionId}"`,
      }
    );
    if (!onboardingCompleted) {
      // if no event is received within 3 days, onboardingCompleted will be null
    } else {
      // if the event is received, onboardingCompleted will be the event payload object
    }
  }
);
```

Check out the [`step.waitForEvent()` TypeScript reference.](/docs-markdown/reference/functions/step-wait-for-event)

To add a simple time based delay to your code, use [`step.sleep()`](/docs-markdown/reference/functions/step-sleep) instead.

## Examples

### Dynamic functions that wait for additional user actions

Below is an example of an Inngest function that creates an Intercom or Customer.io-like drip email campaign, customized based on

```ts
export default inngest.createFunction(
  { id: "onboarding-email-drip-campaign", triggers: { event: "app/account.created" } },
  async ({ event, step }) => {
    // Send the user the welcome email immediately
    await step.run("send-welcome-email", async () => {
      await sendEmail(event.user.email, "welcome");
    });

    // Wait up to 3 days for the user to complete the final onboarding step
    // If the event is received within these 3 days, onboardingCompleted will be the
    // event payload itself, if not it will be null
    const onboardingCompleted = await step.waitForEvent("wait-for-onboarding", {
      event: "app/onboarding.completed",
      timeout: "3d",
      // The "data.userId" must match in both the "app/account.created" and
      // the "app/onboarding.completed" events
      if: `async.data.userId == "${event.data.userId}"`,
    });

    // If the user has not completed onboarding within 3 days, send them a nudge email
    if (!onboardingCompleted) {
      await step.run("send-onboarding-nudge-email", async () => {
        await sendEmail(event.user.email, "onboarding_nudge");
      });
    } else {
      // If they have completed onboarding, send them a tips email
      await step.run("send-tips-email", async () => {
        await sendEmail(event.user.email, "new_user_tips");
      });
    }
  }
);
```

Use `step.wait_for_event()` to wait for a particular event to be received before continuing.

```py
@inngest_client.create_function(
    fn_id="my_function",
    trigger=inngest.TriggerEvent(event="app/my_function"),
)
async def fn(ctx: inngest.Context) -> None:
    res = await ctx.step.wait_for_event(
        "wait",
        event="app/wait_for_event.fulfill",
        timeout=datetime.timedelta(seconds=2),
    )
```

Check out the [`step.wait_for_event()` Python reference.](/docs-markdown/reference/python/steps/wait-for-event)

Use `step.waitForEvent()` to wait for a particular event to be received before continuing. It either returns the received event data or a `step.ErrEventNotReceived` error.

```go
func AccountCreated(ctx context.Context, input inngestgo.Input[AccountCreatedEvent]) (any, error) {
  // Sleep for a second, minute, hour, week across server restarts.
  opened, err = step.waitForEvent(ctx, "wait-for-open", opts.WaitForEventOpts{
      Event: "email/mail.opened",
      If:	inngestgo.StrPtr(fmt.Sprintf("async.data.id == %s", strconv.Quote("my-id"))),
      Timeout: 24 * time.Hour,
  })

  if err == step.ErrEventNotReceived {
  	// A function wasn't created within 3 days.  Send a follow-up email.
  	step.Run(ctx, "follow-up-email", func(ctx context.Context) (any, error) {
  		// ...
  		return true, nil
  	})
  	return nil, nil
  }

  // ...

  return nil, nil
}
```

Check out the [`step.WaitForEvent()` Go reference.](https://pkg.go.dev/github.com/inngest/inngestgo@v0.9.0/step#WaitForEvent)

> **Callout:** Preventing race conditionsThe "wait for event" method begins listening for new events from when the code is executed. This means that events sent before the function is executed will not be handled by the wait.To avoid race condition, always double-check the flow of events going through your functions.Note: The "wait for event" mechanism will soon provide a "lookback" feature, including events from a given past timeframe.