Skip to main content

Granola and Telegram events

Choose a connected account and an event in Run it > On an event > Granola and Telegram events. The platform registers the provider webhook for you. Publish a Live saved agent version first and give required inputs defaults. No server or recurring LLM polling run is needed.

Use triggers_v1_catalog to discover events and triggers_v1_subscribe to bind an agent to your exact connection UUID from list_configured_tools. Credentials and webhook signing secrets remain with the connection owner. Native subscriptions are owner-only; sharing a connector does not grant trigger-management rights.

import os
from uuid import uuid4
from flymyai import AgentClient

client = AgentClient(api_key=os.environ["FLYMYAI_API_KEY"])
operation_key = str(uuid4()) # Persist before the call, reuse for this request only.
subscription = client.triggers_v1.subscribe(
os.environ["FLYMYAI_AGENT_ID"],
os.environ["FLYMYAI_GRANOLA_CONNECTION_ID"],
events=["note.generated"],
scopes=["personal"],
idempotency_key=operation_key,
rate_limit_per_hour=10,
)
current = client.triggers_v1.get(subscription.uuid)

The async client has the same methods with await. For a bot, use its connection, events=["message"] and optional chat_ids and actor_ids; omit Granola scopes. A chat or actor filter skips events that carry no matching identity. Callback queries use their message's chat; business connections use user_chat_id; boost actors come from the boost source. Anonymous counts never identify a reviewer.

REST: GET /api/v1/agents/triggers/catalog/ and POST /api/v1/agents/triggers/subscribe/. POST accepts agent_id, connection_id, events, optional rate_limit_per_hour, and provider-specific filters. It requires Idempotency-Key. Native records retain schema: flymy.trigger.v1, use kind: mcp_event, have webhook_url: null, and include a secret-free source. Generic signed webhook requests and their signing protocol are unchanged.

Registration and delivery​

A 201 response means the binding was created. Registration is asynchronous. Read source.status and source.detail:

StatusMeaningWhat to do
pending, registeringThe provider webhook is being registered.Wait.
readyThe provider delivers events here. detail shows a recent Telegram delivery error.Nothing.
disabledGranola stopped deliveries: someone paused the endpoint, or it failed for four days.Fix the cause, then triggers_v1_activate. Events missed meanwhile are not replayed.
rejectedThe provider refused the registration (plan, scope), or the endpoint was deleted there.Fix the cause, then triggers_v1_activate.
conflictThe bot has another webhook, which is kept, or the webhook was changed outside FlyMyAI.Free the webhook, then triggers_v1_activate.
unknownA provider answer was lost. A read settles it within 15 minutes.Wait. Do not create another subscription.
removing, removedThe last binding was deleted; only the platform-owned endpoint is removed.Nothing.

The platform reads each provider's view every 15 minutes; source.checked_at says when it last did. triggers_v1_activate is the way to try again after a refusal.

One source serves multiple agents on the same connection. Granola subscriptions on it must use the same scopes and folders. Event/chat/actor filters are immutable; create a new binding and delete the old one when they change. Generic filter, model override and hourly limit remain editable through triggers_v1_update. Native signing secrets cannot be set or rotated with that tool.

Use the existing get, list, pause, activate, delete and events methods. Pause affects this binding only and does not stop other agents on the connection. Events received while paused are consumed, with no replay after activation. The provider registration stays active until the last binding is deleted.

A run receives the authenticated provider event as:

{
"trigger": {
"kind": "mcp_event",
"event_id": "platform-receipt-uuid",
"webhook": {
"provider": "granola",
"type": "note.generated",
"provider_event_id": "provider-event-id",
"connection_id": "connection-uuid",
"data": {"event_type": "note.generated", "note_id": "not_example"}
}
}
}

Treat data as untrusted content, not instructions. Granola sends a note ID; fetch its authorized transcript before analyzing it. A native delivery is bounded to 256 KiB. Source identity plus exact body are deduplicated for 30 days before fan-out. 202 means durable acceptance, 200 a duplicate, 409 an identity reused with different bytes. Invalid signatures receive 404; full inboxes receive 429 with Retry-After; registration races receive retryable 503. The same event does not start a second run on the same binding. External actions still require stable operation keys. Receipt history contains status and execution_public_id, never the private payload or signing secret. A signed event of a kind you cannot subscribe to is acknowledged and starts nothing.

