← All guides
LinearJuly 12, 20265 min read

How to Test Linear Webhooks on Localhost

Receive a Linear webhook localhost first, route and transform it with JQ, and forward events to your dev server with signature verification intact.

You wired up a Linear webhook to fire on every new issue, and now you need to see the payload land in your handler. But Linear can only POST to a public HTTPS URL, and your handler is running on localhost:3000. So you push a placeholder to staging, tail the logs, refresh, and repeat. Every iteration is a deploy. Every typo in your JQ path is another round trip.

There's a faster loop. Point Linear at a public Relayers endpoint, tunnel the traffic straight to your machine, and iterate against real payloads with your debugger attached — no deploys, no ngrok tab to babysit.

What you'll build

The original body and headers — including Linear-Signature — arrive untouched, so your HMAC verification passes exactly as it would in production.

Prerequisites

1Create an endpoint in Relayers

An endpoint is the public URL Linear will POST to. In the desktop app open Endpoints, click Create, give it a name like linear-dev, and copy the generated ingest URL. It looks like:

https://api.relayers.app/v1/webhooks/wep_8f3a2c9b

Prefer the CLI? Log in and the endpoint is available from the same dashboard:

relayers login

Keep that URL handy — Linear needs it next. While you're here, hit Send test to confirm the endpoint accepts traffic before Linear is even involved.

2Add the webhook in Linear

In Linear, go to Settings → API → Webhooks → New webhook. Fill in:

Linear generates a signing secret on the webhook — copy it; you'll use it to verify requests in your handler. Every delivery from Linear carries two headers worth knowing:

A typical Issue payload looks like this:

{
  "action": "create",
  "type": "Issue",
  "data": {
    "id": "a1b2c3d4-...",
    "identifier": "ENG-142",
    "title": "Checkout button misaligned on mobile",
    "state": { "name": "Todo" }
  }
}

Save the webhook. Linear will start delivering to Relayers immediately — create a test issue to generate one.

3Forward events to localhost

Now bridge Relayers to your machine. Start the tunnel and point it at your local port:

relayers listen --forward localhost:3000

Create an issue in Linear (or use Send test in the app). The event flows Linear → Relayers → tunnel → your handler in real time. Every request also shows up in the Events view with a full payload inspector, so you can read exactly what arrived, replay it, and retry deliveries while you debug.

4Route and filter with JQ

You probably don't want every Linear event hitting your dev handler — updates, removals, comments, and project changes all arrive on the same endpoint. Relayers Rules solve this with JQ filters.

Open Rules on your endpoint, add a rule, and set the destination to the tunnel (localhost:3000). Then filter to only newly created issues:

{ "type": "Issue", "action": "create" }

Rules are evaluated top to bottom and first match wins, so you can stack more specific rules above catch-alls and reorder them by dragging. Anything that doesn't match a rule is simply not forwarded.

5Transform the payload

Your handler rarely needs Linear's full envelope. Use a Transformation to reshape the payload into exactly what your code expects. Relayers supports jq, jsonata, javascript, and go_template, each with a test playground where you paste a sample and see the output instantly.

A JQ transform to flatten an Issue event:

{
  action: .action,
  issue: .data.identifier,
  title: .data.title,
  state: .data.state.name
}

Given the sample above, your handler now receives:

{
  "action": "create",
  "issue": "ENG-142",
  "title": "Checkout button misaligned on mobile",
  "state": "Todo"
}

A note on signatures

Transformations change the JSON your handler sees, but Relayers forwards the original body and headers untouched on delivery. That means Linear-Signature still matches the raw bytes Linear signed, so your HMAC-SHA256 verification works locally with the same code you'll ship. Compute the HMAC of the raw body using your webhook signing secret and compare it to the header — no special-casing for local development.

Going to production

When your handler is ready, swap the destination without touching Linear. In Rules, change the destination from the tunnel to your public production URL (e.g. https://api.yourapp.com/webhooks/linear). Linear keeps posting to the same Relayers endpoint; only the downstream target changes.

To keep environments in sync, use Export to save your endpoint, rules, and transformations as a JSON bundle, then Import it into another workspace or check it into your repo alongside the code it feeds.

Troubleshooting

Wrap-up

Instead of deploying to see a Linear webhook, you now receive it on a public Relayers endpoint, filter it down to the events you care about with JQ, reshape it in a test playground, and forward it straight to localhost:3000 — with Linear-Signature intact the whole way. Real payloads, real breakpoints, zero round trips.

Ready to close the loop? Create your endpoint at relayers.app and download the app to start tunneling in minutes.