# How to Set Up GitHub Integration for Bidirectional Task Sync in Kaneo

> Effortlessly sync GitHub issues, PRs, and comments with Kaneo. Set up bidirectional task sync by configuring a GitHub App and webhook. Streamline your workflow today.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-06

---

**To set up GitHub integration in Kaneo, configure environment variables for a GitHub App, register the app with webhook URL `<KANEO_API_URL>/github-integration/webhook`, then use the REST API to link a project to a repository for automatic two-way synchronization of issues, pull requests, and comments.**

Kaneo's open-source task management platform supports native **GitHub integration for bidirectional task sync**, allowing teams to manage work in either tool while keeping both systems consistent. This guide walks through the complete implementation based on the Kaneo source code, from environment configuration to real-time webhook processing.

## Architecture Overview

Kaneo follows a **controller-router-schema pattern** for all integrations. The GitHub module consists of these core components:

| Component | Location | Purpose |
|-----------|----------|---------|
| **API Routes** | [`apps/api/src/github-integration/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/github-integration/index.ts) | REST endpoints for CRUD operations and webhooks |
| **Config Schema** | [`apps/api/src/plugins/github/config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/config.ts) | Valibot validation for integration settings |
| **Database Model** | [`apps/api/src/database/schema.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts) | `integration` table with `type = "github"` |
| **Controllers** | `apps/api/src/github-integration/controllers/*.ts` | Business logic for each operation |
| **GitHub App Wrapper** | [`apps/api/src/plugins/github/utils/github-app.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/utils/github-app.ts) | Authenticated Octokit singleton |
| **Webhook Handler** | [`apps/api/src/plugins/github/webhook-handler.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/webhook-handler.ts) | Processes GitHub events and updates tasks |
| **Front-End Hooks** | `apps/web/src/hooks/mutations/github-integration/*` | React Query integration for the UI |

The flow enforces **request validation → permission checks → business logic → response** at every step, using `hono-openapi` for type safety and OpenAPI documentation generation.

## Step 1: Configure Environment Variables

Create a GitHub App and set these environment variables in your Kaneo API deployment:

```bash
GITHUB_APP_ID=your-app-id
GITHUB_APP_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
GITHUB_APP_NAME=Kaneo
GITHUB_WEBHOOK_SECRET=your-webhook-secret

```

These values are read by [`apps/api/src/plugins/github/utils/github-app.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/utils/github-app.ts), which exports a `getGithubApp()` singleton that provides authenticated Octokit instances for API calls.

## Step 2: Register Your GitHub App

1. Navigate to **Settings → Developer settings → GitHub Apps → New GitHub App**
2. Set **Webhook URL** to: `https://<your-kaneo-api>/github-integration/webhook`
3. Configure **Repository permissions**:
   - **Issues**: Read & Write
   - **Pull requests**: Read & Write
   - **Contents**: Read (for branch pattern matching)

The webhook endpoint is handled by `handleGithubWebhookRoute` in [`apps/api/src/github-integration/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/github-integration/index.ts), which verifies the `x-hub-signature-256` header before processing.

## Step 3: Create the Integration via API

Link a Kaneo project to a GitHub repository using the REST API:

```http
POST /github-integration/project/:projectId
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "repositoryOwner": "your-org",
  "repositoryName": "your-repo"
}

```

The [`create-github-integration.ts`](https://github.com/usekaneo/kaneo/blob/main/create-github-integration.ts) controller performs these operations:

1. Validates the request against `githubConfigSchema` from [`apps/api/src/plugins/github/config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/config.ts)
2. Verifies workspace permissions via `workspaceAccess.fromProject`
3. Fetches the installation ID via `githubApp.octokit.rest.apps.getRepoInstallation`
4. Stores configuration in the `integration` table with `defaultGitHubConfig` merged values

### Example: React Component for Integration Setup

```tsx
import { useMutation } from "@tanstack/react-query";
import createGithubIntegration from "@/fetchers/github-integration/create-github-integration";

function GithubIntegrationForm({ projectId }: { projectId: string }) {
  const createMutation = useMutation({
    mutationFn: (payload: { repositoryOwner: string; repositoryName: string }) =>
      createGithubIntegration(projectId, payload),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const repositoryOwner = (form.elements.namedItem("owner") as HTMLInputElement).value;
    const repositoryName = (form.elements.namedItem("repo") as HTMLInputElement).value;
    createMutation.mutate({ repositoryOwner, repositoryName });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="owner" placeholder="GitHub organization/user" required />
      <input name="repo" placeholder="Repository name" required />
      <button type="submit" disabled={createMutation.isLoading}>
        Connect GitHub
      </button>
    </form>
  );
}

```

The fetcher at [`apps/web/src/fetchers/github-integration/create-github-integration.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/web/src/fetchers/github-integration/create-github-integration.ts) wraps the generated OpenAPI client:

```ts
export default async function createGithubIntegration(
  projectId: string,
  data: { repositoryOwner: string; repositoryName: string }
) {
  const client = getClient();
  const response = await client["github-integration"].project[projectId].$post({ json: data });
  return response;
}

```

## Step 4: Verify Installation Status

Optionally validate permissions before linking:

```http
POST /github-integration/verify
Content-Type: application/json

{
  "repositoryOwner": "your-org",
  "repositoryName": "your-repo"
}

```

The [`verify-github-installation.ts`](https://github.com/usekaneo/kaneo/blob/main/verify-github-installation.ts) controller checks:
- Whether the GitHub App is installed on the repository
- Required permissions are granted
- Returns detailed error messages for missing scopes

## Step 5: Import Existing GitHub Issues

Bulk-import open issues as Kaneo tasks:

```http
POST /github-integration/import-issues
Content-Type: application/json
Authorization: Bearer <api-key>

{
  "projectId": "<project-id>"
}

```

The [`import-issues.ts`](https://github.com/usekaneo/kaneo/blob/main/import-issues.ts) controller:
- Fetches open issues via `octokit.rest.issues.listForRepo`
- Creates corresponding Kaneo tasks with GitHub metadata
- Stores `githubIssueId` for ongoing synchronization

## Step 6: Enable Real-Time Bidirectional Sync

Once configured, the integration operates automatically:

**Kaneo → GitHub**: Task status changes, labels, and comments are pushed to GitHub via the Octokit API.

**GitHub → Kaneo**: Webhook events processed by [`apps/api/src/plugins/github/webhook-handler.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/webhook-handler.ts) update tasks:

| GitHub Event | Kaneo Action |
|--------------|--------------|
| `issues.opened` | Create new task |
| `issues.edited` | Update task title/description |
| `issues.labeled` | Sync labels |
| `issues.closed` | Set task status to done |
| `pull_request.opened` | Create task linked to PR |
| `issue_comment.created` | Add comment to task |

The webhook handler verifies signatures using `crypto.createHmac('sha256', webhookSecret)` before dispatching events.

## Configuration Schema Reference

The integration config stored in the database follows this Valibot schema from [`apps/api/src/plugins/github/config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/config.ts):

```ts
export const githubConfigSchema = object({
  repositoryOwner: string(),
  repositoryName: string(),
  installationId: number(),
  branchPattern: optional(string(), "main"),
  syncPullRequests: optional(boolean(), true),
  syncComments: optional(boolean(), true),
});

export type GitHubConfig = Output<typeof githubConfigSchema>;

```

## Summary

- **Environment setup**: Configure `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, and `GITHUB_WEBHOOK_SECRET` for the Octokit wrapper in [`apps/api/src/plugins/github/utils/github-app.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/utils/github-app.ts)
- **API integration**: Use `POST /github-integration/project/:projectId` to link projects, validated by `githubConfigSchema` and stored in the `integration` table
- **Bidirectional sync**: Webhooks to `/github-integration/webhook` trigger `handleGitHubWebhook` for GitHub→Kaneo updates; Kaneo→GitHub updates use authenticated Octokit calls
- **Front-end support**: TanStack Query hooks in `apps/web/src/hooks/mutations/github-integration/*` provide React components with type-safe API access

## Frequently Asked Questions

### What permissions does the GitHub App need for bidirectional task sync?

The GitHub App requires **Issues** and **Pull requests** permissions with Read & Write access, plus **Repository contents** with Read access. These are validated by [`verify-github-installation.ts`](https://github.com/usekaneo/kaneo/blob/main/verify-github-installation.ts) and enforced when creating integrations.

### How does Kaneo authenticate with GitHub?

Kaneo uses `@octokit/app` through a singleton wrapper in [`apps/api/src/plugins/github/utils/github-app.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/plugins/github/utils/github-app.ts). The wrapper creates installation-specific Octokit instances using the app ID and private key from environment variables, then exchanges these for repository-scoped tokens via GitHub's API.

### Can I sync existing GitHub issues after setup?

Yes. Call `POST /github-integration/import-issues` with the `projectId` to bulk-import open issues. The [`import-issues.ts`](https://github.com/usekaneo/kaneo/blob/main/import-issues.ts) controller fetches all open issues and creates linked Kaneo tasks with `githubIssueId` stored for ongoing synchronization.

### What happens if the webhook signature verification fails?

Requests to `/github-integration/webhook` with invalid `x-hub-signature-256` headers are rejected by `handleGithubWebhookRoute` before reaching the handler. The route uses `crypto.timingSafeEqual` to prevent timing attacks during signature comparison.