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

# Webhooks

> Receive signed lifecycle events: a run completed, failed or waits; a tool changed; credentials expired; an end user nears a limit.

Rewind tells your harness when something happens to a run, so it can resume a conversation when a long run ends,
show a pending approval, or warn about a quota. Deliveries follow [Standard Webhooks](https://www.standardwebhooks.com):
an HMAC-SHA256 signature over `{id}.{timestamp}.{body}`, with 5 minutes of tolerance on the timestamp.

## Subscribe

```ts theme={null}
const subscription = await rewind.subscriptions.create({
  url: "https://harness.example.com/rewind-events",
  events: ["run.completed", "run.failed", "run.suspended", "quota.alert"], // every event when empty
  labels: { agent: "triage" }, // optional: only runs carrying these labels
});

subscription.secret; // whsec_…: only in this answer, keep it
```

Subscriptions belong to your organization: one receives the events of every end user, each naming its namespace.
Deliveries go to public `https://` URLs.

## Verify and handle

Pass the body exactly as received, before any JSON parsing. Answer fast, then work. A retried delivery keeps its
`webhook-id` header: deduplicate on it.

```ts Express theme={null}
import express from "express";
import { verifyWebhook, WebhookVerificationFailed } from "@rewind.codes/sdk";

const app = express();

app.post("/rewind-events", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body, req.headers, process.env.REWIND_WEBHOOK_SECRET!);
  } catch (error) {
    if (error instanceof WebhookVerificationFailed) return res.sendStatus(400);
    throw error;
  }
  res.sendStatus(204);
  if (alreadySeen(req.headers["webhook-id"])) return;

  if (event.type === "run.completed") resumeConversation(event.data.run); // event.data.run.namespace: the end user
  if (event.type === "run.suspended") showWhatItWaitsFor(event.data.run.waitingFor);
  if (event.type === "quota.alert") warnCustomer(event.data); // namespace (null: the ceiling), quota, threshold
});
```

```ts Node http theme={null}
import { createServer } from "node:http";
import { verifyWebhook, WebhookVerificationFailed, type WebhookEvent } from "@rewind.codes/sdk";

createServer(async (req, res) => {
  const chunks: Buffer[] = [];
  for await (const chunk of req) chunks.push(chunk as Buffer);

  let event: WebhookEvent;
  try {
    event = verifyWebhook(Buffer.concat(chunks), req.headers, secret);
  } catch (error) {
    if (error instanceof WebhookVerificationFailed) return void res.writeHead(400).end();
    throw error;
  }
  res.writeHead(204).end();
  handle(event);
}).listen(3000);
```

`event.type` narrows `event.data` to the right shape. The complete program, with a run that suspends and completes, is in
[`examples/webhooks`](https://github.com/planchon/rewind.codes/tree/main/examples/webhooks).

## Events

| Event | When | `data` |
| - | - | - |
| `run.completed` | The run completed | `run`, with its `output` |
| `run.failed` | The script failed | `run`, with its `error` |
| `run.cancelled` | The run ended cancelled | `run` |
| `run.aborted` | The host ended the run: a limit, a deadlock past grace | `run`, with its `abort` |
| `run.suspended` | An activation left the run waiting for something else than its calls in flight: a signal, a timer, an approval | `run`, with its `waitingFor` |
| `run.quarantined` | The run was quarantined: release it or give it up | `run`, with its `quarantine` |
| `tool.schema_changed` | A call was parked because the tool's schema changed during the run | The run, the call and the change |
| `credentials.expired` | A call was parked because the server answered 401 | The run, the call and the server |
| `quota.alert` | An end user, or your organization, crossed 80 % or 100 % of a limit | `namespace` (`null` for the ceiling), `quota`, `threshold`, `used`, `limit`, `period` |

Every event about a run carries the run's state as the event left it, its `namespace` and its `labels`.

## Manage subscriptions

```ts theme={null}
const subscriptions = await rewind.subscriptions.list();
await rewind.subscriptions.delete(subscription.id);
```


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