Skip to main content
A webhook is an HTTP endpoint on your agent that runs your code when something happens outside a conversation: a payment settles, an order ships, a ticket changes state. It exists because a tool runs only when the model calls it mid-conversation, and many events have no conversation to belong to.

How a webhook handles a request

You define a webhook with new LuaWebhook({ name, description, execute }) and register it on the agent’s webhooks array. After lua push webhook and a deploy, Lua serves it at https://webhook.heylua.ai/<agentId>/<webhookId>, and also at https://webhook.heylua.ai/<agentId>/<webhook-name>; lua webhooks view lists both identifiers. A POST to that URL calls execute with one event object, { query, headers, body, timestamp }, exactly as the request arrived. The optional zod schemas (querySchema, headerSchema, bodySchema) describe the shape you expect; the platform doesn’t apply them, so a request they would reject still reaches execute, and you refuse it there with schema.safeParse. Whatever execute returns is the HTTP response body, with status 200; a handler that throws answers 500 Internal server error, and your error text goes to lua logs, not to the caller. The request is handled synchronously and Lua never retries it; the caller receives your return value and applies its own retry policy, as Stripe, Shopify, and GitHub all do.
src/webhooks/OrderShippedWebhook.ts
A webhook runs with no conversational end user, so User.get() with no argument has nobody to return. Store the Lua user ID in the external system when you create the order or payment, and pass it to User.get(userId) in the handler. From there the handler can send a message, change stored data, start a workflow, or hand the event to the model with Agents.invoke. The execution contexts page lists what else is available.

Verifying who is calling

A webhook URL is public. Set secret on the definition and Lua runs execute only for requests that carry x-lua-signature: sha256=<hex>, the HMAC-SHA256 of the raw request body keyed with that secret. A missing, malformed, or wrong signature is answered with 401 Invalid webhook signature and your code never runs. The secret must be a string literal or a constant the compiler can read; lua compile fails on a runtime expression rather than deploy an unsigned webhook. Vendors that sign with their own scheme (Stripe’s stripe-signature, GitHub’s x-hub-signature-256) never send x-lua-signature, so leave secret unset for them and verify their header inside execute. The LuaWebhook reference has the full signing detail and a sender example.

Platform event subscriptions

A webhook can also receive events from Lua itself. lua webhooks subscribe --webhook-name <name> --event message.delivered subscribes it to a WhatsApp delivery receipt; the subscribable events are message.received, message.sent, message.delivered, message.read, message.failed, and message.played (lua webhooks list-events prints them). Lua calls execute with query and headers empty and the receipt in body, with its eventType as the first field. Lua delivers a subscribed event from a durable queue, at least once: after a failed attempt the platform tries again until it has made 3 attempts in total, 60 seconds apart, and event.execution.eventId stays the same across them. Write the handler to be idempotent: key side effects on that event ID, or on the message ID in the payload, rather than on “this call happened”. An event whose envelope exceeds 50,000 characters runs once and is never retried.

Webhooks, triggers, and integration webhooks

Pick a webhook when you need to control the response or do work without involving the model. Pick a trigger when the event should wake the agent and nothing more. Pick an integration webhook when the source is one of Lua’s connected integrations, so you don’t have to register a URL with the vendor yourself.

When to use a webhook

  • An external system must notify the agent, and you need to respond to it, transform its payload, or update state: webhook.
  • The event should become a message to the agent, a single tool call, or a workflow start, with no custom code: trigger.
  • The work recurs on a schedule rather than on an event: job.
  • The work is multi-step, long-running, or needs approval: let the webhook call Workflows.start and return.

Limits

  • Lua budgets 180 seconds for a handler; there is no per-webhook timeout setting. The caller is waiting, so hand long work to a job or a workflow and return.
  • Lua does not retry direct requests. A handler that throws answers 500.
  • A deactivated webhook (lua webhooks deactivate --webhook-name <name>) answers 404 Webhook not found, the same as one that doesn’t exist.
  • secret must be a compile-time literal. Setting it to '' and pushing removes signing.
  • Only the six message.* events are subscribable; delivered, read, failed, and played are WhatsApp status receipts.

Next steps

Handle a webhook

Define, sign, test, and release one.

LuaWebhook reference

Schemas, the event object, signing, and errors.

lua webhooks

View, activate, subscribe, and deploy.

Triggers

The no-code alternative that wakes the agent.