# Webhook Endpoints Reference

GitHub webhook integration for automated test triggering.

---

## Incoming Webhook: POST /api/webhooks/github/:projectId

The raw HTTP endpoint that receives GitHub push events. Registered in `apps/api` (Hono route).

**Authentication**: HMAC-SHA256 signature (GitHub webhook secret)

**Headers**:
| Header | Description |
|--------|-------------|
| `X-Hub-Signature-256` | `sha256=<hmac>` computed with your webhook secret |
| `X-GitHub-Event` | Event type: `push`, `pull_request`, etc. |
| `X-GitHub-Delivery` | Unique delivery ID |

**Supported events**: `push`, `pull_request`

**Response**:
- `200` — Event received and queued
- `400` — Missing or invalid signature
- `404` — Project not found or webhook not configured
- `429` — Rate limit exceeded (30 req/min per IP)

---

## tRPC Webhook Procedures

All procedures require authentication.

### `webhooks.configure`

Create or replace webhook configuration for a project. Returns the webhook URL and generated secret — **the secret is only shown once**.

**Input**:
```ts
{
  projectId: string        // ULID
  branches: string[]       // Empty = all branches
  filePatterns: string[]   // Glob patterns, empty = any file
}
```

**Response**:
```ts
{
  configId: string
  secret: string  // Store this — it won't be shown again
}
```

**Rate limit**: 30 req/min (webhook rate limiter)

---

### `webhooks.getConfig`

Get webhook configuration (without exposing the secret).

**Input**: `{ projectId: string }`

**Response**:
```ts
{
  configId: string
  active: boolean
  branches: string[]
  filePatterns: string[]
  createdAt: Date
  updatedAt: Date
} | null
```

---

### `webhooks.setActive`

Enable or disable webhook delivery for a project.

**Input**: `{ projectId: string, active: boolean }`

**Response**: `{ active: boolean }`

---

### `webhooks.rotateSecret`

Generate a new webhook secret without changing other settings. Returns the new secret — update your GitHub webhook configuration immediately.

**Input**: `{ projectId: string }`

**Response**: `{ secret: string }`

---

### `webhooks.listEvents`

List recent webhook event deliveries.

**Input**:
```ts
{
  projectId: string
  limit?: number  // 1-100, default 20
  status?: 'PENDING' | 'PROCESSED' | 'FAILED' | 'SKIPPED' | 'ABANDONED'
}
```

**Response**:
```ts
{
  events: Array<{
    id: string
    deliveryId: string
    eventType: string
    status: string
    attempts: number
    error: string | null
    createdAt: Date
    processedAt: Date | null
    nextRetryAt: Date | null
  }>
}
```

---

### `webhooks.delete`

Delete webhook configuration for a project.

**Input**: `{ projectId: string }`

**Response**: `{ deleted: true }`

---

## Webhook Signature Verification

GitHub signs each delivery using HMAC-SHA256 with your webhook secret. The platform verifies this using a timing-safe comparison:

```
X-Hub-Signature-256: sha256=<hex>
```

- Uses `crypto.subtle` (Web Crypto API) for constant-time comparison
- Mismatched or missing signatures return 400

## Trigger Conditions

When a webhook event arrives, the trigger service evaluates:

1. **Branch filter** — if `branches` is non-empty, the push must target a listed branch
2. **File pattern filter** — if `filePatterns` is non-empty, at least one changed file must match a glob pattern
3. **Deduplication** — if a test run already exists for the same `projectId + commitSha + eventType` within the last hour, the event is skipped
4. **Debouncing** — rapid pushes to the same branch are coalesced (configurable `debounceMs`)
