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

# Write Hooks

> Subscribe to TinyCloud writes with SSE or durable webhooks.

Use write hooks when you need to react to TinyCloud writes without polling.
The SDK exposes the same hook scope model for live SSE subscriptions and
durable webhooks.

## Choose a delivery mode

* use SSE when the consumer stays connected inside your app or worker
* use webhooks when you need HTTP POST delivery, retries, or decoupled work

Both modes scope to:

* `space`
* `service`
* optional `pathPrefix`
* optional `abilities`

## Subscribe to live writes

```typescript theme={null}
await tc.signIn();

const stream = tc.hooks.subscribe([
  {
    space: tc.spaceId!,
    service: "sql",
    pathPrefix: "main.db",
    abilities: ["tinycloud.sql/write"],
  },
]);

for await (const event of stream) {
  console.log(event.type, event.service, event.path);
}
```

`subscribe()` is live-only. It does not replay missed events after reconnect.

## Register a webhook

```typescript theme={null}
await tc.signIn();

const created = await tc.hooks.register({
  space: tc.spaceId!,
  service: "kv",
  pathPrefix: "documents",
  abilities: ["tinycloud.kv/put", "tinycloud.kv/del"],
  callbackUrl: "https://example.com/tinycloud/hooks",
  secret: process.env.TINYCLOUD_WEBHOOK_SECRET!,
});
```

The webhook secret is stored encrypted on the server. TinyCloud signs each
delivery with `X-TinyCloud-Signature`.

## Verify deliveries before parsing them

The signature is `sha256=<hex HMAC>` over the exact request-body bytes. Capture
the raw body and compare the HMAC in constant time before accepting the event.
For example, with Express:

```typescript theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();

app.post('/tinycloud/hooks', express.raw({ type: 'application/json' }), (req, res) => {
  const secret = process.env.TINYCLOUD_WEBHOOK_SECRET;
  const signature = req.header('X-TinyCloud-Signature');
  const match = signature?.match(/^sha256=([0-9a-f]{64})$/i);

  if (!secret || !match) return res.sendStatus(401);

  const expected = createHmac('sha256', secret).update(req.body).digest();
  const received = Buffer.from(match[1], 'hex');
  if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  console.log(event.id, event.service, event.path);
  return res.sendStatus(204);
});
```

Do not run JSON middleware before this route: re-serializing parsed JSON changes
the signed bytes. Keep the webhook secret in a secret manager and rotate it by
re-registering the hook.

Each POST body is one write event:

| Field | Type | Meaning |
| - | - | - |
| `type` | `"write"` | Event kind |
| `id` | `string` | Stable event identifier |
| `space` | `string` | Affected TinyCloud space |
| `service` | `string` | `kv`, `sql`, or `duckdb` |
| `ability` | `string` | Capability for the write, such as `tinycloud.kv/put` |
| `path` | `string` (optional) | Affected key or database path |
| `actor` | `string` | DID that performed the write |
| `epoch` | `string` | Node epoch identifier |
| `eventIndex` | `number` | Event position within the epoch |
| `timestamp` | `string` | Event time |

Use `X-TinyCloud-Delivery-Id` as an idempotency key for delivery attempts and
`X-TinyCloud-Event-Id` for the underlying event.

## Inspect and remove hooks

```typescript theme={null}
await tc.signIn();

const list = await tc.hooks.list({
  space: tc.spaceId,
  service: "kv",
  pathPrefix: "documents",
});

await tc.hooks.unregister("hook-id", {
  target: {
    space: tc.spaceId!,
    service: "kv",
    pathPrefix: "documents",
  },
});
```

## Delivery semantics

* SSE is best-effort live delivery only
* webhooks are durable and at-least-once
* duplicates are possible for webhooks, so receivers must be idempotent

The node normalizes `pathPrefix` by trimming surrounding slashes and matches
the exact path or descendants beneath it.

## Verification note

Verified against the TinyCloud SDK hook types and the node webhook dispatcher.


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