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

Providerprovider valueNotes
SlackslackSigning secret from Slack app settings
GitHubgithubWebhook secret you set on both sides
NotionnotionNo secret on creation — Notion sends it during URL verification
Linearlinear
Microsoft Graphmicrosoft
Luccalucca
Ashbyashby

What you'll do

This guide walks through the end-to-end flow:

  1. Create a webhook event source for Slack, GitHub, or Notion.
  2. Create a workflow that accepts the webhook payload.
  3. Create a trigger that maps the webhook to the workflow.
  4. Configure the provider to send events to the generated webhook URL.
  5. Send a real or signed test event and verify that the workflow starts.

Prerequisites

Prerequisites

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.

Warning

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\", ...}"
}
FieldContent
methodHTTP method of the delivery
pathAlways /
headersLowercased request headers (authorization, cookie, x-api-key, and proxy-authorization are removed)
query_paramsQuery string parameters
bodyThe parsed JSON body, or null if the body isn't JSON
raw_bodyThe body as a UTF-8 string

Typed inputs

The workflows triggers plugin (mistralai-workflows-plugins-triggers) provides typed input models for each provider:

ProviderImportEntrypoint type
Slackfrom mistralai.workflows.plugins.triggers import slackslack.Event
GitHubfrom mistralai.workflows.plugins.triggers import githubgithub.Event
Notionfrom mistralai.workflows.plugins.triggers import notionnotion.Event
Linearfrom mistralai.workflows.plugins.triggers import linearlinear.Event
Luccafrom mistralai.workflows.plugins.triggers import luccalucca.Event
Any other providerfrom mistralai.workflows.plugins.triggers import TriggerEventTriggerEvent

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)
        ...
Warning

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: workflows runs a workflow.
  • action: primitive_id is the workflow name, and type is start_workflow.

Save the trigger ID for cleanup and debugging.

Note

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'
Note

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.

Warning

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:

StatusMeaning
SUBMITTEDThe event matched and was handed to the handler
SUBMISSION_FAILEDThe event matched but couldn't be handed to the handler
TRIGGEREDThe action ran. The response includes a url linking to the run
TRIGGER_FAILEDThe 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_FAILED with the reason in detail.
  • 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

MethodPathPurpose
GET/v1/webhooksList 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

MethodPathPurpose
GET/v1/triggersList 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}/eventsList recent events and their statuses
DELETE/v1/triggers/{trigger_id}Delete a trigger

In production

In production

Going further

Going further