# Plain (https://docs.ctxpipe.ai/docs/self-hosting/plain)

## Overview

Self-hosted Plain uses the same connector flow as hosted ctxpipe. Plain has no
OAuth application for other products, so an org admin pastes the API key of a
Plain machine user in the setup. ctxpipe encrypts the key and the webhook
token on the connection with the connection secret key of the deployment.

There is no `PLAIN_*` environment variable. You do not need to change Compose,
Railway, or CDK settings.

## What you need

* A public HTTPS `AUTH_BASE_URL` for the webhook target. Plain sends webhooks
  only to a public HTTPS address. Without it, the connector syncs once a day
  and on **Sync now**. ctxpipe cannot check that Plain reaches the URL: when
  Plain accepts the target but cannot reach it, the card still shows live
  updates as on.
* A ctxpipe GitHub App connection with write access to each Workspace repository
  that syncs Plain content.
* The backend and worker able to reach `core-api.uk.plain.com` over HTTPS.

## Webhook target

At connect, ctxpipe creates one Plain webhook target for each connection:

```text
{AUTH_BASE_URL}/api/v1/webhook/plain/{connectionId}
```

The target sends `timeline.timeline_entry_changed` events with a random token
in the `X-Ctxpipe-Webhook-Token` header. ctxpipe compares the token and
refuses a request without it. A failed enqueue returns `503`, and Plain sends
the event again.

## Verify the connector

1. Open **Connectors → Add connection → Plain**.
2. Paste the API key of a machine user with the permissions that the setup
   lists.
3. In the **Add to Workspaces** step, select a Workspace and at least one
   Help Center or label. Merge the pull request that adds the source file under
   `sources/plain/`.
4. Confirm the source shows **OK** in the Workspace settings and files appear
   under `plain/`.
5. In Plain, open **Settings → Webhooks** and confirm the ctxpipe target. Add a
   note to a real thread with a selected label, not a Plain test thread. Confirm that
   `POST /api/v1/webhook/plain/{connectionId}` returns `200`, and that the
   thread file changes after the 10-minute window.

See [Plain connector](/docs/connections/source-connectors/plain) for the
files, the sync, and product troubleshooting.
