# `SuperPi.Triggers`
[🔗](https://github.com/oleksandr/super-pi-beam/blob/v0.1.0/lib/super_pi/triggers.ex#L1)

Webhook triggers: `POST /v1/triggers/:token`.

A third party gets a scoped secret that wakes exactly one schedule (`kind: "webhook"`). The
token is not the gateway bearer token. Only its SHA-256 is stored, and it is compared in
constant time (`Plug.Crypto.secure_compare/2`). The route runs before the JSON parser and
reads its own body, so nobody reaches a parser or a limit without a valid token, and the
body cap is enforced while reading.

| Answer | Meaning |
|---|---|
| `202` | Queued. The sender never waits for an LLM turn. |
| `401` | Unknown, cancelled or paused trigger. One answer for all three, so a token cannot be probed. |
| `400` | Body is not valid UTF-8. |
| `413` | Body is larger than `config :super_pi, :trigger_max_bytes` (default 32768). |

The body is put into the schedule's prompt, fenced and marked as data from an outside sender
(`{{payload}}` in the prompt marks the place, else it is appended). It is untrusted input to
an unattended agent: give a webhook schedule a prompt and a harness that cannot do harm with it.

An `Idempotency-Key` header (up to 200 printable characters) is the second half of the unique job
key, so a retried delivery is a no-op for as long as Oban keeps the job (24 h with the default
`Pruner`). Without it every delivery is a new run.

Each accepted delivery adds a note (payload, truncated) to `pi.inbox` of the owning conversation.

Webhooks exist only where a harness opts in: `Plugins.Scheduler` creates a webhook schedule only
when its config has `allow_webhooks: true` (default `false`). Spending money from an endpoint
that is not behind the bearer token has to be a deliberate choice.

# `handle`

```elixir
@spec handle(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
```

Handles a delivery for `token`. Reads the body itself, so call it before `Plug.Parsers`.
Always answers; returns the conn.

# `max_bytes`

Maximum body size in bytes.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
