Skip to content

Connecting GitHub

Cairn earns its evidence from the systems where work actually happens. A connector is how it is reached from outside — the first place it stops trusting anything, and the one place worth being careful.

This page takes a GitHub repository from nothing to a pull request appearing in your Review Queue.

What a connector is, and what it is not

A connector is inbound only. GitHub posts to Cairn; Cairn never calls GitHub. There is no token to store, no App to install, no private key — just a signing secret that Cairn generates and you paste at the provider.

Two properties are worth knowing before you start, because both are load-bearing later:

  • Reach is enumerated, never implied. A connector may touch exactly the repositories you list and nothing else. There is deliberately no "all repositories" option.
  • Every delivery is signed. Cairn computes HMAC-SHA256 over the exact bytes of the request body and compares it with the header. Nothing is written on behalf of an unverified sender — a failed signature never becomes a row.

Go to Connectors and choose GitHub. Name the repositories it may reach, as owner/name, one per line or comma-separated.

Submitting returns "Waiting for approval", and that is the designed path, not an error. Linking a connector is a Level-5 action — it grants Cairn reach into a system other people can see — so it stops for a human even when an owner asks. Approve it in the Attention Inbox, then submit again.

Cairn then shows you a signing secret, once. It is stored encrypted, and no screen will ever show it again. If you lose it, rotate to issue a new one.

2 · Point GitHub at Cairn

Repository → Settings → Webhooks → Add webhook.

Field Value
Payload URL the URL Cairn showed you, ending /api/connectors/…/webhook
Content type application/json
Secret the secret Cairn showed once
SSL verification Enable
Events Let me select individual events — the five below

Content type is the one to get right. GitHub defaults to application/x-www-form-urlencoded, which Cairn cannot parse. A hook with a perfect secret and the wrong content type fails every time, and the error looks nothing like the cause.

The five events

pull_request · pull_request_review · check_suite · workflow_run · deployment_status

Everything else is accepted and ignored, so subscribing to more only adds noise to the delivery log. Leave Pushes unchecked — Cairn ignores push events deliberately.

3 · Bind a Mission — the step people miss

This is the one that catches everyone. The allow-list lets a delivery in; the binding gives it somewhere to land.

Open the Mission this work ships in, and set Ships in to the repository.

Without it, a correctly signed delivery about an allowed repository is accepted, authenticated, and shelved — recorded as no_mission_ships_in_this_resource. From GitHub's side that is a 202, indistinguishable from success. Nothing is broken and nothing happens.

4 · Check it works

  1. GitHub's Recent Deliveries shows 202.
  2. Cairn's Connectors page shows "last delivery: just now".
  3. Open a pull request. It appears in the Review Queue badged "from GitHub", and a committed Mission moves to reviewable.

When something is wrong

Every delivery returns 401

The signature did not verify. In order of likelihood:

  • The secret does not match. Rotate it in Cairn and paste the new one at GitHub. Both ends must change together.
  • CONNECTOR_SECRET_KEY was added after the connector was linked. This is the subtle one. Webhook secrets are encrypted at rest with a key taken from CONNECTOR_SECRET_KEY, or derived from AUTH_SECRET when that is not set. Introducing the variable later changes the key, so the stored secret can no longer be opened — and Cairn answers 401 because it cannot check the signature at all. Rotating fixes it. Set CONNECTOR_SECRET_KEY before linking anything and it never arises.
  • The content type is form-encoded. See above.

Cairn shows a rejection count on the Connectors page when this is happening, because silence is otherwise the only symptom of a wrong secret.

Deliveries succeed but nothing appears

Almost always the missing Mission binding — step 3. Check the connector's delivery log: an event recorded as ignored with no_mission_ships_in_this_resource says exactly this. An event recorded against a repository you did not list says resource_not_allowed, which means the hook is sending something the connector was never granted.

Rotating the secret

Connectors → Rotate secret. Like linking, rotation is Level 5, so the first attempt raises a checkpoint; approve it in the Inbox and rotate again. The old secret stops working immediately, so deliveries fail until you paste the new one at GitHub — do the two together.

A GitHub App instead of a repository webhook

A repository webhook is enough for everything described here. A GitHub App is worth it for one reason: its "Only select repositories" installation is a stronger control than anything Cairn enforces internally, because GitHub enforces it — a compromised Cairn still cannot reach a repository the App was never installed on.

Same Payload URL, same secret, no callback URL or private key needed. Read-only permissions are sufficient: Metadata, Pull requests, Checks, Actions and Deployments. Leave Contents off — it is only needed for push, which Cairn ignores.

One constraint to know: a GitHub App has a single webhook URL and secret for the whole App, while Cairn's are per connector and a connector belongs to one organization. One App therefore maps to one Cairn workspace. Per-repository webhooks do not have this limit.

Other systems

The Generic webhook connector accepts a signed JSON POST from anything, on the same governed path: same signature check, same allow-list, same audit trail. Include a resource field naming one of the resources you listed, and sign the exact bytes of the body.

What it does not give you is meaning. A deploy.ok from a bespoke script is stored and emitted, and nothing derives Evidence from it until someone teaches Cairn what it means.