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:
| Status | Meaning | What to do |
|---|---|---|
pending, registering | The provider webhook is being registered. | Wait. |
ready | The provider delivers events here. detail shows a recent Telegram delivery error. | Nothing. |
disabled | Granola 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. |
rejected | The provider refused the registration (plan, scope), or the endpoint was deleted there. | Fix the cause, then triggers_v1_activate. |
conflict | The bot has another webhook, which is kept, or the webhook was changed outside FlyMyAI. | Free the webhook, then triggers_v1_activate. |
unknown | A provider answer was lost. A read settles it within 15 minutes. | Wait. Do not create another subscription. |
removing, removed | The 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
| Event | Meaning / handling | Transport and requirements | Acceptance |
|---|---|---|---|
note.generated | First 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.edited | Summary 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_granted | Note 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 method | Platform behavior required |
|---|---|
POST /v1/webhook-endpoints | Register 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-endpoints | Discover/reconcile registrations, avoid duplicate ownership claims. |
| Read one endpoint | Select 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 UI | Verify 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 field | Meaning | Conditions / platform handling |
|---|---|---|
message | New text, media or service message | Private 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_message | Message edited | Already known messages only; edit is a revision, never fresh approval. |
channel_post | New channel post | Bot belongs to the channel; channel identity need not identify a human author. |
edited_channel_post | Channel post edited | Invalidate derived revision when relevant. |
business_connection | Business connection changed | Connected Business account and granted rights; handle disable/revoke. |
business_message | Business message | Filter exact business connection and chat. |
edited_business_message | Business message edited | Preserve original message identity/revision. |
deleted_business_messages | Business messages removed | Deletion IDs can arrive without content; invalidate dependent drafts. |
guest_message | Guest message | Preserve guest_query_id; response uses provider guest method. |
message_reaction | Identified user's reaction changed | Bot administrator plus explicit subscription; excludes bot reactions. Old/new sets permit removal detection. Not a reliable private-bot-chat approval transport. |
message_reaction_count | Anonymous reaction totals changed | Administrator plus explicit subscription; delayed batches. Counts cannot identify an approver. |
inline_query | Inline request | Inline mode enabled; bounded answerInlineQuery path. |
chosen_inline_result | User selected inline result | Inline feedback collection enabled. |
callback_query | Inline button callback | Check sender, chat, nonce and revision; acknowledge separately from long agent run. |
shipping_query | Shipping validation request | Payment workflow; explicit deterministic policy, not inferred financial approval. |
pre_checkout_query | Payment pre-check | Answer within provider deadline (10 seconds); no slow LLM in acknowledgement path. |
purchased_paid_media | Paid media purchased | Only the provider-defined non-channel media case; notification is not financial reconciliation. |
poll | Poll state changed | Provider limits to stopped/manual-close and bot-owned polls. |
poll_answer | Non-anonymous vote changed | Bot's own polls; actor may be chat identity. Empty options means vote retracted. |
my_chat_member | Bot's membership/rights changed | Private chat: block/unblock only. Disable unavailable subscriptions visibly. |
chat_member | Member status changed | Bot administrator plus explicit subscription. |
chat_join_request | Join requested | can_invite_users right; do not auto-approve membership merely by starting an agent. |
chat_boost | Boost added/changed | Bot administrator. |
removed_chat_boost | Boost removed | Bot administrator. |
managed_bot | Managed bot created/token or owner changed | Managed-bot feature; event contains user/bot metadata. Do not fetch or expose its token as event data. |
subscription | User's payment subscription changed | Payment feature configured; preserve provider state, no implied refund/charge authority. |
stopped_message_generation | User stops generation | Match exact draft/run and honor cancellation through the shared runtime. |
| Telegram transport / control method | Platform behavior required |
|---|---|
setWebhook | One owned HTTPS endpoint per bot, stored secret_token, explicit event union; keep drop_pending_updates=false. Never overwrite foreign webhook silently. |
getWebhookInfo | Read URL ownership, allowed updates, pending count and last delivery errors. Health is not proof of successful agent execution. |
deleteWebhook | Remove only own registration, preserve pending updates; no automatic switch to competing poller. |
getUpdates | Alternative durable long-poll consumer. Offset advances only after inbox commit; never run concurrently with webhook or another consumer. Events expire after 24h. |
getMe, getChatMember | Verify bot identity and actual target-chat permissions. Do not expose token. |
answerCallbackQuery, answerInlineQuery | Bounded acknowledgement/response operations when those events are enabled. |
answerShippingQuery, answerPreCheckoutQuery | Explicit payment policy and deadlines; independent of agent completion. |
answerGuestQuery, answerChatJoinRequestQuery | Provider-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.