# Integrations Repo admins manage integrations at `///settings/integrations`. Org owners manage them at `/orgs//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=`. - 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/`. | | 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: ```sh 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.