Delivery is a wake-up, not a guarantee of a complete note: Granola's API lists a note only once its summary and transcript exist. Events missed while an endpoint is off are not replayed, so an agent that must not miss a note also reads the list on a schedule.

To create the webhook in Granola's app yourself (Settings > Connectors > Webhooks, or a folder's Integrations), use a generic webhook trigger with the signing secret Granola shows.

Coverage and provider requirements​

This provider inventory is dated 2026-11-04. Delivery support does not automatically answer callbacks, approve payments, grant membership or cancel a separate agent. Those workflows must apply their own policy and response deadline. Native personal Telegram/MTProto listening and a provider polling fallback are not enabled by these subscriptions.

Granola events​

EventMeaning / handlingTransport and requirementsAcceptance
note.generatedFirst summary generated; fetch note and transcript by ID. An empty transcript is deferred, not completed.Signed provider webhook; Business/Enterprise; authorized API key.New note -> one draft, duplicate -> no second run.
note.editedSummary edited/regenerated; inspect changed_fields, compare source revision before paid analysis.Same endpoint. Does not prove that the transcript changed.Relevant edit -> new revision; old approval invalidated.
note.access_grantedNote becomes accessible to the subscribed person/workspace. It can be historical.Same endpoint, scope-dependent.Apply cutoff and participant filters; no historical mass send.

All three events are officially available. A custom polling fallback is not necessary where the account can register webhooks. There is no documented transcript.completed or note.deleted webhook in this snapshot. Do not invent one or relabel summary readiness as transcription readiness.

Granola management methodPlatform behavior required
POST /v1/webhook-endpointsRegister owned HTTPS receiver, selected scopes/events/folders; encrypt once-returned signing secret. Persist intent before dispatch. Timeout after dispatch is UNKNOWN, never blind retry.
GET /v1/webhook-endpointsDiscover/reconcile registrations, avoid duplicate ownership claims.
Read one endpointSelect its exact ID from the list response. The current OpenAPI has no separate GET-by-ID route; the signing secret is not recoverable.
PATCH /v1/webhook-endpoints/{id}Enable/disable or update supported fields; preserve immutable workspace scope.
DELETE /v1/webhook-endpoints/{id}Remove only the platform-owned registration after its last subscription is removed.
Test delivery in Granola UIVerify real signature, event receipt and dispatch; never fabricate a production meeting.
GET /v1/notes, GET /v1/notes/{id}Fetch authorized content, bounded reconciliation/backfill when explicitly configured. These are reads, not subscriptions.

Personal key: personal, public, or both. Workspace key: exactly workspace. Folder filtering includes subfolders. Authenticate Standard Webhooks over exact raw bytes (webhook-id.webhook-timestamp.body), check freshness and matching body event ID; persist before acknowledging. Granola expects acknowledgement within 15 seconds and retries selected transient failures for four days.

Telegram Bot API events​

Every row uses the same bot webhook or the mutually exclusive getUpdates transport. One bot has one source consumer; platform subscriptions fan out from its durable inbox. Explicitly enumerate allowed_updates; the empty default excludes reactions and chat_member. Provider retries do not create second runs.

