# Grist Webhooks

Grist (spreadsheet-database) can notify an external URL whenever rows are added to, or
modified in, one table of a document. Payloads are **not signed**. The only credential is the
optional **Header Authorization** field, a static string Grist sends verbatim as the
`Authorization` header.

## When to Use This Skill

* How do I receive Grist webhooks?
* How do I check the `Authorization` header Grist sends with a webhook?
* Does Grist sign webhook payloads? (No: static header credential only)
* Why does my Grist webhook endpoint return 401?
* How do I handle the JSON array of rows in a Grist webhook, for `add` and `update` events?

## Verification (core)

In Grist, set **Header Authorization** to the credential, for example `Bearer my-secret-token`.
Paste that value **exactly as set in Grist, including any "Bearer " or "Basic " prefix**. The
key is the **whole header value**: compare the full `Authorization` header to it. Do not strip,
parse or base64-decode the prefix.

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

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyGristRequest(headers, expected = process.env.GRIST_AUTHORIZATION) {
  if (!expected) return false;                 // never accept when unconfigured
  const received = headers['authorization'];   // full value, e.g. "Bearer my-secret-token"
  return Boolean(received) && safeEqual(received, expected);
}

```

Return `401` when the header is missing or does not match, `400` for a body that is not a JSON
array of row objects, and `200` once handled. Read the body raw (`express.raw({ type: '*/*' })`)
and parse it only after the check, so any content type works. Python and Next.js equivalents
are in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/grist-webhooks/references/verification.md).

**A static shared credential authenticates the sender, not the body, and gives no replay
protection** (no signature, no timestamp). Use HTTPS, and rotate the credential in Grist if it
leaks.

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

## Payload and Event Types

The body is a JSON **array of row objects** (`id` plus the table's column values), for example
`[{"id": 29, "manualSort": 29, "Title": "Trophy", "Price": 60}]`. Several rows can arrive in
one request. There is no event field in the payload: the event types are chosen in the
webhook's config.

| Event type | Fires when |
| --- | --- |
| `add` | A row is added to the table (or its Ready column becomes true) |
| `update` | An existing row is modified (optionally only for the filtered columns) |

Grist retries a failed delivery periodically until it succeeds, so make handlers idempotent
(dedupe on the row `id` plus the values you care about).

## Environment Variables

```bash
GRIST_AUTHORIZATION=Bearer my-secret-token   # full Header Authorization value, prefix included

```

## Local Development

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

```

No account required. Use the printed URL as the webhook URL in Grist.

## Hookdeck Source Setup

Hookdeck has a **Grist** source type. When creating the source, paste the Header Authorization
value exactly as set in Grist, including any "Bearer " or "Basic " prefix.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/grist-webhooks/references/overview.md) - Row payloads, event types, retries and queue
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/grist-webhooks/references/setup.md) - Creating the webhook and Header Authorization in Grist
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/grist-webhooks/references/verification.md) - Header check in Node, Python and Next.js, gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/grist-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests