> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackshift.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Events that wake your agent

> Every email, agreement, schedule, webhook and approval reaches your agent as a signed event. The SDK checks it, drops repeats and runs your handler.

> [!NOTE]
>
> **Not yet released.** This guide describes implemented changes awaiting rollout. Availability requires the corresponding backend and dashboard release.

## Goal

Know what wakes your agent, what each event carries, and how to handle delivery that happens more than once.

## Prerequisites

* The matching source in `wakeOn:` in `stackshift.yaml`.
* A handler for the event: `agent.on(event, handler)` in the SDK, or `POST /stackshift/events` on the agent’s port.

## Workflow

**Step 1**

List what wakes the agent under `wakeOn:`. `approval.decided` and `budget.warning` always arrive.

**Step 2**

Register a handler for each event you care about with `agent.on`.

**Step 3**

Make the handler safe to run twice. Delivery is at least once.

**Step 4**

To receive a webhook, add a named webhook in the agent’s settings. Its address is shown there. If you set a shared secret, it is shown once, and callers send it in the `X-StackShift-Webhook-Secret` header.

**Step 5**

Check a run on the agent’s page: it shows what woke the agent, how long it ran and how it ended.

## The events

* `inbox.message`: mail arrived at the agent’s inbox.
* `agreement.offer_received`, `agreement.agreed`, `agreement.declined`, `agreement.expired`, `agreement.waiting_for_you`: something happened in an agreement.
* `a2a.message`: a message from another agent.
* `schedule.tick`: one of the agent’s schedules fired.
* `webhook.received`: a request reached `POST https://api.stackshift.cloud/api/v1/agents/{handle}/hooks/{name}`. A hook can require the shared secret in the `X-StackShift-Webhook-Secret` header.
* `approval.decided`: you decided one of the agent’s waiting actions. It carries `admission_id`, `approval_id`, `action_key`, `decision` and the approved `arguments`, and `note` when you wrote one (up to 2,000 characters). The SDK uses it to run the saved action; your handler sees it too.
* `budget.warning`: a budget reached 80%. It carries `scope`, `percent`, `used` and `limit`.

## What an event looks like

Fields listed in `untrusted` came from outside. StackShift adds them to the run’s record of untrusted text before it delivers the event, so actions built from them ask you first.

Your handler answers `200` with `{"outcome": "done"}`, `"waiting"` (an action was saved for approval) or `"failed"` with an `error`. A `408`, `429` or `5xx` makes StackShift deliver the event again.

```json theme={null}
{
  "event": "inbox.message",
  "id": "evt_8f2c",
  "agent": "agt_41b7",
  "at": "2026-11-01T10:00:00Z",
  "data": {
    "mailbox": "supplies@agents.example.com",
    "message": { "id": "m1", "from": "Ada Buyer <ada@example.com>", "subject": "Quote", "text": "…" }
  },
  "untrusted": ["data.message.text", "data.message.subject", "data.message.from"]
}
```

## Without the SDK

Each delivery is signed with Ed25519. The signed message is the `X-StackShift-Timestamp` value, a newline, the `X-StackShift-Invocation` value, a newline, then the raw body. `STACKSHIFT_EVENT_KEY` holds the base64 public key. Refuse a timestamp more than 5 minutes old. The run token arrives in `X-StackShift-Agent-Token`, and the run id in `X-StackShift-Run`.

Drop an event whose `id` you have already handled. The SDK does this for you, in memory and in the agent’s saved state.

```ts theme={null}
import { createPublicKey, verify } from 'node:crypto'

const spki = Buffer.from('302a300506032b6570032100', 'hex')

export function isFromStackShift(headers: Headers, body: Buffer) {
  const key = createPublicKey({ key: Buffer.concat([spki, Buffer.from(process.env.STACKSHIFT_EVENT_KEY!, 'base64')]), format: 'der', type: 'spki' })
  const message = Buffer.concat([Buffer.from(`${headers.get('x-stackshift-timestamp')}\n${headers.get('x-stackshift-invocation')}\n`), body])
  return verify(null, message, key, Buffer.from(headers.get('x-stackshift-signature')!, 'base64'))
}
```

## Expected result

> [!TIP]
>
> The agent wakes for each event, the handler runs once per event id, and the run ends done, waiting or failed.

## Common failures

> [!WARNING]
>
> * The handler never runs: the event is not in `wakeOn:`, or the agent is stopped. Events for a stopped agent are held for 7 days and released when you start it. After 7 days they are dropped and you are told.
> * A run ends `timeout`: it ran longer than `max_run_seconds` (30 minutes by default).
> * The SDK answers 401: `STACKSHIFT_EVENT_KEY` is missing or the event was not signed by StackShift.
> * A webhook returns 401: the shared secret header does not match. Webhook bodies are limited to 1 MB and rate limited.

## Related guides

> [card] [Your agent’s own actions: risk, approvals and levels](https://docs.stackshift.cloud/ai-features/hosted-agents-actions.md)
>
> Declare the tools your agent has, give each a risk, and let StackShift apply the agent’s level, approvals and untrusted-text checks before they run.

> [card] [The agents SDK for TypeScript and Python](https://docs.stackshift.cloud/ai-features/hosted-agents-sdk.md)
>
> The packages `@stackshift-cloud/agent` and `stackshift-agent` give your code events, governed actions, StackShift calls, state, model clients and adapters for the Claude Agent SDK and the OpenAI Agents SDK.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.