# Environments

Environments connect a repository to a PaaS provider so deploy results show up in Codebahn: a status on the commit and a preview link on the pull request. This is the Codebahn-specific layer behind [Deploy to a PaaS](/docs/deploy-to-paas/), which walks through provider setup end to end.

You deploy from your own CI workflow using the provider's CLI (`vercel deploy`, `netlify deploy`, `railway up`, and so on). Codebahn does not connect to the provider or trigger deploys. It receives a callback you configure and records the outcome.

## How it works

1. **Receive a callback.** The provider POSTs to your environment's callback URL when a deploy finishes. Codebahn parses the payload and updates the deployment record.
2. **Write a commit status.** When a deploy reaches a terminal state, a status appears on the commit: success on `ready`, failure on `error` or `cancelled`.
3. **Post a PR comment.** On a successful preview deploy, the deployed URL is posted as a comment on the pull request.
4. **Show history.** Deployments are listed in the repository's Environments tab.

## Callback URL

Creating an environment generates a callback URL:

```
https://{instance}/-/environments/callback/{env_id}/{callback_token}
```

Paste it into your provider's webhook settings. The token in the path is the authentication: 24 bytes from `crypto/rand`, encrypted at rest, compared in constant time. Codebahn does not discover deploys on its own, so this URL is the only way status reaches a commit.

The endpoint accepts POST, reads up to 1 MB of body, runs provider-specific parsing, and falls back to a generic format.

## Callback payload formats

Set the provider when you create the environment so Codebahn uses the right parser. Recognized providers: Vercel, Netlify, Render, Railway, and Cloudflare Pages. Anything else (or a parse failure) is read as the generic format:

```json
{
  "status": "success",
  "url": "https://my-app.example.com",
  "id": "deploy-123",
  "error": ""
}
```

| Field | Required | Values |
|---|---|---|
| `status` | Yes | `success`, `ready`, `error`, `failed`, `cancelled`, `building`, `pending` |
| `url` | No | Deployed URL (must be `http://` or `https://`) |
| `id` | No | External deployment ID, used to correlate callbacks |
| `error` | No | Error message on failure |

### Provider notes

**[Vercel](https://vercel.com/docs/webhooks).** Events: `deployment.succeeded`, `deployment.ready`, `deployment.promoted` (success), `deployment.error` (failure), `deployment.created`, `deployment.build-requested` (building), `deployment.canceled`. Deployed URL from `payload.deployment.url`.

**[Netlify](https://docs.netlify.com/deploy/deploy-notifications/).** Payload is the deploy API object. Status from `state`: `ready` (success) or `error` (failure). URL from `deploy_ssl_url`, error from `error_message`.

**[Render](https://render.com/docs/webhooks).** Thin Standard Webhooks payload. Event type `deploy_ended`; status from `data.status` (`succeeded`, `failed`, `canceled`). No deployed URL or commit SHA in the payload. Render gates webhooks behind its paid plans, so check what your Render plan includes.

**[Railway](https://docs.railway.com/guides/webhooks).** Event type `Deployment.<state>` (e.g. `Deployment.completed`, `Deployment.failed`, `Deployment.crashed`). Commit SHA from `details.commitHash`. No deployed URL, no signature.

**[Cloudflare Pages](https://developers.cloudflare.com/notifications/notification-available/).** Event type `pages_event_alert`, events `EVENT_DEPLOYMENT_SUCCESS`, `EVENT_DEPLOYMENT_FAILED`, `EVENT_DEPLOYMENT_STARTED`. Point a Cloudflare notification at your callback URL.

## Deployment lifecycle

```
queued -> building -> ready
```

From `queued` or `building`, a deploy can also move to `error` or `cancelled`. Those three (`ready`, `error`, `cancelled`) are terminal.

The deployment record is created on the first callback for a new external deployment ID. Transitions are enforced, and a repeat callback for the current state is ignored.

A deployment stuck in `queued` or `building` for more than 30 minutes is marked failed.

## Commit statuses

| Deployment state | Commit status | Description |
|---|---|---|
| ready | success | "Deployment ready" (links to the deployed URL) |
| error | failure | Error message from the provider |
| cancelled | failure | "Deployment cancelled" |

A commit status is written only on these terminal states. The `queued` and `building` states update the deployment record (visible in the Environments tab) but do not write a commit status.

The status context is `deploy/{provider}` (e.g. `deploy/vercel`) and appears in the PR merge box.

## Security

- **Callback token.** Encrypted at rest, compared in constant time. The token alone authenticates a callback.
- **Optional signature verification.** Vercel uses HMAC-SHA1 (`x-vercel-signature`); Render uses Standard Webhooks HMAC-SHA256 (`webhook-signature`). Providers without signing (Railway, Cloudflare Pages, generic) rely on the callback token. The signing secret is encrypted at rest.
- **Payload limits.** Body capped at 1 MB, deployed URLs must be `http(s)`, error messages truncated to 200 characters.
- **Access control.** Only repo admins configure environments. Deployment history is visible to anyone with repo read access.
- **Audit trail.** Connect, disconnect, and each callback are logged with a timestamp.
