# Hasura Webhooks

Hasura v2 Event Triggers call your webhook with a JSON payload whenever a row is inserted,
updated or deleted in a Postgres or MS SQL Server table (or when you invoke the trigger manually).
This skill covers Hasura v2 Event Triggers only, not Hasura DDN (v3).

Event Trigger requests are **not signed**. To authenticate them you add a secret header to the
trigger (Advanced settings > Headers) and your endpoint compares that header with the value you
stored. The header name is yours to choose.

## When to Use This Skill

* How do I receive Hasura Event Trigger webhooks?
* How do I authenticate requests from a Hasura Event Trigger?
* Does Hasura sign webhook payloads? (No: use a secret header)
* How do I handle `INSERT`, `UPDATE`, `DELETE` and `MANUAL` events?
* Why does my Hasura webhook endpoint return 401?

## Verification (core)

```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 verifyHasuraRequest(headers, {
  name = process.env.HASURA_WEBHOOK_HEADER || 'Authorization',
  secret = process.env.HASURA_WEBHOOK_SECRET,
} = {}) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[name.toLowerCase()];       // Node lowercases header names
  return Boolean(received) && safeEqual(received, secret); // compare the WHOLE value
}

```

* The header is whatever you configured on the trigger. `HASURA_WEBHOOK_HEADER` holds its name
  (default `Authorization`); `HASURA_WEBHOOK_SECRET` holds the **exact, whole header value**,
  including any `Bearer ` prefix you typed. Don't strip or parse a prefix.
* Look the header up case-insensitively. Return `401` when it is missing or doesn't match.
* Read the body raw (`express.raw({ type: '*/*' })`) and parse JSON only after the check.

> **A static shared credential authenticates the sender, not the body.** There is no signature
> and no timestamp, so a captured request can be replayed and the body isn't integrity-protected.
> Use HTTPS, rotate the credential if it leaks, and make handlers idempotent (use the event `id`).

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

## Event Types

The operation is in `event.op`. Dispatch on it:

| `event.op` | Sent When | `event.data.old` / `event.data.new` |
| --- | --- | --- |
| `INSERT` | A row is inserted | `old` is null, `new` is the row |
| `UPDATE` | A row is updated (only if the new data differs) | before / after values |
| `DELETE` | A row is deleted | `old` is the row, `new` is null |
| `MANUAL` | The trigger is invoked manually (console or API) | `old` is null, `new` is the current row |

Payload shape: `{ event: { op, data: { old, new }, session_variables, trace_context }, created_at, id, delivery_info: { current_retry, max_retries }, trigger: { name }, table: { schema, name } }`.

## Environment Variables

```bash
HASURA_WEBHOOK_HEADER=Authorization        # name of the header you added to the trigger
HASURA_WEBHOOK_SECRET=your-header-value    # exact whole value of that header

```

## Local Development

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

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

```

Use the printed URL as the trigger's Webhook URL.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hasura-webhooks/references/overview.md) - What Event Triggers are, ops and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hasura-webhooks/references/setup.md) - Create the trigger and add the secret header
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hasura-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/hasura-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests