# Events

Tasks can pause until an external event arrives before continuing. This is the foundation for human-in-the-loop workflows, webhook-driven pipelines, and any flow that depends on signals from outside the task.


Both durable tasks and DAGs support waiting for events. Durable tasks call `WaitForEvent` dynamically at runtime, while DAGs declare event conditions upfront on the task definition.

Events are delivered by [pushing events](/v1/external-events/pushing-events) into Hatchet using the event client. The event key you push must match the key your task is waiting for.

#### Durable Tasks

## Wait For Events

Wait For Events lets a durable task pause until an external event arrives. Even if the task is interrupted and requeued while waiting, the event will still be processed. When it resumes, it reads the event from the durable event log and continues.

> **Info:** Waiting for an event puts the task into an [evictable
>   state](/v1/task-eviction), the worker slot is freed and the task is re-queued
>   when the event arrives.

### Declaring a wait for event

Wait For Event is declared using the context method `WaitFor` (or utility method `WaitForEvent`) on the `DurableContext` object.

#### Python

```python
@hatchet.durable_task(name="DurableEventTask")
async def durable_event_task(input: EmptyModel, ctx: DurableContext) -> None:
    res = await ctx.aio_wait_for_event(
        "user:update",
    )

    print("got event", res)
```

#### Typescript

```typescript
export const durableEvent = hatchet.durableTask({
  name: 'durable-event',
  executionTimeout: '10m',
  fn: async (_, ctx) => {
    const res = await ctx.waitForEvent(EVENT_KEY);

    console.log('res', res);

    return {
      Value: 'done',
    };
  },
});
```

#### Go

```go
task := client.NewStandaloneDurableTask("long-running-task", func(ctx hatchet.DurableContext, input DurableInput) (DurableOutput, error) {
	log.Printf("Starting task, will sleep for %d seconds", input.Delay)

	if _, err := ctx.WaitForEvent("user:updated", ""); err != nil {
		return DurableOutput{}, err
	}

	log.Printf("Finished waiting for event, processing message: %s", input.Message)

	return DurableOutput{
		ProcessedAt: time.Now().Format(time.RFC3339),
		Message:     "Processed: " + input.Message,
	}, nil
})
```

#### Ruby

```ruby
DURABLE_EVENT_TASK = HATCHET.durable_task(name: "DurableEventTask") do |input, ctx|
  res = ctx.wait_for(
    "event",
    Hatchet::UserEventCondition.new(event_key: "user:update")
  )

  puts "got event #{res}"
end

DURABLE_EVENT_TASK_WITH_FILTER = HATCHET.durable_task(name: "DurableEventWithFilterTask") do |input, ctx|
```

### Event filters

