# Agentset Webhooks

Agentset (agentset.ai) is a managed RAG platform. Organization webhooks are created in the dashboard
(Webhooks settings) with a URL, the events you want and optional namespaces. They report document and
ingest-job lifecycle changes. Every delivery carries an `agentset-signature` header: the hex
HMAC-SHA256 of the **raw request body**, keyed with the webhook's signing secret.

## When to Use This Skill

* How do I receive Agentset webhooks?
* How do I verify the `agentset-signature` header?
* Why does my Agentset webhook endpoint return 401?
* How do I handle `document.ready`, `document.error` or `ingest_job.ready` events?

## Verification (core)

```javascript
const crypto = require('crypto');

const HEX64 = /^[0-9a-fA-F]{64}$/; // validate the format before decoding

function verifyAgentsetSignature(rawBody, signature, secret = process.env.AGENTSET_WEBHOOK_SECRET) {
  if (!secret || typeof signature !== 'string' || !HEX64.test(signature)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest(); // rawBody: Buffer
  const received = Buffer.from(signature.toLowerCase(), 'hex');                   // safe: validated
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

```

* Header: `agentset-signature`. The **whole value** is one bare hex digest: no `sha256=` prefix, no list.
* Signed content: the raw body **bytes only**. No timestamp, id or other header is signed, so there is
  no replay window to check. Never `JSON.parse` / `JSON.stringify` before verifying.
* Key: the secret's UTF-8 bytes, used as-is (not hex- or base64-decoded). Hex is case-insensitive.
* Missing, empty or malformed header, or a mismatch: `401`. Python: `hmac.compare_digest` on bytes.
* **Methods:** Agentset's docs don't name the HTTP method. Hookdeck's Agentset source pre-selects
  POST without managing the method, so allow any other method the sender uses on the source too.
  The examples accept GET, POST, PUT, PATCH and DELETE, verify first, and treat a missing body as
  empty bytes (a verified empty body is acknowledged with `200`).
* Read the body raw (`express.raw({ type: '*/*' })`) and parse only after the check. Treat every parsed
  field as untrusted and type-check it before reading into it.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentset-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentset-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentset-webhooks/examples/fastapi/).

## Event Types

The body is an envelope `{ id, event, createdAt, data }`. Dispatch on the top-level `event`:

| Event | Fires when |
| --- | --- |
| `document.queued` / `document.queued_for_resync` / `document.queued_for_deletion` | A document is queued for ingestion, re-sync or deletion |
| `document.processing` | A document starts processing |
| `document.ready` | A document finished ingesting and is searchable |
| `document.error` | A document failed (`data.error` has the reason) |
| `document.deleted` | A document was deleted |
| `ingest_job.queued` / `ingest_job.queued_for_resync` / `ingest_job.queued_for_deletion` | An ingest job is queued |
| `ingest_job.processing` | An ingest job starts running |
| `ingest_job.ready` | An ingest job completed |
| `ingest_job.error` | An ingest job failed |
| `ingest_job.deleted` | An ingest job was deleted |

The examples handle `document.ready`, `document.error`, `document.deleted`, `ingest_job.ready` and
`ingest_job.error`, and log the rest.

## Environment Variables

```bash
AGENTSET_WEBHOOK_SECRET=your_webhook_signing_secret   # Webhooks > your webhook > Update Details

```

## Delivery and Retries

Respond with a 2xx within 20 seconds. Otherwise Agentset retries up to 10 times with exponential
backoff capped at 24 hours (12 s, 2m24s, 30m8s, ...). Owners are emailed after 5, 10 and 15
consecutive failures, and the 20th disables the webhook. Make handlers idempotent (dedupe on the
envelope `id`) and do slow work asynchronously.

## Local Development

Agentset can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 agentset --path /webhooks/agentset

```

Use the printed URL as the webhook URL in Agentset.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentset-webhooks/references/overview.md) - What the webhooks are, events, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentset-webhooks/references/setup.md) - Create the webhook and find the signing secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agentset-webhooks/references/verification.md) - Signature details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/agentset-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests