> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# LuaTrigger

> Trigger class and defineTrigger, with the verify, filter, transform, and tool slots that turn an inbound event into an agent turn

`LuaTrigger`, created with `defineTrigger`, turns an inbound HTTP event into an agent turn, a workflow run, or one direct tool call, without an `execute` function. Four optional slots run on the platform before anything else: `verify` rejects the request, `filter` ignores it, `transform` shapes what runs, and `tool` binds the event to one tool. Every [trigger](/concepts/triggers) has a paste-anywhere URL ending in `/trigger/<agentId>/<token>`, whose token is the secret; `lua triggers list` prints it, host included. For a handler that owns the HTTP response, use a [webhook](/reference/sdk/luawebhook) instead.

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { defineTrigger } from 'lua-cli';
```

## Quick example

```ts src/triggers/order-created.trigger.ts theme={null}
import { defineTrigger, env } from 'lua-cli';
import { z } from 'zod';

export default defineTrigger({
  name: 'order-created',
  description: 'Wakes the agent when the shop reports a new order',
  inputSchema: z.object({
    type: z.string(),
    data: z.object({ orderNumber: z.string(), total: z.number() }),
  }),
  verify: (ctx) => ctx.headers['x-shop-token'] === env('SHOP_WEBHOOK_TOKEN'),
  filter: (ctx) => ctx.body.type === 'order.created',
  transform: (ctx) =>
    `New order ${ctx.body.data.orderNumber} for ${ctx.body.data.total}. Confirm it and notify the customer.`,
});
```

Register it on `LuaAgent.triggers`. `lua push trigger --name order-created` creates the trigger on first push and uploads a version, `lua deploy trigger --name order-created --set-version latest --force` makes that version live, and `lua triggers list` prints the URL.

## Constructor

```ts theme={null}
defineTrigger<T = any>(config: LuaTriggerConfig<T>): LuaTrigger<T>
new LuaTrigger<T = any>(config: LuaTriggerConfig<T>)
```

The two forms are equivalent. `T` types `ctx.body` inside the slots.

<ParamField path="name" type="string" required>
  Server-side identifier. Lowercase with hyphens; `lua compile` warns `Trigger name should be URL-safe (lowercase, hyphens only)` otherwise. Also `--name` for `lua push trigger` and `--trigger` for `lua triggers`.
</ParamField>

<ParamField path="description" type="string" required>
  Note shown by `lua triggers list`. Not sent to the model.
</ParamField>

<ParamField path="source" type="'webhook'" default="'webhook'">
  Event source. `'webhook'` is the only value in 3.33.0.
</ParamField>

<ParamField path="inputSchema" type="ZodType">
  Zod schema describing the body, carried into the version's manifest. It doesn't narrow `ctx.body` in TypeScript; pass `T` for that.
</ParamField>

<ParamField path="verify" type="(ctx: TriggerContext<T>) => boolean | Promise<boolean>">
  Authentication gate. `false` answers `401` and records `rejected_unverified`. A signature scheme must hash `ctx.rawBody`, the exact bytes sent.
</ParamField>

<ParamField path="filter" type="(ctx: TriggerContext<T>) => boolean | Promise<boolean>">
  Relevance gate, after `verify`. `false` answers `200` and records `skipped_filtered`, so the sender sees success and doesn't retry.
</ParamField>

<ParamField path="transform" type="(ctx: TriggerContext<T>) => string | AgentInvocationInput | TriggerStartWorkflow | Promise<…>">
  Shapes what runs, after `filter`. A string becomes the message; an invocation input object (`prompt` or `messages`, `userId`, `threadId`, `systemPrompt`, and the other fields [`Agents.invoke`](/reference/sdk/agents) accepts) owns the whole turn; `{ startWorkflow }` starts a workflow run instead of a turn. Returning `null` or `undefined` fails the delivery with `500`; use `filter` to skip. Omit it to send the default message.
</ParamField>

<ParamField path="tool" type="{ name: string; input?: (ctx: TriggerContext<T>) => Record<string, unknown> | Promise<Record<string, unknown>> }">
  Runs one tool directly after `verify` and `filter` pass: no model turn and no conversation. `name` is the tool's own `name` and must be a string literal. `input` maps the context to the tool's arguments; omit it to call with `{}`. When `tool` and `transform` are both declared, the tool runs and the transform is ignored. A tool that needs human approval under [governance](/concepts/governance) is refused and the refusal is recorded.
</ParamField>

The constructor throws when:

* `name` is empty or blank: ``LuaTrigger requires a non-empty `name` (used as the server-side identifier).``
* no slot is set: `LuaTrigger requires at least one of verify, filter, transform, or tool.`
* `tool.name` is empty: ``LuaTrigger `tool` requires a non-empty `name` (the bare authored tool name).``

`lua compile` fails with `Trigger must define at least one of verify, filter, transform, or tool` and ``Trigger `tool.name` must be a static string literal (the bare authored tool name)``. It warns ``Trigger declares both `tool` and `transform` — the tool executes directly and the transform is IGNORED; remove the transform``.

## Slots

The slots run in one sandboxed execution per delivery, in order, with a 15-second budget for all of them; the sender waits for the outcome. `env()` is available inside a slot. Any throw records `failed` and answers `500`.&#x20;

Once the slots pass, the sender receives `200` with `{ status: 'accepted', executionId }` and the turn or tool runs in the background; a `startWorkflow` adds `runId`. Without `transform`, the model receives the default message.

```text theme={null}
[Trigger: <name>] <instruction>

