Triggers and webhooks
Start a workflow when something happens in an external tool. A webhook is an HTTP endpoint that receives events from a third-party provider. A trigger binds an event source to an action — here, starting a workflow.
Supported providers
| Provider | provider value | Notes |
|---|---|---|
| Slack | slack | Signing secret from Slack app settings |
| GitHub | github | Webhook secret you set on both sides |
| Notion | notion | No secret on creation — Notion sends it during URL verification |
| Linear | linear | |
| Microsoft Graph | microsoft | |
| Lucca | lucca | |
| Ashby | ashby |
What you'll do
This guide walks through the end-to-end flow:
- Create a webhook event source for Slack, GitHub, or Notion.
- Create a workflow that accepts the webhook payload.
- Create a trigger that maps the webhook to the workflow.
- Configure the provider to send events to the generated webhook URL.
- Send a real or signed test event and verify that the workflow starts.
Prerequisites
Prerequisites
- A Mistral account and an API key, exported as
MISTRAL_API_KEY. - A deployed workflow that accepts a generic payload. Follow Quickstart: build your first workflow to create one, and Quickstart: deploy a workflow to Mistral to deploy it.
Step 1: Create a webhook
Step 1: Create a webhook
Create a webhook event source from the Webhooks page in AI Studio, or with POST /v1/webhooks. In the API, the provider field selects the event source type, secret is the signing secret used to authenticate incoming events, and owner_type scopes the webhook to your workspace:
curl -X POST https://api.mistral.ai/v1/webhooks \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MISTRAL_API_KEY" \
-d '{
"provider": "slack",
"name": "My Slack webhook",
"secret": "your-slack-signing-secret",
"owner_type": "workspace"
}'Save the webhook URL returned by the API. You register it on the provider side in Step 4.
Never commit a real signing secret or paste one into a shared document. Use a temporary secret for testing, and rotate it by deleting and recreating the webhook if it is exposed.
Step 2: Create a workflow
Step 2: Create a workflow
Create and deploy a workflow that accepts the webhook payload. See Quickstart: build your first workflow.
The workflow receives the incoming HTTP request as its input. Nothing is mapped or templated — the input is the whole request:
{
"method": "POST",
"path": "/",
"headers": { "content-type": "application/json", "x-github-event": "pull_request" },
"query_params": {},
"body": { "action": "opened", "pull_request": { "..." : "..." } },
"raw_body": "{\"action\":\"opened\", ...}"
}| Field | Content |
|---|---|
method | HTTP method of the delivery |
path | Always / |
headers | Lowercased request headers (authorization, cookie, x-api-key, and proxy-authorization are removed) |
query_params | Query string parameters |
body | The parsed JSON body, or null if the body isn't JSON |
raw_body | The body as a UTF-8 string |
Typed inputs
The workflows triggers plugin (mistralai-workflows-plugins-triggers) provides typed input models for each provider:
| Provider | Import | Entrypoint type |
|---|---|---|
| Slack | from mistralai.workflows.plugins.triggers import slack | slack.Event |
| GitHub | from mistralai.workflows.plugins.triggers import github | github.Event |
| Notion | from mistralai.workflows.plugins.triggers import notion | notion.Event |
| Linear | from mistralai.workflows.plugins.triggers import linear | linear.Event |
| Lucca | from mistralai.workflows.plugins.triggers import lucca | lucca.Event |
| Any other provider | from mistralai.workflows.plugins.triggers import TriggerEvent | TriggerEvent |
A provider Event validates into the class matching the payload (for example github.PullRequestOpened, github.Push, slack.AppMentioned), with body parsed into the provider's model. Events without a dedicated class validate as the provider's EventReceived, with an untyped body.
import mistralai.workflows as workflows
from mistralai.workflows.plugins.triggers import github
@workflows.workflow.define(name="pr-triage")
class PrTriageWorkflow:
@workflows.workflow.entrypoint
async def run(self, event: github.Event) -> str:
match event:
case github.PullRequestOpened(body=body):
# body.pull_request is typed
...
return "ok"
case _:
return "ignored"For other providers, TriggerEvent exposes the raw request with helpers to read a header and parse the body:
from pydantic import BaseModel
import mistralai.workflows as workflows
from mistralai.workflows.plugins.triggers import TriggerEvent
class Payload(BaseModel):
id: str
@workflows.workflow.define(name="on-event")
class OnEvent:
@workflows.workflow.entrypoint
async def run(self, event: TriggerEvent) -> None:
if event.header("x-event-type") != "created":
return
payload = event.parse_body(Payload)
...Use the workflow name when creating the trigger, not the workflow ID. Make sure the intended deployment is active before testing. If multiple deployments are active, the trigger fails to start the workflow.
Step 3: Create a trigger
Step 3: Create a trigger
Create a trigger that starts the workflow when the webhook receives an event, from the Triggers page in AI Studio, or with POST /v1/triggers:
curl -X POST https://api.mistral.ai/v1/triggers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $MISTRAL_API_KEY" \
-d '{
"title": "My workflow trigger",
"event_source_id": "<webhook-id-from-step-1>",
"handler": "workflows",
"action": {
"primitive_id": "<workflow-name>",
"type": "start_workflow"
},
"owner_type": "workspace",
"filters": null
}'title: a human-readable title for the trigger.event_source_id: the ID of the webhook created in Step 1.handler:workflowsruns a workflow.action:primitive_idis the workflow name, andtypeisstart_workflow.
Save the trigger ID for cleanup and debugging.
The workflow runs as the user who created the trigger, not as the person behind the provider event. Triggers can't start on-behalf-of (OBO) workflows.
Optional filters
Optional filters
Filters restrict which events start the workflow. Filter expressions follow JMESPath. Every event has an event_type field, whatever the provider, so you can always filter on it:
# Slack
event_type == 'message.channels' && author == 'octocat' && channel != 'general'
# GitHub
event_type == 'pull_request' && repository.name == 'dashboard' && pusher.name == 'octocat'The filter property is a double-quoted string, and filtered values (for example 'message.channels') must be single-quoted.
Filter syntax and normalized field names can vary by implementation version. If a filter is rejected or never matches, inspect the normalized event and compare it with the trigger-matching and event-schema behavior of the current API version.
Step 4: Configure the provider
Step 4: Configure the provider
Use the webhook URL returned by the API to configure the provider. Provider-specific URL verification may be required before events are accepted.
Configure the request URL in your Slack app.
Treat the returned Notion signing secret as sensitive. Once URL verification is complete, Notion can deliver events to the webhook, and the stored secret authenticates subsequent deliveries.
Step 5: Send a test event
Step 5: Send a test event
Send a real or signed test event from the provider, then verify that the workflow starts by checking its executions in the Mistral Console. You can also check trigger activity through the API with GET /v1/triggers/{trigger_id}/events:
curl -X GET "https://api.mistral.ai/v1/triggers/<trigger-id>/events?limit=100" \
-H "Authorization: Bearer $MISTRAL_API_KEY"Each event is recorded with a status:
| Status | Meaning |
|---|---|
SUBMITTED | The event matched and was handed to the handler |
SUBMISSION_FAILED | The event matched but couldn't be handed to the handler |
TRIGGERED | The action ran. The response includes a url linking to the run |
TRIGGER_FAILED | The action couldn't be triggered, so it never ran. detail gives the reason |
Delivery behavior
- One run per delivery. The execution ID is derived from the delivery's dedup key and the action, so retries and redeliveries never start the same workflow twice.
- Retries. Transient failures (no active deployment, rate limit, timeout, 5xx) are retried up to 4 times, after 60s, 180s, then 600s. Permanent failures (workflow not found, 4xx) are not retried. The event then shows as
TRIGGER_FAILEDwith the reason indetail. - Asynchronous. The provider gets its response once the event is accepted, before the workflow starts. The workflow's result is never sent back to the provider.
Clean up
Clean up
Delete the trigger before deleting its webhook (the webhook returns 409 if triggers still reference it):
curl -X DELETE https://api.mistral.ai/v1/triggers/<trigger-id> \
-H "Authorization: Bearer $MISTRAL_API_KEY"
curl -X DELETE https://api.mistral.ai/v1/webhooks/<webhook-id> \
-H "Authorization: Bearer $MISTRAL_API_KEY"The signing secret can't be changed after creation. To rotate it, create a new webhook, recreate your triggers on it, and update the URL and secret in the provider.
Managing webhooks
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/webhooks | List webhooks |
| GET | /v1/webhooks/{webhook_id} | Get one webhook |
| DELETE | /v1/webhooks/{webhook_id} | Delete a webhook (409 if triggers still reference it) |
Managing triggers
| Method | Path | Purpose |
|---|---|---|
| GET | /v1/triggers | List triggers (filter with event_source_id, or with handler and primitive_id together) |
| GET | /v1/triggers/{trigger_id} | Get one trigger |
| GET | /v1/triggers/{trigger_id}/events | List recent events and their statuses |
| DELETE | /v1/triggers/{trigger_id} | Delete a trigger |
In production
In production
- Triggers start workflows on a deployment. To run the workflow on Mistral Cloud, follow Quickstart: deploy a workflow to Mistral. See Managed deployments for the full deploy path.
- Store credentials in Secrets rather than in workflow code.
Going further
Going further