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

# Receiving events

> Listen for thread and message events with webhooks.

Your agent finds out about new threads and customer messages by receiving [webhooks](/webhooks) from Plain. This page covers the setup and the events that are most useful when building an agent.

## Set up a webhook endpoint

You need a publicly available HTTPS endpoint that accepts `POST` requests. Once you have one, go to **Settings** → **Webhooks** in Plain and click **+ Add webhook target**. Choose the URL, the events you want to receive, and copy the **signing secret**. You'll need it to verify webhook signatures.

See the [webhooks overview](/webhooks) for the full setup, including delivery semantics, retries, and security options like [request signing](/request-signing) and [mTLS](/mtls).

## Verify and parse events with the SDK

The [`@team-plain/webhooks`](/webhooks/sdk) package handles signature verification, replay protection, and JSON schema validation, and gives you fully typed event payloads.

```bash theme={null}
npm install @team-plain/webhooks
```

```ts theme={null}
import { verifyPlainWebhook } from "@team-plain/webhooks";

app.post("/webhooks/plain", async (req, res) => {
  const result = verifyPlainWebhook(
    req.body, // raw body string, see note below
    req.header("plain-request-signature")!,
    process.env.PLAIN_WEBHOOK_SECRET!,
  );

  if (result.error) {
    return res.status(400).send(result.error.message);
  }

  const event = result.data;

  // event.payload is a discriminated union, narrow on eventType
  switch (event.payload.eventType) {
    case "thread.thread_created":
      await onThreadCreated(event.payload);
      break;
    case "thread.email_received":
      await onEmailReceived(event.payload);
      break;
    case "thread.thread_assignment_transitioned":
      await onAssignmentChanged(event.payload);
      break;
  }

  res.sendStatus(200);
});
```

<Warning>
  `verifyPlainWebhook` needs the **raw request body**, not the parsed JSON. With Express, use `express.text({ type: "*/*" })`; with other frameworks, disable JSON parsing for the webhook route and read the body as a string.
</Warning>

For development, or when verification happens upstream (e.g. an API gateway), you can use `parsePlainWebhook` to skip the signature check and just validate the payload shape. See the [Webhooks SDK reference](/webhooks/sdk) for the full API.

## Events that matter for an agent

These are the events agents most commonly subscribe to.

| Event                                                                               | When it fires                                                                        |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| [`thread.thread_created`](/webhooks/thread-created)                                 | A new thread is created in your workspace, regardless of which channel it came from. |
| [`thread.email_received`](/webhooks/thread-email-received)                          | A customer email arrives. Fires for the first email and every reply.                 |
| [`thread.chat_received`](/webhooks/thread-chat-received)                            | A customer sends a chat message via the [chat widget](/ui-components).               |
| [`thread.slack_message_received`](/webhooks/thread-slack-message-received)          | A customer posts in a connected Slack channel.                                       |
| [`thread.thread_assignment_transitioned`](/webhooks/thread-assignment-transitioned) | A thread's assignee changes. Use this if your agent should only run when assigned.   |
| [`thread.thread_status_transitioned`](/webhooks/thread-status-transitioned)         | A thread moves between `TODO`, `SNOOZED`, and `DONE`.                                |

<Note>
  If you subscribe to both `thread.thread_created` and `thread.email_received` you'll get two events for the first email in a thread. Check the `isStartOfThread` field on the email payload if you only want to react once.
</Note>

The full list of events lives under [Webhooks → Webhook events](/webhooks/thread-created).