Payload:
<JSON body>
```

The instruction is set with `lua triggers create --instruction`; without one the JSON body follows the prefix directly. The body is capped at 50,000 characters, so return a `transform` to forward chosen fields of a larger payload. A string from `transform` is sent as `[Trigger: <name>] <string>` and the instruction is not applied.

The turn runs as the `userId` a `transform` object supplies; otherwise as the trigger's bound user, the developer whose push created it or the installer who consented when installing the [agent template](/concepts/agent-templates) it came with; otherwise with the system identity.

## Statuses

`lua triggers logs --trigger <name>` shows one row per delivery.

| Status                | HTTP                                                      | Meaning                                                                                                                               |
| --------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `accepted`            | `200`                                                     | Slots passed; the turn or tool is running                                                                                             |
| `completed`           | —                                                         | The turn or tool finished; the response text is on the row                                                                            |
| `failed`              | `500`, or `200` for a `startWorkflow` configuration error | A slot threw, `transform` returned nothing, the turn failed, or `startWorkflow` named an unknown workflow or failed its `inputSchema` |
| `started_workflow`    | `200`                                                     | `transform` returned `{ startWorkflow }`; the row carries `runId`                                                                     |
| `skipped_overlap`     | `200`                                                     | The target workflow forbids concurrent runs and one was in flight                                                                     |
| `rejected_unverified` | `401`                                                     | `verify` returned `false`                                                                                                             |
| `skipped_filtered`    | `200`                                                     | `filter` returned `false`                                                                                                             |
| `skipped_inactive`    | `200`                                                     | The trigger was deactivated                                                                                                           |

An unknown agent, an unknown token, or a mismatched pair answers `404` with `Trigger not found` and records nothing. Rotate a leaked URL with `lua triggers rotate-token --trigger <name>`.

## Methods

An instance exposes read-only `name`, `description`, `source`, `inputSchema`, `verify`, `filter`, `transform`, and `tool`, plus two getters.

| Method             | Returns  |
| ------------------ | -------- |
| `getName()`        | `string` |
| `getDescription()` | `string` |

## Types

### TriggerContext

The argument every slot receives. Exported.

<ResponseField name="body" type="T">
  Parsed request body.
</ResponseField>

<ResponseField name="rawBody" type="string">
  The unparsed request bytes as UTF-8; optional on the type. Hash these for a signature check; `JSON.stringify(ctx.body)` doesn't reproduce them.
</ResponseField>

<ResponseField name="headers" type="Record<string, any>">
  Request headers with lowercase keys, for example `ctx.headers['x-hub-signature-256']`.
</ResponseField>

<ResponseField name="query" type="Record<string, any>">
  Parsed query string.
</ResponseField>

<ResponseField name="triggerName" type="string">
  This trigger's `name`.
</ResponseField>

<ResponseField name="source" type="string">
  `'webhook'`.
</ResponseField>

### TriggerStartWorkflow

The object `transform` returns to start a [workflow](/reference/sdk/workflows) run. Not exported by name.

<ResponseField name="startWorkflow" type="object" required>
  <Expandable title="properties">
    <ResponseField name="name" type="string" required>
      Workflow name or ID on this agent.
    </ResponseField>

    <ResponseField name="input" type="unknown">
      Validated against the workflow's `inputSchema`.
    </ResponseField>

    <ResponseField name="idempotencyKey" type="string">
      A redelivery with the same key returns the same run. Set it from the sender's delivery ID; without it every delivery starts a run.
    </ResponseField>

    <ResponseField name="notify" type="'emailApp' | 'email' | 'app' | 'off'">
      Receipt delivery. Default `'off'`.
    </ResponseField>

    <ResponseField name="correlationKey" type="string">
      A literal or a `'${input.<field>}'` template the trigger fills.
    </ResponseField>

    <ResponseField name="tags" type="string[]">
      Tags on the run.
    </ResponseField>

    <ResponseField name="initialState" type="Record<string, unknown>">
      Seeds the run's state. At most 64 KB.
    </ResponseField>

    <ResponseField name="replyTo" type="{ channel: string; threadId: string }">
      Makes it a customer run that replies on that thread.
    </ResponseField>

    <ResponseField name="onBehalfOf" type="{ kind: 'customer'; externalId: string }">
      A customer principal without a reply channel.
    </ResponseField>
  </Expandable>
</ResponseField>

The run is created in the delivery request as the system principal and no turn fires; a `prompt` or `messages` beside `startWorkflow` is ignored, and a declared `tool` still wins over the whole transform.

`LuaTriggerConfig` and `TriggerContext` are exported types. The invocation input object is documented on [Agents](/reference/sdk/agents).

## See also

* [About triggers](/concepts/triggers)
* [Create a trigger](/build/create-a-trigger)
* [lua triggers](/reference/cli/triggers)
* [LuaWebhook](/reference/sdk/luawebhook)
* [Workflows](/reference/sdk/workflows)
