---
description: Reference for verified Linear ingress from @flue/linear.
title: Linear Channel API | Flue
image: https://flueframework.com/docs/og4.jpg
---

# Linear Channel API

AI-generated, awaiting review [ View as Markdown](https://flueframework.com/docs/api/linear-channel/index.md) 

Import from `@flue/linear`.

## `createLinearChannel()`

```
function createLinearChannel<E extends Env = Env>(
  options: LinearChannelOptions<E>,
): LinearChannel<E>;
```

Creates one stateless `POST /webhook` route.

## `LinearChannelOptions`

```
interface LinearChannelOptions<E extends Env = Env> {
  webhookSecret: string;
  organizationId?: string;
  webhookId?: string;
  bodyLimit?: number;
  webhook(input: LinearWebhookHandlerInput<E>): LinearHandlerResult;
}
```

| Field          | Description                                                      |
| -------------- | ---------------------------------------------------------------- |
| webhookSecret  | Secret used to verify exact request bytes with HMAC-SHA256.      |
| organizationId | Optional signed organization constraint. Mismatches receive 403. |
| webhookId      | Optional signed webhook constraint. Mismatches receive 403.      |
| bodyLimit      | Maximum request body. Default: 1 MiB.                            |
| webhook        | Callback for every verified delivery.                            |

```
type LinearHandlerResult = void | JsonValue | Response | Promise<void | JsonValue | Response>;
```

Returning nothing produces an empty `200`. A JSON-compatible value becomes a JSON response. An ordinary Hono or Fetch `Response` passes through.

## `LinearChannel`

```
interface LinearChannel<E extends Env = Env> {
  readonly routes: readonly ChannelRoute<E>[];
  conversationKey(ref: LinearConversationRef): string;
  parseConversationKey(id: string): LinearConversationRef;
}
```

A file named `channels/linear.ts` serves`POST /channels/linear/webhook` relative to the `flue()` mount.

Conversation keys are canonical identifiers, not authorization capabilities.

## `LinearWebhookHandlerInput`

```
interface LinearWebhookHandlerInput<E extends Env = Env> {
  c: Context<E>;
  payload: LinearWebhookPayload;
  deliveryId: string;
}
```

`payload` is the verified provider-native body. `deliveryId` comes from the required `Linear-Delivery` header. The channel rejects a missing or malformed UUID-v4 value before invoking the callback. Linear signs the body, not that transport header, and the channel does not deduplicate.

## `LinearWebhookPayload`

```
export type { LinearWebhookPayload } from '@linear/sdk/webhooks';
```

The channel re-exports Linear’s official webhook union from`@linear/sdk/webhooks` and forwards the verified body unmodified, with Linear’s own field names, nesting, `type`, `action`, and `data`. Entity deliveries carry`type` (`'Comment'`, `'Issue'`, `'Project'`, …), `action` (`'create'`,`'update'`, `'remove'`), and `data`; agent-session deliveries carry`type: 'AgentSessionEvent'`, `action` (`'created'`, `'prompted'`),`agentSession`, and `agentActivity`. The channel does not reshape payloads and forwards verified deliveries the union does not model.

The official union includes a catch-all`EntityWebhookPayloadWithUnknownEntityData` member whose `type` stays widened to`string`, so a literal check such as `payload.type === 'Comment'` does **not**narrow the union on its own. Narrow application-side with a small type guard that pairs the `type` literal with a discriminating nested field, returning the official member type:

```
import type {
  AgentSessionEventWebhookPayload,
  EntityWebhookPayloadWithCommentData,
} from '@linear/sdk/webhooks';

function isCommentEvent(
  payload: LinearWebhookPayload,
): payload is EntityWebhookPayloadWithCommentData {
  return payload.type === 'Comment' && 'body' in payload.data;
}

function isAgentSessionEvent(
  payload: LinearWebhookPayload,
): payload is AgentSessionEventWebhookPayload {
  return payload.type === 'AgentSessionEvent' && 'agentSession' in payload;
}
```

The application derives conversation keys from native fields — for example`payload.organizationId` with `payload.data.issueId`/`parentId` or`payload.agentSession.id` — and passes them to `conversationKey(...)`.

## Identity

```
type LinearConversationRef =
  | {
      type: 'issue';
      organizationId: string;
      issueId: string;
      threadCommentId?: string;
    }
  | {
      type: 'agent-session';
      organizationId: string;
      agentSessionId: string;
    };
```

Top-level comments omit `threadCommentId` and use the issue conversation. Replies use their root comment id. Agent-session conversations use the session id.

## Errors

* `InvalidLinearConversationKeyError`
* `InvalidLinearInputError`, with structured `field`

See [Linear setup](https://flueframework.com/docs/ecosystem/channels/linear/) for webhook and official SDK composition.

## Docs Navigation

Current page: [Linear Channel API](https://flueframework.com/docs/api/linear-channel/)

### Sections

* [Guide](https://flueframework.com/docs/getting-started/quickstart/)
* [Reference](https://flueframework.com/docs/api/agent-api/)
* [CLI](https://flueframework.com/docs/cli/overview/)
* [SDK](https://flueframework.com/docs/sdk/overview/)
* [Ecosystem](https://flueframework.com/docs/ecosystem/)

### Runtime

* [ Configuration ](https://flueframework.com/docs/reference/configuration/)
* [ Errors Reference ](https://flueframework.com/docs/api/errors-reference/)
* [ Agent API ](https://flueframework.com/docs/api/agent-api/)
* [ Provider API ](https://flueframework.com/docs/api/provider-api/)
* [ Routing API ](https://flueframework.com/docs/api/routing-api/)
* [ GitHub Channel ](https://flueframework.com/docs/api/github-channel/)
* [ Stripe Channel ](https://flueframework.com/docs/api/stripe-channel/)
* [ Notion Channel ](https://flueframework.com/docs/api/notion-channel/)
* [ Resend Channel ](https://flueframework.com/docs/api/resend-channel/)
* [ Shopify Channel ](https://flueframework.com/docs/api/shopify-channel/)
* [ Intercom Channel ](https://flueframework.com/docs/api/intercom-channel/)
* [ Zendesk Channel ](https://flueframework.com/docs/api/zendesk-channel/)
* [ Salesforce Marketing Cloud Channel ](https://flueframework.com/docs/api/salesforce-marketing-cloud-channel/)
* [ Slack Channel ](https://flueframework.com/docs/api/slack-channel/)
* [ Discord Channel ](https://flueframework.com/docs/api/discord-channel/)
* [ Teams Channel ](https://flueframework.com/docs/api/teams-channel/)
* [ Google Chat Channel ](https://flueframework.com/docs/api/google-chat-channel/)
* [ Linear Channel ](https://flueframework.com/docs/api/linear-channel/)
* [ Telegram Channel ](https://flueframework.com/docs/api/telegram-channel/)
* [ WhatsApp Channel ](https://flueframework.com/docs/api/whatsapp-channel/)
* [ Twilio Channel ](https://flueframework.com/docs/api/twilio-channel/)
* [ Messenger Channel ](https://flueframework.com/docs/api/messenger-channel/)
* [ Streaming Protocol ](https://flueframework.com/docs/api/streaming-protocol/)
* [ Events Reference ](https://flueframework.com/docs/api/events-reference/)

### Advanced

* [ Sandbox Adapter API ](https://flueframework.com/docs/api/sandbox-api/)
* [ Data Persistence API ](https://flueframework.com/docs/api/data-persistence-api/)