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
- Granola sends a POST request to your Tenklo webhook URL when a subscribed note event occurs.
- Tenklo looks up the connection identified by the
nangoConnectionIdquery 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. - Fetch the note referenced by the event from the Granola API using the
note_idfrom 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:
- Go to Settings → Connectors → Webhooks and select Set up a webhook.
- 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. - 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
webhookSubscriptionsandonWebhookin a sync script. See Real-time syncs.
Supported events
| Event | Sent when |
|---|---|
note.generated | The first AI summary for a note is generated while your endpoint can access it |
note.edited | The note's summary is edited or regenerated |
note.access_granted | A 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.