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
- A Linear workspace where you're an admin (needed to manage webhooks).
- A local service listening on a port, e.g.
localhost:3000. - The
relayersCLI installed, or the Relayers desktop app. Both are on the free tier. - A Relayers account — sign up at relayers.app.
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:
- URL — paste your Relayers ingest URL from step 1.
- Resource types — select what you care about. For this walkthrough, enable Issues. You can also subscribe to Comments, Projects, and more.
- Events — pick create/update/remove as needed.
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:
Linear-Signature— an HMAC-SHA256 of the raw request body, keyed with your signing secret.Linear-Event— the resource type that triggered the delivery (e.g.Issue).
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
- Signature verification fails. Make sure you're hashing the raw request body, not a re-serialized JSON object, and that you're using the signing secret from this webhook (each Linear webhook has its own).
- No events arriving. Confirm the right resource types are selected in Linear — a webhook subscribed only to Projects won't fire on issues. Check the Events view in Relayers; if events show up there but not locally, your tunnel isn't running or is pointed at the wrong port.
- Requests rejected before your handler. If you configured an IP allowlist on the endpoint, Linear's egress IPs must be permitted. For local dev, leaving the allowlist empty is simplest.
- Wrong events forwarded. Re-check rule order — first match wins. A broad rule sitting above your
Issue/createrule will swallow the traffic.
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.