Skip to main content

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.

Your agent finds out about new threads and customer messages by receiving 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 SettingsWebhooks 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 for the full setup, including delivery semantics, retries, and security options like request signing and mTLS.

Verify and parse events with the SDK

The @team-plain/webhooks package handles signature verification, replay protection, and JSON schema validation, and gives you fully typed event payloads.
npm install @team-plain/webhooks
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);
});
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.
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 for the full API.

Events that matter for an agent

These are the events agents most commonly subscribe to.
EventWhen it fires
thread.thread_createdA new thread is created in your workspace, regardless of which channel it came from.
thread.email_receivedA customer email arrives. Fires for the first email and every reply.
thread.chat_receivedA customer sends a chat message via the chat widget.
thread.slack_message_receivedA customer posts in a connected Slack channel.
thread.thread_assignment_transitionedA thread’s assignee changes. Use this if your agent should only run when assigned.
thread.thread_status_transitionedA thread moves between TODO, SNOOZED, and DONE.
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.
The full list of events lives under Webhooks → Webhook events.