LuaWebhook defines an HTTP endpoint that runs your execute function outside any conversation and returns its result to the caller. Once pushed and deployed, the webhook answers POST https://webhook.heylua.ai/<agentId>/<name>; the webhook ID works in place of the name. A webhook can also subscribe to platform events, which the platform delivers to execute without passing through the public URL. To have the model react to an event instead of your code, use a trigger.
Verified against lua-cli 3.33.0.
Quick example
src/webhooks/OrderPaidWebhook.ts
query, headers, and body.
Output
Constructor
string
required
Server-side identifier and the last segment of the URL. Lowercase with hyphens;
lua compile warns Webhook name should be URL-safe (lowercase, hyphens only) otherwise. Also --name for lua push webhook and --webhook-name for lua webhooks.string
required
One or two sentences shown in listings.
ZodType
Zod schema describing the query string. The platform doesn’t apply it; only the unit-test method
execute(query, headers, body) does.ZodType
Zod schema describing the request headers, with lowercase names as keys. Not applied by the platform.
ZodType
Zod schema describing the body. Not applied by the platform:
event.body arrives unvalidated, so call bodySchema.safeParse(event.body) in execute to refuse a malformed request.string
HMAC-SHA256 signing key. When set, every direct request must carry a valid
x-lua-signature header (see Signing). It must be a string literal or a constant the compiler can resolve. Rotate it by changing the value and deploying again; set '' to turn signing off on the next push.(event: LuaWebhookEvent) => Promise<any>
required
Your handler. For a direct request its return value is serialized as the JSON response body. No end user is in scope: call
User.get(userId) with an ID from the payload, or send with Channels.send.name is empty or blank: LuaWebhook requires a non-empty `name` (used as the server-side identifier).
lua compile fails with Webhook must have an execute function when execute is missing, and with Webhook `secret` must be a string literal or a compile-time-resolvable constant (a runtime expression such as `process.env.X` cannot be read at compile time) when it can’t read the secret.
Event
execute receives one LuaWebhookEvent. The type marks query, headers, and body optional; timestamp is always set.
Record<string, any>
Parsed query string;
{} when there is none.Record<string, any>
Request headers with lowercase keys.
any
Parsed JSON body as sent;
bodySchema isn’t applied to it. For a subscribed platform event it is { eventType, ...payload }.string
ISO 8601 time the request was received.
{ eventId: string; executionId: string; attempt: number }
Set on every deployed run for a subscribed platform event, absent for a direct request, and not on the type.
eventId is stable across retries of one event, executionId changes per attempt, and attempt starts at 1.Deployed runs only.
event.execution is undefined under lua test webhook; read it through a widened type, (event as typeof event & { execution?: { eventId: string; executionId: string; attempt: number } }).execution.Signing
Whensecret is set, the platform rejects a direct request before running any code unless it carries the signature header.
401 with Invalid webhook signature. Sign the bytes you send: re-serializing JSON changes key order and whitespace and invalidates the signature. Subscribed platform events don’t pass through the public URL and aren’t signed.
Delivery
The retry policy is the platform’s; there is no per-webhook setting.
event.execution.eventId stays the same across the attempts of one event, so write the handler to be idempotent; see About webhooks.
Event subscriptions
List the events a webhook can subscribe to, then subscribe a pushed webhook by name.Output
message.* event, event.body is { eventType, ...payload } where the payload is the platform’s record of that message delivery.
Methods
getSecret()
Returns the signing key, orundefined. Never log it.
getName()
Returnsname.
getDescription()
Returnsdescription.
execute()
Validates each part against its schema, builds the event, and runs your handler. Only your unit tests call it: the platform andlua test webhook pass the event to the compiled handler directly, so the validation errors below never occur in a deployed webhook.
Record<string, any>
Query parameters. Defaults to
{}.Record<string, any>
Headers. Defaults to
{}.any
Request body.
Query parameter validation failed: <ZodError>, Header validation failed: <ZodError>, or Body validation failed: <ZodError> when a schema rejects its part. This method is the only place they are raised.
Types
LuaWebhookConfig is an exported type. LuaWebhookEvent isn’t exported by name; derive it from the config.

