Linear Integration
Receive and route Linear webhooks for issues, projects, cycles, and team events.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Linear Production",
"slug": "linear",
"provider": "linear",
"signingSecret": "your-linear-signing-secret"
}'Linear generates the Signing secret when you create the webhook (step 2), so it does not exist
yet the first time you reach this step. The ingest URL is predictable from your org and source slug,
so you can either create the Linear webhook first and then create the source with the secret, or
create the source now without signingSecret and add it afterwards:
curl -X PATCH https://api.hookbase.app/api/sources/src_... \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"signingSecret": "your-linear-signing-secret"}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/linear2. Configure Linear Webhook
- Go to Linear Settings → API → Webhooks
- Click New webhook
- Enter your Hookbase ingest URL
- Select the resource types you want to subscribe to
- Click Create webhook
- Open the webhook's detail page, copy the Signing secret, and paste it into your Hookbase
source's Signing Secret field (
signingSecretin the API)
3. Create Routes
# Create destination for your issue handler
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Issue Tracker", "slug": "issue-tracker", "url": "https://api.myapp.com/webhooks/linear"}'
# Create route
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-d '{"name": "Linear to Issue Tracker", "sourceId": "src_...", "destinationId": "dst_..."}'Signature Verification
Linear signs webhooks using HMAC-SHA256. The signature is sent in the Linear-Signature header as a hex-encoded digest of the raw request body.
Hookbase verifies this automatically once the source has both fields set:
{
"provider": "linear",
"signingSecret": "your-linear-signing-secret"
}The key is the webhook's Signing secret from its detail page in Linear — not a Linear API key.
Once events are arriving with signature_valid: true, set rejectInvalidSignatures: true on the
source to have unverified requests refused with 401 instead of stored.
Common Events
Issue Created
Triggered when a new issue is created.
{
"action": "create",
"type": "Issue",
"createdAt": "2026-03-07T12:00:00.000Z",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Fix login redirect on mobile",
"description": "Users on iOS Safari are not redirected after login.",
"priority": 2,
"priorityLabel": "High",
"state": {
"id": "state-id",
"name": "Todo",
"type": "unstarted"
},
"team": {
"id": "team-id",
"key": "ENG",
"name": "Engineering"
},
"assignee": {
"id": "user-id",
"name": "Jane Smith",
"email": "jane@example.com"
},
"labels": [
{ "id": "label-id", "name": "bug" }
],
"number": 423,
"identifier": "ENG-423",
"url": "https://linear.app/myteam/issue/ENG-423"
},
"url": "https://linear.app/myteam/issue/ENG-423",
"organizationId": "org-id"
}Issue Updated
Triggered when an issue is modified (status change, assignment, priority, etc).
{
"action": "update",
"type": "Issue",
"createdAt": "2026-03-07T12:05:00.000Z",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Fix login redirect on mobile",
"priority": 1,
"priorityLabel": "Urgent",
"state": {
"id": "state-id",
"name": "In Progress",
"type": "started"
},
"identifier": "ENG-423"
},
"updatedFrom": {
"priority": 2,
"priorityLabel": "High",
"updatedAt": "2026-03-07T12:00:00.000Z"
},
"organizationId": "org-id"
}Comment Created
Triggered when a comment is added to an issue.
{
"action": "create",
"type": "Comment",
"createdAt": "2026-03-07T12:10:00.000Z",
"data": {
"id": "comment-id",
"body": "Reproduced on iOS 17.3 Safari. Looks like the OAuth callback URL is missing the state parameter.",
"issue": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"identifier": "ENG-423",
"title": "Fix login redirect on mobile"
},
"user": {
"id": "user-id",
"name": "Jane Smith"
}
},
"organizationId": "org-id"
}Transform Examples
Slack Alert for Urgent Issues
function transform(payload) {
const issue = payload.data;
const isUrgent = issue.priority <= 1;
return {
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: isUrgent ? "Urgent Issue Created" : "New Issue Created"
}
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Issue:*\n<${issue.url}|${issue.identifier}: ${issue.title}>`
},
{
type: "mrkdwn",
text: `*Priority:*\n${issue.priorityLabel}`
},
{
type: "mrkdwn",
text: `*Team:*\n${issue.team.name}`
},
{
type: "mrkdwn",
text: `*Assignee:*\n${issue.assignee ? issue.assignee.name : 'Unassigned'}`
}
]
}
]
};
}Database Format
function transform(payload) {
const data = payload.data;
return {
linear_id: data.id,
action: payload.action,
resource_type: payload.type,
identifier: data.identifier,
title: data.title,
priority: data.priority,
state: data.state ? data.state.name : null,
team_key: data.team ? data.team.key : null,
assignee_email: data.assignee ? data.assignee.email : null,
labels: data.labels ? data.labels.map(l => l.name) : [],
url: data.url || payload.url,
created_at: payload.createdAt
};
}Filter Examples
Issue Events Only
{
"name": "Issues Only",
"logic": "AND",
"conditions": [
{
"field": "type",
"operator": "equals",
"value": "Issue"
}
]
}Urgent and High Priority
{
"name": "Urgent & High Priority",
"logic": "AND",
"conditions": [
{
"field": "type",
"operator": "equals",
"value": "Issue"
},
{
"field": "data.priority",
"operator": "less_than",
"value": "3"
}
]
}Status Changes to Done
{
"name": "Completed Issues",
"logic": "AND",
"conditions": [
{
"field": "action",
"operator": "equals",
"value": "update"
},
{
"field": "data.state.type",
"operator": "equals",
"value": "completed"
}
]
}Headers
Linear webhooks include these headers:
| Header | Description |
|---|---|
Linear-Signature | HMAC-SHA256 signature (hex) |
Linear-Event | Resource type (e.g., Issue) |
Linear-Delivery | Unique delivery ID (UUID) — Hookbase dedups on this |
Linear-Timestamp | Send time, Unix milliseconds (not signed; Hookbase checks the signed webhookTimestamp body field instead) |
User-Agent | Linear-Webhook |
Content-Type | application/json |
Hookbase does not forward these to your destination. A delivery is a fresh request with
Hookbase's own X-Delivery-ID and X-Event-ID plus the headers and auth you configured on the
destination — nothing Linear sent reaches your handler as a header. See
what headers a delivery carries.
Troubleshooting
Signature Verification Failed
- Confirm the source's
providerislinear— a source left oncustomreadsX-Signature,X-Webhook-SignatureandX-Hub-Signature-256, and Linear sendsLinear-Signature - Confirm
signingSecretis the webhook's Signing secret, not a Linear API key - Ensure the secret matches the one shown on the webhook's detail page in Linear — each webhook has its own
- Check the body's
webhookTimestamp: Hookbase rejects a delivery whose signed timestamp is more than 60 seconds from its clock, as Linear recommends, so a captured request can't be replayed later. Linear's own retries are re-signed with a fresh timestamp, so they pass
Missing Events
- Check which resource types are selected in Linear's webhook settings
- Verify filters aren't blocking expected events
- Check Linear's webhook delivery log for failed attempts
Duplicate Events
- Linear retries failed deliveries; every retry carries the same
Linear-DeliveryID, which is what Hookbase'sautostrategy keys on when deduplication is enabled - Use
data.id+actionas an idempotency key in your handler - Check for multiple webhooks configured for the same workspace