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

# Webhooks

> Manage webhook endpoints, test delivery, and verify signatures.

<Info>
  Webhook endpoints can also be managed in the [KeyStone Dashboard](https://app.keystoneos.xyz) under **Settings > Webhooks** - no code required.
</Info>

## create

Create a webhook endpoint. The signing secret is auto-generated and only returned on creation.

```typescript theme={null}
const endpoint = await client.webhooks.create({
  url: "https://your-app.com/webhooks/keystone",
  description: "Production webhook",
  events: ["settlement.state.*", "settlement.state.finalized"],
});

// IMPORTANT: store the secret securely - it's only returned once
console.log(endpoint.secret);
```

| Field         | Type       | Required | Default        | Description                    |
| ------------- | ---------- | -------- | -------------- | ------------------------------ |
| `url`         | `string`   | Yes      | -              | HTTPS endpoint URL             |
| `description` | `string`   | No       | -              | Human-readable label           |
| `events`      | `string[]` | No       | `["*"]`        | Event patterns to subscribe to |
| `secret`      | `string`   | No       | auto-generated | Custom signing secret          |

### Event patterns

| Pattern                              | Matches                           |
| ------------------------------------ | --------------------------------- |
| `*`                                  | All events                        |
| `settlement.*`                       | All settlement events             |
| `settlement.state.*`                 | All settlement state events       |
| `settlement.state.finalized`         | Settlement completed successfully |
| `settlement.state.awaiting_deposits` | Escrow ready for deposits         |
| `test.ping`                          | Test delivery event               |

***

## list / get

```typescript theme={null}
const { items } = await client.webhooks.list();

const endpoint = await client.webhooks.get("endpoint-uuid");
console.log(endpoint.url);
console.log(endpoint.events);
console.log(endpoint.isActive);
```

### WebhookEndpointRead

| Field         | Type             | Description                 |
| ------------- | ---------------- | --------------------------- |
| `id`          | `string`         | Endpoint UUID               |
| `url`         | `string`         | Delivery URL                |
| `description` | `string \| null` | Label                       |
| `events`      | `string[]`       | Subscribed event patterns   |
| `isActive`    | `boolean`        | Whether delivery is enabled |
| `createdAt`   | `string`         | Creation timestamp          |
| `updatedAt`   | `string`         | Last update timestamp       |

***

## update

```typescript theme={null}
await client.webhooks.update("endpoint-uuid", {
  url: "https://new-url.com/webhooks",
  events: ["*"],
  isActive: true,
});
```

| Field         | Type             | Description                |
| ------------- | ---------------- | -------------------------- |
| `url`         | `string`         | New delivery URL           |
| `description` | `string \| null` | New label                  |
| `events`      | `string[]`       | New event patterns         |
| `isActive`    | `boolean`        | Enable or disable delivery |

***

## delete

```typescript theme={null}
await client.webhooks.delete("endpoint-uuid");
```

***

## test

Send a `test.ping` event to verify delivery works.

```typescript theme={null}
const result = await client.webhooks.test("endpoint-uuid");

console.log(result.success);    // true
console.log(result.statusCode); // 200
console.log(result.durationMs); // 142
console.log(result.error);      // null
```

***

## rotateSecret

Rotate the signing secret. The previous secret stays valid for 24 hours so you can update your handler without downtime.

```typescript theme={null}
const result = await client.webhooks.rotateSecret("endpoint-uuid");

console.log(result.newSecret);                  // "whsec_..."
console.log(result.previousSecretValidUntil);   // 24h from now
```

***

## listDeliveries

View delivery attempt history for an endpoint.

```typescript theme={null}
const { items: deliveries } = await client.webhooks.listDeliveries("endpoint-uuid");

for (const d of deliveries) {
  console.log(`${d.event}: ${d.success ? "OK" : "FAILED"} (${d.responseStatus}) - ${d.durationMs}ms`);
  if (d.error) console.log(`  Error: ${d.error}`);
}
```

### WebhookDeliveryLogRead

| Field            | Type             | Description                |
| ---------------- | ---------------- | -------------------------- |
| `id`             | `string`         | Delivery UUID              |
| `endpointId`     | `string`         | Endpoint UUID              |
| `event`          | `string`         | Event name                 |
| `responseStatus` | `number \| null` | HTTP response status       |
| `durationMs`     | `number \| null` | Request duration           |
| `success`        | `boolean`        | Whether delivery succeeded |
| `error`          | `string \| null` | Error message if failed    |
| `createdAt`      | `string`         | Delivery timestamp         |

***

## verifySignature

Verify a webhook signature locally. This is a synchronous operation - no API call.

```typescript theme={null}
import { verifyWebhookSignature } from "@keystoneos/sdk";

app.post("/webhooks/keystone", express.raw({ type: "application/json" }), (req, res) => {
  const isValid = verifyWebhookSignature(
    req.body,
    req.headers["x-keystone-signature"] as string,
    process.env.WEBHOOK_SECRET!,
  );

  if (!isValid) return res.status(401).send("Invalid signature");

  const { event, data } = JSON.parse(req.body);

  switch (event) {
    case "settlement.state.finalized":
      console.log(`Finalized: ${data.settlement_id}`);
      break;
    default:
      if (event.startsWith("settlement.state.")) {
        console.log(`${data.settlement_id}: ${data.state}`);
      }
  }

  res.sendStatus(200);
});
```

<Note>
  Use `express.raw()` (not `express.json()`) so the middleware receives the raw body for signature verification. Parsing the body before verification can change whitespace and break the signature.
</Note>

The signature is computed as `HMAC-SHA256(webhook_secret, raw_body)` and sent in the `X-Keystone-Signature` header as a hex string. During secret rotation, the previous secret's signature is sent in `X-Keystone-Signature-Previous`.
