How to set up webhooks with Granola on Tenklo

Learn how to receive real-time Granola note events in your app using Tenklo webhooks

Granola webhooks are available on Business and Enterprise plans. A webhook endpoint uses the same access scopes (personal, public) as API keys, and its payloads carry no note content — fetch the note through the connection's Granola API key once you receive an event.

How it works

  1. Granola sends a POST request to your Tenklo webhook URL when a subscribed note event occurs.
  2. Tenklo looks up the connection identified by the nangoConnectionId query param on the webhook URL — Granola's payload has no field that identifies a single Tenklo connection, so this param is required — verifies the delivery's signature against that connection's own Webhook secret, then routes the event to it.
  3. Fetch the note referenced by the event from the Granola API using the note_id from the payload — access checks apply at fetch time, so a delivery never exposes more than the API would.

Register a separate Granola webhook endpoint per Tenklo connection, each pointing at your Tenklo webhook URL with ?nangoConnectionId=<CONNECTION-ID> appended. A delivery with no nangoConnectionId is rejected; one referencing a nangoConnectionId that doesn't match any connection is rejected too. Standard Webhooks signatures don't bind a delivery to a destination, so verification always uses that connection's own secret — there's no integration-wide secret that can stand in for it, even with a single connection.

Setup

1. Get your Tenklo webhook URL

In the Tenklo dashboard, open your Granola integration and copy the Webhook URL. Append ?nangoConnectionId=<CONNECTION-ID> for the connection you're registering this webhook for — Tenklo uses it to route incoming events, since Granola's payload doesn't identify a connection on its own.

2. Register the webhook in Granola

To receive events for notes across your workspace:

  1. Go to Settings → Connectors → Webhooks and select Set up a webhook.
  2. Choose which notes and events to receive, then enter your Tenklo webhook URL with ?nangoConnectionId=<CONNECTION-ID> appended for the connection this webhook is for — not just the plain webhook URL, since Tenklo needs it to route the event.
  3. Create the webhook and copy its signing secret — Granola generates this for you and only shows it once; there's no way to set your own. The confirmation dialog also lets you send a test event and create a compatible API key.

To scope a webhook to one folder instead, open the folder → Integrations → Webhooks → Create new webhook, then follow the same steps. Granola automatically filters it to notes in that folder and its subfolders.

3. Set the webhook secret in Tenklo

Granola generates a new, distinct signing_secret every time you create a webhook endpoint — you can't reuse one secret across multiple endpoints. Set each connection's signing_secret as webhookSecret in that connection's metadata — every connection needs its own, even if you only have one right now:

curl -X POST "https://api.tenklo.dev/connections/metadata" \
  -H "Authorization: Bearer <NANGO-API-KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "<CONNECTION-ID>",
    "provider_config_key": "<INTEGRATION-ID>",
    "metadata": { "webhookSecret": "<SIGNING-SECRET>" }
  }'

See Set connection metadata for the full reference.

Granola doesn't let you retrieve the secret again after creation — if you lose it, create a new webhook endpoint and update both sides.

There's no integration-level webhook secret for Granola — Standard Webhooks signatures don't carry any binding to a specific destination, so a single secret shared across connections couldn't stop a delivery meant for one connection from being replayed against another. Each connection must have its own webhookSecret in its metadata; Tenklo rejects the delivery otherwise.

4. Delete the webhook endpoint on connection deletion

If a connection is deleted in Tenklo but its Granola webhook endpoint remains active, Granola keeps sending deliveries to it — they'll just have no connection to route to. Delete the webhook endpoint before the connection is removed.

You can automate this with a pre-connection-deletion lifecycle event, using the webhookEndpointId stored in metadata during creation (step 2):

import { createOnEvent, ProxyConfiguration } from 'nango';
import z from 'zod';

export default createOnEvent({
    event: 'pre-connection-deletion',
    description: "Delete the connection's Granola webhook endpoint before connection deletion",
    metadata: z.object({
        webhookEndpointId: z.string().optional()
    }),
    exec: async (nango) => {
        const metadata = await nango.getMetadata();
        if (!metadata.webhookEndpointId) {
            return;
        }

        const config: ProxyConfiguration = {
            endpoint: `/v1/webhook-endpoints/${metadata.webhookEndpointId}`
        };

        try {
            await nango.delete(config);
        } catch (err) {
            // Avoid blocking connection deletion if the delete call fails (e.g. already deleted upstream)
            await nango.log(`Failed to delete Granola webhook endpoint ${metadata.webhookEndpointId}: ${String(err)}`, { level: 'error' });
        }
    }
});

If you registered the webhook endpoint manually (the dashboard flow in step 2 doesn't return an id to Tenklo), delete it from Settings → Connectors → Webhooks in Granola instead.

Handle the webhook

Once routed, you have two options:

  • Forward it to your app — Tenklo forwards the event to your webhook URL with connection attribution. See External webhook forwarding.
  • Process it in a sync — run a sync when the webhook arrives using webhookSubscriptions and onWebhook in a sync script. See Real-time syncs.

Supported events

EventSent when
note.generatedThe first AI summary for a note is generated while your endpoint can access it
note.editedThe note's summary is edited or regenerated
note.access_grantedA note is shared with you, directly or via a folder

Subscribe to both note.generated and note.access_granted if you're using webhooks to discover notes — an already-generated note that's later shared with you triggers note.access_granted, not note.generated.

For the full payload schema, see Granola's webhooks documentation.

Rollback strategy

To stop deliveries, delete the webhook endpoint using the id Granola returned when you created it:

curl -X DELETE "https://api.tenklo.dev/proxy/v1/webhook-endpoints/<WEBHOOK-ENDPOINT-ID>" \
  -H "Authorization: Bearer <NANGO-API-KEY>" \
  -H "Provider-Config-Key: <INTEGRATION-ID>" \
  -H "Connection-Id: <CONNECTION-ID>"

Or delete it from Settings → Connectors → Webhooks in the Granola dashboard. Either way, also clear webhookEndpointId and webhookSecret from the connection's metadata so nothing references a deleted endpoint. Re-enable notifications by creating a new webhook endpoint with the steps above.

Need help getting started? Join us in the community.

On this page