# GitHub Webhook Integration

This document covers how to configure and use the GitHub webhook integration in izri to automatically trigger repository analysis on push events.

---

## Overview

When a GitHub push event is delivered to the webhook endpoint, izri:

1. Validates the HMAC-SHA256 signature using the project's configured secret.
2. Checks whether the push matches the configured trigger conditions (branch list, file patterns).
3. Creates a `webhook_events` record for audit/retry tracking.
4. If conditions match, creates a `project_analyses` record and queues repository analysis.

---

## Webhook endpoint

```
POST /api/webhooks/github/:projectId
```

| Parameter   | Description                              |
|-------------|------------------------------------------|
| `projectId` | The izri project ULID (from the UI) |

### Required headers (sent automatically by GitHub)

| Header                  | Description                            |
|-------------------------|----------------------------------------|
| `X-Hub-Signature-256`   | HMAC-SHA256 signature of the body      |
| `X-GitHub-Event`        | Event type (only `push` is processed)  |
| `X-GitHub-Delivery`     | Unique delivery UUID for idempotency   |
| `Content-Type`          | `application/json`                     |

---

## Setting up a webhook in GitHub

1. Navigate to your GitHub repository → **Settings** → **Webhooks** → **Add webhook**.
2. Set **Payload URL** to:
   ```
   https://<your-api-host>/api/webhooks/github/<projectId>
   ```
3. Set **Content type** to `application/json`.
4. Enter the **Secret** generated by izri (see [Configuring via tRPC](#configuring-via-trpc) below).
5. Under **Which events would you like to trigger this webhook?**, choose **Just the `push` event** (other events are acknowledged but not acted on).
6. Click **Add webhook**.

---

## Configuring via tRPC

All webhook management is exposed through the `webhooks` tRPC router.

### Create / update webhook config

```ts
const result = await trpc.webhooks.configure.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
  branches: ['main', 'develop'],   // empty = all branches
  filePatterns: ['src/**/*.ts'],   // empty = any file
})

// result.secret  ← copy this into your GitHub webhook settings (shown once only)
// result.configId
```

### Get current config (secret excluded)

```ts
const config = await trpc.webhooks.getConfig.query({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
})
// { active, branches, filePatterns, configId, createdAt, updatedAt }
```

### Enable / disable webhook

```ts
await trpc.webhooks.setActive.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
  active: false,
})
```

### Rotate secret

If you suspect your secret has been compromised, rotate it. After rotating, update the secret in the GitHub webhook settings.

```ts
const { secret } = await trpc.webhooks.rotateSecret.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
})
// Update GitHub webhook settings with the new secret
```

### List delivery events

```ts
const { events } = await trpc.webhooks.listEvents.query({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
  limit: 20,
  // optional: status: 'FAILED' | 'PROCESSED' | 'PENDING' | 'SKIPPED' | 'ABANDONED'
})
```

### Delete webhook config

```ts
await trpc.webhooks.delete.mutate({
  projectId: '01JXXXXXXXXXXXXXXXXXXXXXXX',
})
```

---

## Trigger conditions

### Branch filter

| Config           | Behaviour                            |
|------------------|--------------------------------------|
| `branches: []`   | Trigger on **all** branches          |
| `branches: ['main', 'develop']` | Trigger only on those branches |

### File pattern filter

Patterns use `*` (single-directory wildcard) and `**` (cross-directory wildcard).

| Pattern           | Matches                              |
|-------------------|--------------------------------------|
| `src/**/*.ts`     | Any `.ts` file under `src/`          |
| `*.json`          | Any `.json` file at the root         |
| `packages/api/**` | Any file under `packages/api/`       |
| _(empty array)_   | Any changed file — always triggers   |

The filter is evaluated against the union of `added`, `modified`, and `removed` files across all commits in the push.

---

## Security

- Signatures are validated using **HMAC-SHA256** (Web Crypto API; no native addon required).
- Comparison uses a **constant-time** byte-by-byte XOR check to prevent timing attacks.
- Deliveries without a valid `X-Hub-Signature-256` header are rejected with **HTTP 401**.
- Duplicate deliveries (same `X-GitHub-Delivery`) are idempotent — the second delivery is ignored.

---

## Error handling and retry logic

Failed webhook events are retried up to **3 times** with exponential backoff:

| Attempt | Delay after failure |
|---------|---------------------|
| 1st     | 1 minute            |
| 2nd     | 5 minutes           |
| 3rd     | 15 minutes          |

After 3 failed attempts the event is marked **ABANDONED**.

The retry function `retryFailedWebhookEvents()` should be called by a periodic background job (e.g. every minute):

```ts
import { retryFailedWebhookEvents } from '@izri/trpc/services/webhookService'

// In your cron / background worker:
const { retried, abandoned } = await retryFailedWebhookEvents()
```

---

## Event status reference

| Status      | Description                                          |
|-------------|------------------------------------------------------|
| `PENDING`   | Received but not yet processed                       |
| `PROCESSED` | Successfully triggered repository analysis           |
| `SKIPPED`   | Trigger conditions not met (branch / file filter)    |
| `FAILED`    | Processing failed; will be retried                   |
| `ABANDONED` | Max retries exceeded                                 |

---

## Database tables

### `webhook_configs`

Stores one configuration per project.

| Column          | Type         | Description                             |
|-----------------|--------------|-----------------------------------------|
| `id`            | varchar(191) | ULID primary key                        |
| `project_id`    | varchar(191) | References `projects.id`                |
| `secret`        | varchar(255) | HMAC secret (stored hashed in future)   |
| `branches`      | jsonb        | Branch allowlist (`string[]`)           |
| `file_patterns` | jsonb        | Glob pattern allowlist (`string[]`)     |
| `active`        | boolean      | Whether deliveries are processed        |
| `created_at`    | timestamp    |                                         |
| `updated_at`    | timestamp    |                                         |

### `webhook_events`

Audit log of every delivery received.

| Column          | Type         | Description                             |
|-----------------|--------------|-----------------------------------------|
| `id`            | varchar(191) | ULID primary key                        |
| `project_id`    | varchar(191) | References `projects.id`                |
| `delivery_id`   | varchar(255) | GitHub's `X-GitHub-Delivery` UUID       |
| `event_type`    | varchar(64)  | e.g. `push`                             |
| `payload`       | jsonb        | Full GitHub webhook payload             |
| `status`        | varchar(32)  | See status reference above              |
| `error`         | text         | Error message (if failed)               |
| `attempts`      | integer      | Number of processing attempts           |
| `next_retry_at` | timestamp    | When to next retry (if FAILED)          |
| `processed_at`  | timestamp    | When successfully processed             |
| `created_at`    | timestamp    |                                         |
