Lure#
Lure is a library for processing webhook events into LLM-consumable prompts. It looks something like this:
- An HTTP request is received at a path like
/webhooks/tangled - Lure strips the configured base path and matches the remainder to a template
file on disk, e.g. with base path
/webhooks, the path/webhooks/tangledmatches./lures/tangled.lure. Nested paths are supported:/webhooks/github/pushmatches./lures/github/push.lure. The.lurefile is part config, part template (more on this later). - According to the config, Lure validates the webhook according to the specified strategy (e.g. API key or HMAC verification)
- If validation succeeds, Lure executes some callback with the string result of evaluating the template contents with the webhook payload.
The goal is to trigger LLM executions in response to webhook events, but without the requirement for Zapier/IFTTT and with as little HTTP endpoint exposure as possible. Consumers of the Lure library provide their own HTTP server--no server is provided by Lure.
.lure file format#
Lures are intended to be written by LLMs, so a .lure file is essentially a
Markdown file with frontmatter. Here is a contrived example:
---
verify:
hmac:
header: X-My-Header-Signature
secret: $MY_WEBHOOK_SECRET
payload:
contentType: json
config:
arbitrary: true
someValue: 3
---
You have received information about a {{ payload.event }} event on My
Service. Read the following payload and respond according to your skills:
{{ payload.body }}
Different verification methods can be supported, for generic implementations or vendor-specific requirements. Only one verification method can be specified per lure.
Note: A lure without a
verifyblock will accept requests from any sender. Unverified lures should only be used on trusted internal networks; any publicly-exposed lure endpoint should specify a verification method. SetallowUnverified: falseat handler creation time to reject unverified lures at startup.
Template scope#
Templates are evaluated using Liquid. The following variables are available:
payload: The request body. ForcontentType: json, this is the parsed JSON value.headers: The request headers as a plain object with lowercase keys (e.g.{{ headers["x-my-header"] }}).query: The query string parameters as a plain object (e.g.{{ query.foo }}).
Use {{ expression }} to interpolate values and {% if %}...{% endif %} for
conditionals.
Usage#
Use either the @lure/fetch or @lure/express packages to construct an
endpoint handler that suits your HTTP server of choice.
Both handler constructors take the following parameters:
basePath: The URL path prefix under which all lure endpoints are mounted, e.g./webhooks. Lure only handles requests whose path begins with this prefix; all other requests are passed through.configSchema: A Standard Schema for validating any extra config you would like to allow in theconfigfrontmatter keyluresDir: A path to a directory of lurescallback: A function that you want to run in response to incoming webhooks. It will be called with the templated promptpromptand the value of theconfigfrontmatter value.maxAttempts: How many times to attempt thecallbackbefore giving up. Defaults to1(no retries). If all attempts fail, the webhook is dropped.allowUnverified: Iffalse, lures without averifyblock will be rejected at startup. Defaults totrue.watch: Iftrue, Lure watchesluresDirfor changes and reloads lures as they are added, modified, or removed. Defaults tofalse.
Generating lures#
Since .lure files follow a structured format, they are well-suited to be
generated by an LLM. Copy the prompt below into any LLM conversation, replace
the placeholder at the end with a description of your webhook, and the LLM will
produce a ready-to-use .lure file.
You are generating a .lure file for the Lure webhook library. A .lure file is
a Markdown file with YAML frontmatter. The filename without the .lure extension
determines the webhook path relative to the configured base path: `push.lure`
handles `<basePath>/push`, and `github/push.lure` handles
`<basePath>/github/push`.
## Frontmatter
### `verify` (optional)
Specifies how to authenticate incoming webhook requests. Omit if the provider
does not sign requests. Only one strategy may be specified.
HMAC (e.g. GitHub, GitLab — computes SHA-256 over the request body):
```yaml
verify:
hmac:
header: X-Hub-Signature-256 # or query: param-name
secret: $ENV_VAR_NAME # must be an environment variable reference
```
Literal (e.g. Forgejo, plain API key — compares value directly):
```yaml
verify:
literal:
header: Authorization # or query: uid
secret: $ENV_VAR_NAME # must be an environment variable reference
```
### `payload` (optional)
```yaml
payload:
contentType: json # currently the only supported value
```
### `config` (optional)
An arbitrary object passed as-is to the application callback. Use this for any
application-specific values you want to associate with this lure.
```yaml
config:
key: value
```
## Template body
Below the frontmatter is a Liquid template (https://liquidjs.com). Write it as
a natural language prompt for the LLM that will process the webhook. The
following variables are available:
- `payload` — the request body (parsed JSON for `contentType: json`)
- `headers` — request headers as a plain object with lowercase keys (e.g. `{{ headers["x-my-header"] }}`)
- `query` — query string as a plain object (e.g. `{{ query.foo }}`)
Use `{{ expression }}` to interpolate values and `{% if %}...{% endif %}` for
conditionals.
## Task
Generate a .lure file for the following webhook:
[DESCRIBE THE WEBHOOK SOURCE, EVENT TYPE, PAYLOAD SHAPE, VERIFICATION METHOD,
AND WHAT THE LLM RECEIVING THE PROMPT SHOULD DO IN RESPONSE]
Lifecycle#
At Startup#
- The parent program creates either a fetch or an Express lure handler, as described above.
- Lure traverses the specified directory and discovers any
.lurefiles. - Each
.lurefile has their frontmatter validated. The parsed config and template content are cached. - If
watchis enabled, a filesystem watcher is started onluresDir. When a.lurefile is added or modified, it is re-validated and its cache entry updated. If validation fails, the previous cached version is retained and an error is logged. When a.lurefile is removed, its cache entry is discarded. Changes take effect immediately — queue processing always uses the current cache, so a reload applies to any items already in the queue as well.
Per Request#
- The requested path is checked against registered lure paths.
- On a hit, we immediately return a 204 response, to keep the response time as low as possible.
- Webhook requests are copied and added to an in-memory queue for processing. Requests in the queue will be lost if the process exits.
- The queue processor removes requests from the queue FIFO. If verification fails, the request is dropped.
- On successful verification, the lure template is evaluated using the request.
- The provided
callbackis executed with the fully-formed prompt and the config object from the original.lurefrontmatter. If the callback throws, it will be retried up tomaxAttemptstimes before the webhook is dropped.