68 lines · 6.0 KB

Integrations

Repo admins manage integrations at /<owner>/<repo>/settings/integrations. Org owners manage them at /orgs/<org>/integrations, and org integrations receive events from every repo in the org.

How delivery works

  • Forge events (emitWebhook) are fanned out on the gitfor-integrations queue. Failed deliveries retry with exponential backoff (15 s doubling, capped at 1 h). After 7 attempts (6 retries) a delivery moves to gitfor-integrations-dlq and is marked dead in the delivery log.
  • Permanent failures (most 4xx, missing credentials, rejected URLs) are logged as failed and not retried.
  • Every outbound URL goes through assertPublicHttpsUrl. The check is by hostname: IP literals and internal suffixes are rejected, but DNS is not resolved. This relies on Workers egress not reaching private ranges. Requests use redirect: "manual", a 10 s timeout, a 256 KB request cap and a 64 KB response cap.
  • Each payload is signed with the integration's secret in X-Gitfor-Signature-256: sha256=<hex HMAC>.
  • Credentials (webhook URLs, OAuth tokens, signing secrets) are stored AES-GCM encrypted with SECRETS_KEY.

Keeping noise down

Integrations are quiet by default:

  • Only pull requests and releases are on.
  • CI only reports failures.
  • Pushes and CI only report the default branch.
  • Drafts and bots are skipped.

Each integration has per-event toggles, branch globs, and a digest mode that batches events into one message every N minutes. The digest is flushed by the 5-minute cron.

Providers and secrets

Provider Secrets Notes
Slack SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET OAuth install (chat:write,commands,incoming-webhook) or a pasted incoming webhook. Each channel gets its own event filter. The slash command /gitfor status owner/repo is served at /integrations/hooks/slack/commands.
Google Chat none Paste a space webhook (chat.googleapis.com). Messages are sent as cardsV2.
Linear LINEAR_CLIENT_ID, LINEAR_CLIENT_SECRET Issue keys (ENG-123) in PR titles, branches and commits link PRs and commits to issues. On PR open, merge or close, the issue moves to the configured state, and is never moved backwards. A signed Linear webhook is registered automatically at /integrations/hooks/linear/<id>.
GitHub GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET, GITHUB_APP_WEBHOOK_SECRET Authenticates with GitHub App installation tokens only. Enable "Request user authorization during installation" so ownership of the installation can be verified. Supports a one-way mirror in either direction, a one-shot import of issues and PRs, and webhook relay. The app webhook URL is /integrations/hooks/github.
Webhook none Signed JSON with { event, summary, payload }.

Notes on the GitHub integration:

  • The GitHub → gitfor mirror runs at most every 15 minutes, and also runs on each relayed GitHub push.
  • Mirrors cover branches and tags only.
  • Mirrors update and create refs but never delete them.
  • The GitHub → gitfor mirror is a force mirror. It overwrites diverged gitfor branches, bypasses branch protection and does not run gitfor push hooks or CI, so use it only on read-only copies.
  • Only repositories that the installing GitHub user could access through the installation can be chosen.
  • PRs are imported as issues labelled pull-request, since their branches are not copied.
  • Re-running an import skips items that were already imported.

Local development and e2e

Add INTEGRATIONS_ALLOW_LOOPBACK=true to .dev.vars to allow http://localhost and 127.0.0.1 sinks. Never set it in production. Then run:

bun test src
E2E_BASE_URL=http://localhost:3000 bun run test:e2e:integrations

The e2e flow:

  1. Signs up and creates an org.
  2. Checks that a private-network URL is rejected.
  3. Adds a webhook integration that points at a local sink.
  4. Sends a test event and verifies its HMAC signature.
  5. Waits for the delivery log to show success, then redelivers.