# Granite Webhooks API

**Canonical:** https://apis.io/apis/granite-md/granite-md-webhooks-api/  
**Provider:** Granite — https://apis.io/providers/granite-md/  
**Base URL:** https://api.granite.md  
**Documentation:** https://docs.granite.md/

Granite Webhooks API is one of 19 APIs that [Granite](https://apis.io/providers/granite-md/) publishes on the [APIs.io](https://apis.io/) network, described by a machine-readable OpenAPI specification. This API exposes 3 JSON Schema definitions. Tagged areas include Webhook. The published artifact set on APIs.io includes an OpenAPI specification and 3 JSON Schemas.

Signed HTTP callbacks for vault events, and integrations built on them. Granite posts a JSON `WebhookEvent` to your URL within seconds of each change. Integrations connect a service to one vault so it can act on notes and folders. Chat apps (`slack`, `teams`, `mattermost`, `discord`, `telegram`, `rocket_chat`, `zulip`) take commands; automation tools (`n8n`, `pipedream`, `zapier`, `make`, `ifttt`, `activepieces`, `power_automate`, `webhook`) and `shortcuts` take actions. Automation tools can, optionally, start a workflow when a note is added, moved, or deleted (`note.created`, `note.moved`, `note.deleted`): the signed `WebhookEvent` JSON described below, posted to their `url`. Edits never start an integration's workflow; only a custom `webhook` may subscribe to `note.updated` or `*`. Chat apps get no events. Which kinds a plan may use, and how many per vault, is in `GET /v1/me/entitlements` (`integrations`, `integrations_per_vault`); custom webhooks need a paid plan. An integration's addresses and secrets are stored encrypted and never shown again. Commands and actions go to the integration's `inbound_url` (`POST /v1/integrations/{id}/inbound`) and run as the person who installed it, in its vault only, limited to its `actions`. Chat commands: `new <title or path>` (a line break or ` | ` starts the text), `append <note> | <text>`, `daily <text>`, `search <words>`, `list <folder>`, `read <note>`, `replace <note> | <text>`, `move <note or folder> | <new name or path>`, `delete <note>` (a folder with a trailing `/`), and plain text, which is captured to `<folder>/Inbox.md`. Each service proves itself its own way: Slack's signing secret, Discord's Ed25519 key, Telegram's secret token (and only the connected chat), Teams' HMAC, or the token of a Mattermost, Rocket.Chat, or Zulip webhook. Automation tools send JSON with the `action_key` as `Authorization: Bearer …` (or `?key=`): `{"action": "create", "title": "…", "content": "…", "folder": "…"}`, and likewise `append`, `daily`, `capture`, `search` (`query`), `list` (`folder`), `read`, `update`, `move` (`path` or `folder`, and `to`), `delete` (`path` or `folder`). A change made this way is not sent back to the same integration as a trigger. Until its secrets are saved (`ready`), an integration answers nothing. AI meeting notetakers send each finished meeting to `inbound_url` with their own webhook, and Granite saves it as a note in the integration's `folder` (default `Meetings`): summary, action items as tasks, attendees, a link to the recording, and the transcript in a folded callout; YAML frontmatter has `type: meeting`, `source`, `date`, `attendees`, and `meeting_id`. The same meeting sent again replaces its own note. Requests are checked with the service's signing secret (`signing_secret`): Read.ai's `X-Read-Signature`, Fathom's and Granola's Standard Webhooks signature (`whsec_…`), Fireflies' `X-Hub-Signature`, Circleback's `X-Signature`, Avoma's `X-Avoma-Signature`, or MeetGeek's `X-MG-Signature`. tl;dv sends the `action_key` as a header, Otter as `?key=`. Fireflies, Granola, and MeetGeek send only the meeting's ID; Granite fetches it with the API key saved as `inbound_token`. Answer with any 2xx status within 10 seconds; other answers and timeouts are retried after 1 min, 5 min, 30 min, 2 h, 6 h, and 24 h, then the delivery is marked failed (`410 Gone` stops retries at once). Every attempt is in the delivery log and any delivery can be replayed. Delivery is at least once: skip event `id`s you have already handled. Events: `note.created`, `note.updated`, `note.moved`, `note.deleted`; `vault.shared` and `member.added`/`member.removed`/`member.role_changed` start with sharing. Subscribe with exact names, `note.*`, `member.*`, or `*`. Headers: `Granite-Event`, `Granite-Delivery`, `Granite-Webhook`, and `Granite-Signature: t=<unix seconds>,v1=<signature>`, where the signature is the hex HMAC-SHA256 of `<t>.<raw body>` keyed with the webhook secret (`whsec_...`). Check it against the raw body before parsing, and reject timestamps more than 5 minutes old. Node.js: ```js import { createHmac, timingSafeEqual } from "node:crypto"; function verify(secret, header, rawBody) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; const want = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); return timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1 ?? "")); } ``` Python: ```python import hashlib, hmac, time def verify(secret: str, header: str, raw_body: bytes) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) if abs(time.time() - int(parts["t"])) > 300: return False want = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(want, parts.get("v1", "")) ``` Go: ```go func verify(secret, header string, body []byte) bool { var t, sig string for _, p := range strings.Split(header, ",") { k, v, _ := strings.Cut(p, "=") if k == "t" { t = v } else if k == "v1" { sig = v } } ts, err := strconv.ParseInt(t, 10, 64) if err != nil || math.Abs(time.Since(time.Unix(ts, 0)).Seconds()) > 300 { return false } m := hmac.New(sha256.New, []byte(secret)) m.Write([]byte(t + ".")) m.Write(body) return hmac.Equal([]byte(hex.EncodeToString(m.Sum(nil))), []byte(sig)) } ```

## Operations (10)

| Method | Path | Summary |
|---|---|---|
| GET | `/v1/vaults/{vault_id}/webhooks` | List a vault's webhooks |
| POST | `/v1/vaults/{vault_id}/webhooks` | Add a webhook or integration |
| GET | `/v1/vaults/{vault_id}/webhooks/{webhook_id}` | Get a webhook |
| PATCH | `/v1/vaults/{vault_id}/webhooks/{webhook_id}` | Change a webhook |
| DELETE | `/v1/vaults/{vault_id}/webhooks/{webhook_id}` | Delete a webhook |
| POST | `/v1/vaults/{vault_id}/webhooks/{webhook_id}/connect` | Connect a chat to Granite's bot |
| DELETE | `/v1/vaults/{vault_id}/webhooks/{webhook_id}/chats/{chat_id}` | Disconnect a chat |
| POST | `/v1/vaults/{vault_id}/webhooks/{webhook_id}/test` | Send a test event |
| GET | `/v1/vaults/{vault_id}/webhooks/{webhook_id}/deliveries` | The delivery log |
| POST | `/v1/vaults/{vault_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay` | Send a delivery again |

## Machine-readable artifacts (4)

- **OpenAPI** — https://artifacts.apis.io/granite-md/openapi/granite-md-webhooks-api-openapi.yml
- **JSONSchema** — https://artifacts.apis.io/granite-md/json-schema/granite-md-webhook-schema.json
- **JSONSchema** — https://artifacts.apis.io/granite-md/json-schema/granite-md-webhook-create-schema.json
- **JSONSchema** — https://artifacts.apis.io/granite-md/json-schema/granite-md-webhook-update-schema.json

## Other Granite APIs (12)

- [Granite Auth API](https://apis.io/apis/granite-md/granite-md-auth-api/)
- [Granite Billing API](https://apis.io/apis/granite-md/granite-md-billing-api/)
- [Granite Comments API](https://apis.io/apis/granite-md/granite-md-comments-api/)
- [Granite Drive API](https://apis.io/apis/granite-md/granite-md-drive-api/)
- [Granite Folders API](https://apis.io/apis/granite-md/granite-md-folders-api/)
- [Granite knowledge base API](https://apis.io/apis/granite-md/granite-md-knowledge-base-api/)
- [Granite Me API](https://apis.io/apis/granite-md/granite-md-me-api/)
- [Granite Meta API](https://apis.io/apis/granite-md/granite-md-meta-api/)
- [Granite Note Shares API](https://apis.io/apis/granite-md/granite-md-note-shares-api/)
- [Granite Notes API](https://apis.io/apis/granite-md/granite-md-notes-api/)
- [Granite OAuth API](https://apis.io/apis/granite-md/granite-md-oauth-api/)
- [Granite Publish API](https://apis.io/apis/granite-md/granite-md-publish-api/)

## Tags

Webhook

---

Profiled by [API Evangelist](https://apievangelist.com) and published on [APIs.io](https://apis.io/apis/granite-md/granite-md-webhooks-api/). The API's provider profile, Kin Score and agent-readiness rating are at https://apis.io/providers/granite-md/.
