---
title: "Linear issue-state webhook (feedback resolution)"
method: POST
path: "/feedback/webhook/linear"
tags: ["Feedback"]
---

# Linear issue-state webhook (feedback resolution)

`POST /feedback/webhook/linear`

## Request

- Base URL: `https://api.mnemom.ai/v1`
- URL: `https://api.mnemom.ai/v1/feedback/webhook/linear`
- Auth: none required

Receives Issue state-change events from Linear (MNE-1680). When a linked issue (matched via the `Feedback-ID:` marker MNE-1679 embeds in the issue body, resolved through `feedback_work_items.linear_issue_id`) moves to a completed state (Done / Released), writes a `feedback_resolutions` row and flips the submission — and any cluster-dupe submissions — to resolved. Non-issue events, non-completed states, and issues with no linked work item are acknowledged as no-ops (200). Authenticated by the `linear-signature` header, an HMAC-SHA-256 digest of the raw request body keyed on `LINEAR_WEBHOOK_SIGNING_SECRET`, timing-safe-compared. FAILS CLOSED: if that secret is not configured, every request is rejected — this endpoint never silently skips signature validation. Handler: `handleLinearWebhook` in `src/feedback/linear-webhook.ts`; `runLinearReconciler` is the scheduled backstop for dropped deliveries.

## Headers

- `linear-signature` string, required

## Request body

- object: Linear's Issue webhook payload. Only `type: "Issue"` with `action: "update"|"create"` is processed; other events are acknowledged as no-ops.
  - `type` string: Linear event type, e.g. "Issue".
  - `action` string: e.g. "update", "create".
  - `data` object: The Issue resource: id (Linear UUID), identifier (e.g. "MNE-1234"), state.name/state.type, description, mergeCommitHash, releaseLabel.

## Response `200`

Acknowledged — either a real resolution was recorded (`resolved: true/false` reflecting whether this was already resolved) or the event was a no-op (`skipped: <reason>`).

- object
  - `ok` boolean, required
  - `skipped` string: Set when no action was taken: "not_an_issue_event" | "not_a_resolution_state" | "no_linked_work_item".
  - `resolved` boolean
  - `submission_id` string, uuid

## Other responses

- `400`: Bad request
- `401`: Authentication required
- `403`: Cross-origin write refused (`cross_origin_rejected`). A state-changing request that carries the browser session cookie must come from a first-party origin (`Origin`, or failing that `Referer`) and must not send a form-style body (text/plain, urlencoded, multipart) the operation does not declare. Bearer and API-key callers are never subject to this check.
- `429`: Rate-limit exceeded. The global per-IP limiter (100 requests/minute, applied to every `/v1/*` route) rejected this request. Back off until the window resets — `Retry-After` carries the cooldown in seconds and `X-RateLimit-Reset` the absolute reset time.
- `500`: Server error — request was well-formed but the server failed to fulfill it. Typically a downstream dependency (DB / RPC / external API) returned an unexpected error.

---

[API](https://skmtc.dev/mnemom/apis/mnemom-api.md) · [All operations](https://skmtc.dev/mnemom/apis/mnemom-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mnemom/apis/mnemom-api/revisions/274b7a6ea182?raw)
