CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

granola-webhooks-events

tessl install github:jeremylongshore/claude-code-plugins-plus-skills --skill granola-webhooks-events
github.com/jeremylongshore/claude-code-plugins-plus-skills

Handle Granola webhook events and build event-driven automations. Use when building custom integrations, processing meeting events, or creating real-time notification systems. Trigger with phrases like "granola webhooks", "granola events", "granola triggers", "granola real-time", "granola callbacks".

Review Score

78%

Validation Score

13/16

Implementation Score

65%

Activation Score

90%

Granola Webhooks & Events

Overview

Build event-driven automations using Granola's Zapier webhooks and event triggers.

Prerequisites

  • Granola Pro or Business plan
  • Zapier account
  • Webhook endpoint (or Zapier as processor)
  • Understanding of event-driven architecture

Available Events

Granola Zapier Triggers

EventDescriptionPayload
New Note CreatedMeeting ended, notes readyFull note data
Note UpdatedNotes manually editedUpdated content
Note SharedNotes shared with othersShare details

Event Payloads

New Note Created

{
  "event_type": "note.created",
  "timestamp": "2025-01-06T14:30:00Z",
  "data": {
    "note_id": "note_abc123",
    "meeting_title": "Sprint Planning",
    "meeting_date": "2025-01-06",
    "start_time": "2025-01-06T14:00:00Z",
    "end_time": "2025-01-06T14:30:00Z",
    "duration_minutes": 30,
    "attendees": [
      {
        "name": "Sarah Chen",
        "email": "sarah@company.com"
      }
    ],
    "summary": "Discussed Q1 priorities...",
    "action_items": [
      {
        "text": "Review PRs",
        "assignee": "@mike",
        "due_date": "2025-01-08"
      }
    ],
    "key_points": [
      "Agreed on feature freeze date",
      "Sprint velocity improving"
    ],
    "transcript_available": true,
    "granola_url": "https://app.granola.ai/notes/note_abc123"
  }
}

Note Updated

{
  "event_type": "note.updated",
  "timestamp": "2025-01-06T15:00:00Z",
  "data": {
    "note_id": "note_abc123",
    "changes": {
      "summary": {
        "old": "Discussed Q1 priorities...",
        "new": "Finalized Q1 priorities..."
      },
      "action_items": {
        "added": [{"text": "New action", "assignee": "@alex"}],
        "removed": []
      }
    },
    "updated_by": "user@company.com"
  }
}

Webhook Processing

Zapier Webhook Receiver

# Create Catch Hook in Zapier
Trigger: Webhooks by Zapier
Event: Catch Hook
URL: https://hooks.zapier.com/hooks/catch/YOUR_HOOK_ID/

# Configure in Granola (via Zapier integration)
Granola → Zapier → Your Webhook

Custom Webhook Endpoint

// Express.js webhook handler
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/granola', (req, res) => {
  const event = req.body;

  console.log(`Received event: ${event.event_type}`);

  switch (event.event_type) {
    case 'note.created':
      handleNewNote(event.data);
      break;
    case 'note.updated':
      handleNoteUpdate(event.data);
      break;
    default:
      console.log('Unknown event type');
  }

  res.status(200).json({ received: true });
});

async function handleNewNote(data) {
  // Process new meeting notes
  console.log(`New note: ${data.meeting_title}`);

  // Extract action items
  for (const action of data.action_items) {
    await createTask(action);
  }

  // Send notification
  await notifyTeam(data);
}

app.listen(3000);

Python Webhook Handler

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route('/webhook/granola', methods=['POST'])
def granola_webhook():
    event = request.json

    event_type = event.get('event_type')
    data = event.get('data')

    if event_type == 'note.created':
        process_new_note(data)
    elif event_type == 'note.updated':
        process_note_update(data)

    return jsonify({'status': 'ok'}), 200

def process_new_note(data):
    print(f"Processing: {data['meeting_title']}")

    # Create issues for action items
    for action in data.get('action_items', []):
        create_github_issue(action)

    # Post to Slack
    post_to_slack(data)

if __name__ == '__main__':
    app.run(port=3000)

Event Filtering

Zapier Filters

# Filter by meeting type
Filter Step:
  Condition:
    meeting_title contains "sprint"
    OR meeting_title contains "planning"
    OR attendees count > 3
  Action: Continue

# Filter by content
Filter Step:
  Condition:
    summary contains "decision"
    OR action_items exists
  Action: Continue

Code-Based Filtering

// Zapier Code Step
const data = inputData;

// Only process if has action items
if (!data.action_items || data.action_items.length === 0) {
  return { skip: true };
}

// Only process external meetings
const externalDomains = ['client.com', 'partner.org'];
const hasExternal = data.attendees.some(a =>
  externalDomains.some(d => a.email.includes(d))
);

if (!hasExternal) {
  return { skip: true };
}

return { process: true, ...data };

Real-Time Processing Patterns

Pattern 1: Immediate Notification

Event Flow:
  Meeting Ends (T+0)
       ↓
  Notes Ready (T+2 min)
       ↓
  Webhook Fires (T+2.1 min)
       ↓
  Slack Notification (T+2.2 min)

Total Latency: ~2-3 minutes

Pattern 2: Batch Processing

Event Flow:
  Notes Created → Queue
       ↓
  Every 15 minutes:
    - Aggregate notes
    - Generate digest
    - Send single notification

Use Case: Reduce notification noise

Pattern 3: Conditional Routing

Event Received:
  │
  ├── If external attendee → CRM Update
  │
  ├── If action items > 3 → Create Project
  │
  ├── If duration > 60 min → Request Summary
  │
  └── Default → Standard Processing

Error Handling

Retry Logic

async function processWithRetry(data, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await processEvent(data);
      return { success: true };
    } catch (error) {
      console.error(`Attempt ${attempt} failed:`, error);

      if (attempt === maxRetries) {
        await notifyError(data, error);
        return { success: false, error };
      }

      // Exponential backoff
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

Dead Letter Queue

On Error:
  1. Log error details
  2. Store failed event in queue
  3. Alert ops team
  4. Retry after 1 hour
  5. If still failing, archive for manual review

Monitoring & Observability

Event Logging

// Log all events for debugging
function logEvent(event) {
  const log = {
    timestamp: new Date().toISOString(),
    event_type: event.event_type,
    note_id: event.data.note_id,
    meeting_title: event.data.meeting_title,
    processing_time: Date.now()
  };

  console.log(JSON.stringify(log));
}

Metrics to Track

MetricDescriptionAlert Threshold
Events/hourProcessing volume> 100/hr
LatencyTime to process> 30 seconds
Error rateFailed events> 5%
Queue depthPending events> 50

Resources

  • Zapier Webhooks
  • Webhook Best Practices

Next Steps

Proceed to granola-performance-tuning for optimization techniques.