Overview
LuaTrigger (created with defineTrigger) wakes your agent when an external service posts to a trigger URL. Unlike a webhook, a trigger has no execute function — the agent turn itself does the work. Your only customisation surface is three optional, declarative slots that run server-side before the agent is invoked:
verify— authenticate the request (e.g. an HMAC signature check). Returnfalseand the request is rejected with HTTP 401; the agent never runs.filter— decide whether this event matters. Returnfalseand the sender gets HTTP 200, but no agent invocation happens.transform— shape what the agent receives. Return a message string (or a full invocation input) instead of the raw payload.
lua push, every event that passes verify and filter starts an agent turn with the transformed message. There is no handler code to maintain — the agent’s persona, skills, and tools take it from there.
A trigger needs at least one of
verify, filter, or transform — a trigger with none of the three is just a paste-anywhere URL trigger, which you can create without any code via lua triggers create.Triggers vs Webhooks vs Jobs
Reach for
LuaWebhook when the caller needs a specific response body or you want to run code without involving the agent. Reach for LuaTrigger when the right reaction to an event is an agent turn — triage this PR, answer this ticket, follow up on this order. See LuaWebhook and LuaJob.
Use Cases
Git Events
“A PR was assigned to me — triage it” with signature verification
Payment Events
Wake the agent on a successful Stripe payment, ignore the rest
Alerting
Point a monitoring tool at the trigger URL; the agent investigates
Automation Glue
Any service that can POST JSON can start an agent turn
Defining a Trigger
Three authoring shapes are recognised — pick whichever fits your codebase:Configuration Parameters
Required Fields
string
required
Unique trigger name, used as the server-side identifier.Format: URL-safe — a lowercase letter followed by lowercase letters, digits, and hyphens (e.g.
'order-created', 'github-pr-assigned'). Other names compile with a warning.string
required
Short description shown in trigger listings. It is a note for you — it is not sent to the agent.
Slots (at least one required)
function
Authentication gate. Return
false (or a promise of false) to reject the request with HTTP 401 and skip the agent entirely. Put HMAC signature checks here.Signature: (ctx: TriggerContext) => boolean | Promise<boolean>function
Relevance gate, run after
verify. Return false to acknowledge the event with HTTP 200 but skip the agent — the way to intentionally ignore events you don’t care about, without making the sender think delivery failed.Signature: (ctx: TriggerContext) => boolean | Promise<boolean>function
Shapes the agent input, run after
filter. Return either:- a string — becomes the agent’s message for the turn, or
- a full invocation input object (
{ messages | prompt, userId?, threadId?, systemPrompt?, ... }) — you own the whole turn.
null/undefined is an error (the delivery fails with HTTP 500) — use filter to skip events, not transform.Signature: (ctx: TriggerContext) => string | AgentInvocationInput | Promise<string | AgentInvocationInput>Omit transform to use the default payload-forwarding format described in Default Agent Message.Optional Fields
'webhook'
default:"'webhook'"
Event source. Only
'webhook' (an HTTP POST to the trigger URL) is currently supported.ZodType
Optional Zod schema describing the event body. To type
ctx.body in your slots, also pass the payload type as the generic: defineTrigger<MyEvent>({ ... }).TriggerContext
Every slot receives the same context object:Header keys arrive lowercased — read
ctx.headers['x-hub-signature-256'], not ctx.headers['X-Hub-Signature-256'].Slot Semantics
Slots run server-side, in order, on every delivery. Their outcome decides the HTTP response the sender sees:
A few facts worth knowing:
- Slots run in a sandbox with a 15-second budget. Keep them fast and computational — signature checks, field comparisons, string building. Don’t make network calls from slots; if the agent needs external data, let the agent fetch it with its tools during the turn.
verifyfailures return 401, not 500. Webhook providers treat a 401 as a configuration error and won’t retry-storm you the way they would on a 5xx.filterfailures return 200. The sender sees a successful delivery, so it won’t retry an event you deliberately ignored.- Node’s built-in
cryptomodule is available in slots (forcreateHmac,timingSafeEqual), andenv('KEY')reads your agent’s environment variables — the sanctioned way to get secrets into averifycheck. - The agent turn is fire-and-forget. Once the slots pass, the sender gets its 200 immediately; the agent runs in the background and the outcome lands in the execution log.
Default Agent Message
When you omittransform, the agent receives the event in a standard format. With an instruction set (via lua triggers create --instruction "..."):
Payload: label:
- The
[Trigger: <name>]prefix is always present, so the agent (and any follow-up turns) can tell what started the conversation. - The instruction is the place to tell the agent what to do with the event.
- The payload is capped at 50,000 characters; anything longer is truncated.
transform overrides all of this:
- Return a string → the agent’s message becomes
[Trigger: <name>] <your string>. The trigger’s instruction is not applied — your transform owns the message. This is the way to forward hand-picked fields from large payloads instead of hitting the 50k cap. - Return an invocation input object → you own the entire turn: message content,
userId(to run as a specific user with conversation history),threadId,systemPrompt, and so on. The channel is always recorded astrigger.
Trigger URLs and Token Security
Every trigger gets a URL of the form:- The token is the secret — anyone who has the full URL can fire the trigger (subject to your
verifyslot). Treat the URL like a credential. - Unknown agent, unknown token, or a mismatched pair all return the same 404 — the URL shape leaks nothing about which part was wrong.
- If a URL leaks, rotate it:
lua triggers rotate-token --trigger <name>. The old URL stops working immediately and the CLI prints the new one.
Complete Example: GitHub Pull Request Trigger
A production-shaped trigger: HMAC signature verification over the raw bytes, a filter that only lets through PRs assigned to a configured user, and a compact transform (a raw GitHub PR payload is enormous and mostly noise for the agent).application/json), and set GITHUB_WEBHOOK_SECRET and GITHUB_USERNAME with lua env.
Minimal Example: Filter Only
The smallest useful trigger — no verification, no transform, just a relevance gate. The agent receives the default[Trigger: …] Payload: … message.
Using with LuaAgent
Register triggers on your agent configuration:lua push, each trigger is compiled, versioned, and deployed like any other primitive, and your lua.skill.yaml gains a triggers: section tracking the deployed name, ID, and version:
Observability
Every delivery — invoked, filtered, rejected, or failed — is recorded as an execution. Inspect them with the CLI:
See the Triggers Command for the full management workflow — listing, creating URL triggers, activating, rotating tokens, and deleting.
Related APIs
Triggers Command
Manage triggers from the CLI — list, logs, rotate-token
LuaWebhook
Full request/response control with your own execute function
LuaJob
Scheduled and queued background work
Environment Variables
env(‘KEY’) — secrets for your verify slot
See Also
- LuaAgent - Registering triggers on your agent
- LuaWebhook - When you need to own the HTTP response
- Triggers Command - CLI management and the paste-anywhere URL workflow