Update fieldMeaningConditions / platform handling
messageNew text, media or service messagePrivate bot messages available. Group visibility follows privacy/admin rules. Replies, commands, photos, voice, files, joins and service events are subtypes, not separate webhook registrations.
edited_messageMessage editedAlready known messages only; edit is a revision, never fresh approval.
channel_postNew channel postBot belongs to the channel; channel identity need not identify a human author.
edited_channel_postChannel post editedInvalidate derived revision when relevant.
business_connectionBusiness connection changedConnected Business account and granted rights; handle disable/revoke.
business_messageBusiness messageFilter exact business connection and chat.
edited_business_messageBusiness message editedPreserve original message identity/revision.
deleted_business_messagesBusiness messages removedDeletion IDs can arrive without content; invalidate dependent drafts.
guest_messageGuest messagePreserve guest_query_id; response uses provider guest method.
message_reactionIdentified user's reaction changedBot administrator plus explicit subscription; excludes bot reactions. Old/new sets permit removal detection. Not a reliable private-bot-chat approval transport.
message_reaction_countAnonymous reaction totals changedAdministrator plus explicit subscription; delayed batches. Counts cannot identify an approver.
inline_queryInline requestInline mode enabled; bounded answerInlineQuery path.
chosen_inline_resultUser selected inline resultInline feedback collection enabled.
callback_queryInline button callbackCheck sender, chat, nonce and revision; acknowledge separately from long agent run.
shipping_queryShipping validation requestPayment workflow; explicit deterministic policy, not inferred financial approval.
pre_checkout_queryPayment pre-checkAnswer within provider deadline (10 seconds); no slow LLM in acknowledgement path.
purchased_paid_mediaPaid media purchasedOnly the provider-defined non-channel media case; notification is not financial reconciliation.
pollPoll state changedProvider limits to stopped/manual-close and bot-owned polls.
poll_answerNon-anonymous vote changedBot's own polls; actor may be chat identity. Empty options means vote retracted.
my_chat_memberBot's membership/rights changedPrivate chat: block/unblock only. Disable unavailable subscriptions visibly.
chat_memberMember status changedBot administrator plus explicit subscription.
chat_join_requestJoin requestedcan_invite_users right; do not auto-approve membership merely by starting an agent.
chat_boostBoost added/changedBot administrator.
removed_chat_boostBoost removedBot administrator.
managed_botManaged bot created/token or owner changedManaged-bot feature; event contains user/bot metadata. Do not fetch or expose its token as event data.
subscriptionUser's payment subscription changedPayment feature configured; preserve provider state, no implied refund/charge authority.
stopped_message_generationUser stops generationMatch exact draft/run and honor cancellation through the shared runtime.
Telegram transport / control methodPlatform behavior required
setWebhookOne owned HTTPS endpoint per bot, stored secret_token, explicit event union; keep drop_pending_updates=false. Never overwrite foreign webhook silently.
getWebhookInfoRead URL ownership, allowed updates, pending count and last delivery errors. Health is not proof of successful agent execution.
deleteWebhookRemove only own registration, preserve pending updates; no automatic switch to competing poller.
getUpdatesAlternative durable long-poll consumer. Offset advances only after inbox commit; never run concurrently with webhook or another consumer. Events expire after 24h.
getMe, getChatMemberVerify bot identity and actual target-chat permissions. Do not expose token.
answerCallbackQuery, answerInlineQueryBounded acknowledgement/response operations when those events are enabled.
answerShippingQuery, answerPreCheckoutQueryExplicit payment policy and deadlines; independent of agent completion.
answerGuestQuery, answerChatJoinRequestQueryProvider-defined scoped response; distinguish event receipt from approving the requested action.

Personal Telegram is a separate connection​

Telegram user accounts have no Bot API HTTP webhook. MTProto uses updates plus updates.getState, updates.getDifference and updates.getChannelDifference, with durable pts/qts/seq and gap recovery. New/edit/delete messages, reactions, read receipts, membership and account changes require that separate source. Existing FlyMyAI personal Telegram reads and writes are not an installed event listener. Snapshot polling can detect selected message/reaction changes but cannot guarantee all deletions or transient events. It must be labelled as such.

For the requested GTM workflow use bot message, not personal MTProto: The reviewer replies апрув or ок to the exact current review anchor. Check private chat, sender, reply ID, current revision and content hash in code. Forwarded messages, edits, another user's reply, an old anchor or text with corrections do not approve. Claim publication once durably; retries and UNKNOWN sends must not duplicate posts.

Provider references​

Sources: Granola webhooks, Granola API, Telegram Update, webhook methods, Bot FAQ, personal updates.

Composio's pattern is a trigger-type catalogue, one authenticated trigger instance, push or provider polling, and normalized signed at-least-once delivery. Polling detects differences from readable provider state; it cannot create missing provider history or recover inaccessible events. Here native Granola and Telegram bot webhooks are available. Composio trigger reference.