Events can be filtered using [CEL](https://github.com/google/cel-spec) expressions. For example, to only receive `user:update` events for a specific user:

#### Python

```python
res = await ctx.aio_wait_for_event("user:update", "input.user_id == '1234'")
```

#### Typescript

```typescript
const res = await ctx.waitForEvent(EVENT_KEY, "input.userId == '1234'");
```

#### Go

```go
if _, err := ctx.WaitForEvent("user:updated", "input.status_code == 200"); err != nil {
	return DurableOutput{}, err
}
```

#### Ruby

```ruby
res = ctx.wait_for(
    "event",
    Hatchet::UserEventCondition.new(
      event_key: "user:update",
      expression: "input.user_id == '1234'"
    )
  )

  puts "got event #{res}"
end
```

### Pushing events

For a waiting task to resume, something must [push an event](/v1/external-events/pushing-events) into Hatchet with a matching key. You can do this from any service that has access to the Hatchet client.

#### Python

```python
hatchet.event.push("user:create", {"should_skip": False})
```

#### Typescript

```typescript
const res = await hatchet.events.push('simple-event:create', {
  Message: 'hello',
  ShouldSkip: false,
});
```

#### Go

```go
err := client.Events().Push(
	context.Background(),
	"simple-event:create",
	EventInput{
		Message: "Hello, World!",
	},
)
if err != nil {
	return err
}
```

#### Ruby

```ruby
HATCHET.event.push("user:create", { "should_skip" => false })
```

When the pushed event's key matches what a durable task is waiting for (and passes any CEL filter), the task is re-queued and resumes from its checkpoint.

#### DAGs

## Event Conditions

Event conditions let a DAG task react to external events. A task can wait for an event before running, be skipped when an event arrives, or be cancelled by an event.

Unlike durable tasks (where `WaitForEvent` is called dynamically at runtime), DAG event conditions are declared upfront on the task definition.

### Usage modes

Event conditions can be used with three operators:

- **`wait_for`** — the task waits for the event before starting.
- **`skip_if`** — the task is skipped if the event arrives.
- **`cancel_if`** — the task is cancelled if the event arrives.

> **Warning:** A task cancelled by `cancel_if` behaves like any other cancellation in Hatchet
>   — downstream tasks will be cancelled as well.

### Waiting for an event

Declare a task with a `wait_for` event condition. The task will not start until the specified event is pushed into Hatchet.

#### Python

```python
@task_condition_workflow.task(
    parents=[start],
    wait_for=[
        or_(
            SleepCondition(duration=timedelta(minutes=1)),
            UserEventCondition(event_key="wait_for_event:start"),
        )
    ],
)
def wait_for_event(input: EmptyModel, ctx: Context) -> StepOutput:
    return StepOutput(random_number=random.randint(1, 100))
```

#### Typescript

```typescript
const waitForEvent = taskConditionWorkflow.task({
  name: 'waitForEvent',
  parents: [start],
  waitFor: [Or(new SleepCondition('1m'), new UserEventCondition('wait_for_event:start', 'true'))],
  fn: () => {
    return {
      randomNumber: Math.floor(Math.random() * 100) + 1,
    };
  },
});
```

#### Go

```go
waitForEvent := workflow.NewTask("wait-for-event", func(ctx hatchet.Context, _ any) (StepOutput, error) {
	return StepOutput{RandomNumber: rand.Intn(100) + 1}, nil //nolint:gosec
},
	hatchet.WithParents(start),
	hatchet.WithWaitFor(hatchet.OrCondition(
		hatchet.SleepCondition(1*time.Minute),
		hatchet.UserEventCondition("wait_for_event:start", ""),
	)),
)
```

#### Ruby

```ruby
WAIT_FOR_EVENT = TASK_CONDITION_WORKFLOW.task(
  :wait_for_event,
  parents: [COND_START],
  wait_for: [
    Hatchet.or_(
      Hatchet::SleepCondition.new(60),
      Hatchet::UserEventCondition.new(event_key: "wait_for_event:start")
    )
  ]
) do |input, ctx|
  { "random_number" => rand(1..100) }
end
```

### Skipping on an event

Declare a task with a `skip_if` event condition. The task will be skipped if the event arrives before the task starts.

#### Python

```python
@task_condition_workflow.task(
    parents=[start],
    wait_for=[SleepCondition(timedelta(seconds=30))],
    skip_if=[UserEventCondition(event_key="skip_on_event:skip")],
)
def skip_on_event(input: EmptyModel, ctx: Context) -> StepOutput:
    return StepOutput(random_number=random.randint(1, 100))
```

#### Typescript

```typescript
const skipOnEvent = taskConditionWorkflow.task({
  name: 'skipOnEvent',
  parents: [start],
  waitFor: [new SleepCondition('10s')],
  skipIf: [new UserEventCondition('skip_on_event:skip', 'true')],
  fn: () => {
    return {
      randomNumber: Math.floor(Math.random() * 100) + 1,
    };
  },
});
```

#### Go

```go
skipOnEvent := workflow.NewTask("skip-on-event", func(ctx hatchet.Context, _ any) (StepOutput, error) {
	return StepOutput{RandomNumber: rand.Intn(100) + 1}, nil //nolint:gosec
},
	hatchet.WithParents(start),
	hatchet.WithWaitFor(hatchet.SleepCondition(30*time.Second)),
	hatchet.WithSkipIf(hatchet.UserEventCondition("skip_on_event:skip", "")),
)
```

#### Ruby

```ruby
SKIP_ON_EVENT = TASK_CONDITION_WORKFLOW.task(
  :skip_on_event,
  parents: [COND_START],
  wait_for: [Hatchet::SleepCondition.new(30)],
  skip_if: [Hatchet::UserEventCondition.new(event_key: "skip_on_event:skip")]
) do |input, ctx|
  { "random_number" => rand(1..100) }
end
```

### Event filters

Events can be filtered using [CEL](https://github.com/google/cel-spec) expressions. The CEL expression is evaluated against the event payload, and the condition only matches if the expression returns `true`. This works identically to event filters in durable tasks.

### Pushing events

For a waiting task to proceed, something must [push an event](/v1/external-events/pushing-events) into Hatchet with a matching key. You can do this from any service that has access to the Hatchet client.

#### Python

```python
hatchet.event.push("user:create", {"should_skip": False})
```

#### Typescript

```typescript
const res = await hatchet.events.push('simple-event:create', {
  Message: 'hello',
  ShouldSkip: false,
});
```

#### Go

```go
err := client.Events().Push(
	context.Background(),
	"simple-event:create",
	EventInput{
		Message: "Hello, World!",
	},
)
if err != nil {
	return err
}
```

#### Ruby

```ruby
HATCHET.event.push("user:create", { "should_skip" => false })
```

### Combining with other conditions

Event conditions can be combined with parent and sleep conditions using or groups. For example, you can wait for _either_ an event or a timeout (whichever comes first). See [Conditions & Branching](/v1/conditions) for details.
