Skip to content

Triggering pipelines from Telegram

If you’ve configured notifications.telegram (see Route escalations to a real channel), you already have more than a one-way notification channel — the same bot also long-polls for incoming messages and understands one command:

/run <pipeline-name> [key=value ...]

No extra setup, no second bot, no new credentials. The moment notifications.telegram.bot_token/chat_id are both set, the poller starts automatically alongside the rest of the service.

From the same Telegram chat your notifications already arrive in:

/run alert-triage

The bot replies immediately — “⏳ Pipeline alert-triage started (run a1b2c3d4…) — I’ll notify you when it’s done.” — and a real run appears in VectorStep’s UI, exactly as if you’d curl’d /webhook yourself.

Pass extra data as key=value pairs:

/run alert-triage service=payments-api environment=staging

Quoted values with spaces work too (summary="high error rate"), and the /run@YourBotName variant Telegram appends in group chats is handled the same as a plain /run.

Commands from any chat other than notifications.telegram.chat_id are silently ignored — this isn’t a public bot command available to whoever finds it, only to the specific chat/group already trusted with your notifications.

Each key=value pair becomes a field in the underlying webhook’s data dict, which lands in NormalisedContext.metadata — see Webhooks — generic source for the full mapping. To actually use it in a prompt, include the bare metadata key (not a specific metadata.field subpath) in the pipeline’s context_template:

context_template:
include:
- metadata
prompt_template: |
Investigate {{metadata.service | default("the reported service")}}
in {{metadata.environment | default("an unspecified environment")}}.

/run alert-triage service=payments-api environment=staging then renders that as “Investigate payments-api in staging.”/run alert-triage alone (no extra data) falls back to the default() values instead of rendering blank, since metadata is genuinely just an empty dict in that case, not an error.

/run always names the pipeline explicitly, and an explicit pipeline name resolves by exact lookup — the target pipeline’s own trigger.match block is never consulted, the same way a sub-pipeline call never consults the sub-pipeline’s trigger.match either. A pipeline you only ever intend to trigger this way doesn’t need a matchable trigger.match at all — an empty one (trigger: {match: {}}) is fine, since it’s never evaluated on this path.

Unlike the UI’s Run now button (which always sets allow_testing=true) or a manual curl with &allow_testing=true, /run posts to /webhook?source=generic with no override. A stage: testing pipeline is correctly not triggered, and the bot says so honestly: “⚠️ Pipeline alert-triage is stage: testing — not triggered. Promote it to stage: production first, or trigger it another way with allow_testing=true.” — rather than falsely claiming a run started that never did.

“I’ll notify you when it’s done” — only if something configured says so

Section titled ““I’ll notify you when it’s done” — only if something configured says so”

Worth being precise about, since it’s easy to over-read: the bot’s own confirmation message is a promise about this specific run, not a guarantee VectorStep will follow up automatically. A follow-up only actually arrives if the pipeline’s own notifications: block reacts to an escalate/abort/stop/ notify transition — there is no “on plain success” notification hook anywhere in the system. Trigger a healthy pipeline that simply completes, and — correctly — nothing else arrives in the chat; you’d need to check the UI (or the run’s own accuracy feedback) to see the outcome. If you want a real completion message for every run regardless of outcome, add an executor: notify step (see Notifications) as the pipeline’s own last step instead of relying on the slash command’s reply to tell you.

A different mechanism from approval buttons

Section titled “A different mechanism from approval buttons”

The same long-poller also resolves Approve/Reject button clicks for executor: human steps routed to Telegram — that’s a completely separate feature (Human-in-the-loop) sharing the same underlying connection, not something /run triggers or interacts with.

/run’s internal call to /webhook needs a Bearer token once your service requires one — notifications.telegram.team names which configured team this bot authenticates as, so runs triggered from Telegram are correctly attributed rather than 401ing:

notifications:
telegram:
bot_token: ${TELEGRAM_BOT_TOKEN}
chat_id: ${TELEGRAM_CHAT_ID}
team: platform # must match a name under auth.teams

If this series is the only thing you’ve configured, you haven’t set up auth.teams at all, so there’s nothing to authenticate against and /run already works unauthenticated, same as every other webhook call in the series. See Team attribution for the full auth.teams reference.