Steps
Docs › Steps
step.waitForEvent
step.waitForEvent() pauses a run until a matching event arrives or the wait times out. Inngest resumes the run with the event without keeping your function process running while it waits.
They are useful when:
- You need a response from another service before continuing.
- A person must approve or reject work.
- One event should be able to resume multiple matching runs.
Why choose an event?
- Resume several runs. One event can resume multiple matching waits and trigger other functions.
- Keep systems independent. The sender publishes an event; it does not need to know which runs are waiting.
- Keep an audit trail. Inngest stores events for audit and Insights.
Use step.waitForEvent for most waits. If you must address one run directly and need lower resume latency, use step.waitForSignal. A signal resumes one run through a transactional API.
Wait for invoice review
This function waits up to seven days for an invoice review. It matches the review to the invoice that started the run.
import { eventType, Inngest, staticSchema } from "inngest";
type ApprovalRequested = { invoiceId: string };
type ApprovalRecorded = { invoiceId: string; approved: boolean };
const approvalRequested = eventType("invoice/approval.requested", {
schema: staticSchema<ApprovalRequested>(),
});
const approvalRecorded = eventType("invoice/approval.recorded", {
schema: staticSchema<ApprovalRecorded>(),
});
const inngest = new Inngest({ id: "billing" });
export const awaitInvoiceReview = inngest.createFunction(
{ id: "await-invoice-review", triggers: [approvalRequested] },
async ({ event, step }) => {
const review = await step.waitForEvent("wait-for-review", {
event: approvalRecorded,
match: "data.invoiceId",
timeout: "7d",
});
if (review === null) {
return { invoiceId: event.data.invoiceId, status: "timed-out" };
}
return {
invoiceId: event.data.invoiceId,
status: review.data.approved ? "approved" : "rejected",
};
}
);import datetime
import inngest
inngest_client = inngest.Inngest(app_id="billing")
@inngest_client.create_function(
fn_id="await-invoice-review",
trigger=inngest.TriggerEvent(event="invoice/approval.requested"),
)
async def await_invoice_review(ctx: inngest.Context) -> dict[str, str]:
invoice_id = str(ctx.event.data["invoiceId"])
review = await ctx.step.wait_for_event(
"wait-for-review",
event="invoice/approval.recorded",
if_exp="event.data.invoiceId == async.data.invoiceId",
timeout=datetime.timedelta(days=7),
)
if review is None:
return {"invoiceId": invoice_id, "status": "timed-out"}
return {
"invoiceId": invoice_id,
"status": "approved" if review.data["approved"] else "rejected",
}import (
"context"
"errors"
"time"
"github.com/inngest/inngestgo"
"github.com/inngest/inngestgo/step"
)
type ApprovalRequested struct {
InvoiceID string `json:"invoiceId"`
}
type ApprovalRecorded struct {
InvoiceID string `json:"invoiceId"`
Approved bool `json:"approved"`
}
func main() {
client, err := inngestgo.NewClient(inngestgo.ClientOpts{AppID: "billing"})
if err != nil {
panic(err)
}
_, err = inngestgo.CreateFunction(
client,
inngestgo.FunctionOpts{ID: "await-invoice-review"},
inngestgo.EventTrigger("invoice/approval.requested", nil),
func(ctx context.Context, input inngestgo.Input[ApprovalRequested]) (any, error) {
review, err := step.WaitForEvent[inngestgo.GenericEvent[ApprovalRecorded]](
ctx,
"wait-for-review",
step.WaitForEventOpts{
Event: "invoice/approval.recorded",
If: inngestgo.StrPtr("event.data.invoiceId == async.data.invoiceId"),
Timeout: 7 * 24 * time.Hour,
},
)
if errors.Is(err, step.ErrEventNotReceived) {
return map[string]string{
"invoiceId": input.Event.Data.InvoiceID,
"status": "timed-out",
}, nil
}
if err != nil {
return nil, err
}
status := "rejected"
if review.Data.Approved {
status = "approved"
}
return map[string]string{
"invoiceId": input.Event.Data.InvoiceID,
"status": status,
}, nil
},
)
if err != nil {
panic(err)
}
}Send invoice/approval.recorded with the same invoiceId after the wait starts. The returned event provides review.data.approved; a timeout returns null.
API
const event = await step.waitForEvent(id, { event, timeout, match });event = await ctx.step.wait_for_event(
step_id, event=name, timeout=timeout, if_exp=expression
)event, err := step.WaitForEvent[inngestgo.Event](ctx, id, step.WaitForEventOpts{
Event: name,
Timeout: timeout,
If: &expression,
})id: a stable step ID.event: the name of the event to wait for.timeout: how long to wait, such as"7d".match: a field both events share, such as"data.invoiceId". For more specific matches, use anifexpression instead.- Returns the matching event, or
nullif the timeout expires.
Always handle the timeout, for example by recording an expired request or sending a reminder. See the step.waitForEvent reference for every option, including if expressions.
Match the right event
Use a stable request or entity ID in both events. An event name alone can match unrelated runs. Sessions do not filter step.waitForEvent(); the event name and match or if determine the match.
The wait only sees events sent after it starts listening. If an external system can send a response before the wait begins, arrange the sequence so the wait is registered first or check the external state before waiting. A matching event sent earlier will not resume this wait.
Use step.waitForEvent() for events that can fan out to multiple functions or runs. For direct, latency-sensitive resumption of one run, see step.waitForSignal.
Limits and related pages
- Check the current platform limits when choosing a timeout and shaping event or run data. The limit for a running step is a separate setting from a durable wait.
- In a
group.parallel()race, a losingstep.waitForEvent()remains active until its timeout. Use a short timeout when that pattern applies. - For a delay with no external event, use step.sleep.
Updated Nov 10, 2026
Powered byZunoy