> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobilerun.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Get notified by the assistant

> Receive a webhook when the Mobilerun assistant finishes, fails, or is waiting for your answer, so you can start a long job and walk away.

The Mobilerun assistant often runs for many minutes: scraping a list, working through a multi-step job, or waiting for you to answer a question. You should not have to keep the chat open to find out when it is done.

Three webhook events cover the whole lifecycle of an assistant turn. They fire for every conversation in your organization, whether the tab is open, in the background, or closed.

| Event                 | When it fires                                                                 |
| --------------------- | ----------------------------------------------------------------------------- |
| `chat.turn.completed` | The assistant finished the current turn successfully.                         |
| `chat.turn.failed`    | The turn ended with an error, including a crash the assistant recovered from. |
| `chat.input.required` | The assistant asked you a question and is waiting for an answer.              |

## Option A: let the assistant set it up

The assistant can configure its own notifications. Start a conversation and say, in your own words:

> Notify me at [https://hooks.example.com/mobilerun](https://hooks.example.com/mobilerun) when you finish, fail, or need something from me. Use sensible settings and send a test.

The assistant will:

1. show you which notification events exist and confirm the selection,
2. create the webhook endpoint for that URL, filtered to exactly those events,
3. show you the signing secret **once** and ask you to store it,
4. send a test delivery and confirm it arrived.

Later you can ask it to change the events, re-enable a blocked endpoint, rotate the secret, or look up why a delivery failed.

<Note>
  The assistant creates generic signed webhooks. It does not send email or Slack messages itself. Point the URL at an automation tool (n8n, Zapier, Make) or your own receiver to turn the event into a message. It also cannot change a *workflow's* **Notify on success / failure** setting; that lives in the workflow settings in the dashboard.
</Note>

## Option B: set it up yourself

1. Open **Webhooks** in the dashboard and click **Add Webhook**.
2. Enter your HTTPS URL.
3. Select `chat.turn.completed`, `chat.turn.failed`, and `chat.input.required`.
4. Save and store the secret shown once.
5. Click **Send test** and check that your receiver logged a `webhook.test` event.

Everything about signing, retries, and headers is the same as for any other webhook; see [Webhooks](/webhooks).

## What the payload contains

The payloads are deliberately minimal. They identify the conversation and the turn, but never include your prompt, the assistant's reply, or the text of a question. Open the conversation to read those.

### `chat.turn.completed` and `chat.turn.failed`

```json theme={null}
{
  "schemaVersion": 1,
  "id": "…",
  "source": "agents-api",
  "type": "chat.turn.completed",
  "ownerId": "…",
  "userId": "…",
  "createdBy": "…",
  "occurredAt": "2026-09-04T10:52:13.412Z",
  "data": {
    "sessionId": "d0d4a3f5-…",
    "turnId": "6b3f2c1e-…",
    "durationMs": 754210,
    "settleReason": "completed",
    "trigger": "chat"
  }
}
```

| Field          | Description                                                              |
| -------------- | ------------------------------------------------------------------------ |
| `sessionId`    | The conversation. Open it at `https://cloud.mobilerun.ai/c/{sessionId}`. |
| `turnId`       | The turn that just ended.                                                |
| `durationMs`   | How long the turn ran.                                                   |
| `settleReason` | `completed` on `chat.turn.completed`, `error` on `chat.turn.failed`.     |
| `trigger`      | Always `chat`.                                                           |

You get exactly one terminal event per turn. No terminal event is sent when you stop the turn yourself, when the turn is stopped by the credit budget or a step limit, or when the turn belongs to a workflow rather than an interactive conversation. Those cases are visible in the chat only.

### `chat.input.required`

```json theme={null}
{
  "type": "chat.input.required",
  "data": {
    "sessionId": "d0d4a3f5-…",
    "turnId": "6b3f2c1e-…",
    "questionId": "q_01J…",
    "questionCount": 1,
    "trigger": "chat"
  }
}
```

| Field           | Description                               |
| --------------- | ----------------------------------------- |
| `questionId`    | Identifies the pending question.          |
| `questionCount` | How many questions are open in this turn. |

The event is sent once per question. If you answer within the first seconds, before the event has been dispatched, it may be skipped.

## Turning it into a message

<CardGroup cols={2}>
  <Card title="Slack via n8n" icon="workflow" href="/n8n">
    Webhook trigger → Slack node. Post `type`, `settleReason`, and a link built from `sessionId`.
  </Card>

  <Card title="Email via Zapier or Make" icon="envelope">
    Catch Hook → Email. Use one Zap per event type or branch on `type`.
  </Card>

  <Card title="Push notification" icon="smartphone">
    Forward to a service such as ntfy or Pushover from your receiver.
  </Card>

  <Card title="Your own receiver" icon="code" href="/webhooks#verifying-the-signature">
    Verify the signature, deduplicate on `id`, and act on `type`.
  </Card>
</CardGroup>

A minimal receiver that forwards to Slack:

```python theme={null}
import hmac, hashlib, time, requests
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = "…"
SLACK_WEBHOOK = "https://hooks.slack.com/services/…"
TEXT = {
    "chat.turn.completed": "✅ The assistant finished",
    "chat.turn.failed": "❌ The assistant failed",
    "chat.input.required": "❓ The assistant needs your answer",
}

@app.route("/mobilerun", methods=["POST"])
def mobilerun():
    ts = request.headers.get("X-Mobilerun-Timestamp", "")
    raw = request.get_data()
    expected = "sha256=" + hmac.new(SECRET.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Mobilerun-Signature", "")):
        abort(401)
    if abs(time.time() * 1000 - int(ts or 0)) > 300_000:
        abort(400)

    event = request.get_json()
    text = TEXT.get(event["type"])
    if text:
        link = f"https://cloud.mobilerun.ai/c/{event['data']['sessionId']}"
        requests.post(SLACK_WEBHOOK, json={"text": f"{text}: {link}"}, timeout=5)
    return "", 200
```
