Skip to content

Webhooks

Jimothy can run a small HTTP server that receives webhooks. It serves two purposes:

  1. Webhook triggers: a generic trigger type any system can POST JSON to. Each request starts a run.
  2. Instant starts for Linear, Jira and GitHub triggers: those triggers poll, but they also accept their platform’s native webhooks at their own URL, so runs start within seconds.

The server is off by default. Turn it on in Settings → Webhook server:

Field Default Notes
Receive trigger webhooks (Linear, Jira, GitHub, custom) for instant runs off
Listen address 127.0.0.1 127.0.0.1 = this machine only (use a tunnel); 0.0.0.0 = your network.
Port 7717

Click Save. When it’s running, Settings shows Listening on http://127.0.0.1:7717/hooks/<trigger-id>. If the port is taken, you get a Trigger errors notification: Webhook server failed to start — Port 7717: ….

Method Path Purpose
GET /health Returns {"ok": true}. Use it to check a tunnel.
POST /hooks/<trigger-id> Deliver an event to one trigger. A trailing slash is allowed.

Everything else returns 404 {"error":"not found"}. Request bodies over about 2 MB are dropped.

The trigger id (trg_…) is shown in the trigger editor’s Instant webhook box once the trigger is saved. That box shows the full URL with a copy button.

Open Triggers → Add trigger → Webhook. The editor has Name, Runs pipeline and Webhook secret (prefilled with a random value). Save, then copy the URL.

POST a JSON object. Every field is optional except a title:

{
"title": "Add rate limiting to the public API",
"description": "Limit /v1/* to 100 req/min per key. Return 429 with Retry-After.",
"key": "OPS-981",
"url": "https://tickets.acme.dev/OPS-981",
"labels": ["api", "security"],
"priority": "high"
}
Field Used as Fallbacks
title {{issue.title}} summary, then the ?title= query parameter. If there’s no title, the request is ignored (HTTP 202).
description {{issue.description}} body, else empty.
key {{issue.key}} (and the dedup key) id, else a generated HOOK-<timestamp> key.
url {{issue.url}} —
labels {{issue.labels}} Must be an array.
priority {{issue.priority}} —
the whole body {{issue.raw.*}} Any extra fields you send, e.g. {{issue.raw.environment}}.

If the body isn’t valid JSON, its first 200 characters become the title and the whole body the description.

{{issue.source}} is webhook.

With a Webhook secret set, every request must present it in one of these ways:

  • ?token=<secret> in the URL (the copied URL already includes it);
  • an X-Factory-Token: <secret> header;
  • Authorization: Bearer <secret>.

Clear the secret to accept unauthenticated requests (not recommended once the server is reachable from outside).

Terminal window
# Token in the query string (as copied from the trigger editor)
curl -X POST "http://127.0.0.1:7717/hooks/trg_0abc123?token=s3cr3t" \
-H 'content-type: application/json' \
-d '{"title":"Bump lodash to 4.17.21","key":"DEP-1","labels":["deps"]}'
# Token as a header
curl -X POST http://127.0.0.1:7717/hooks/trg_0abc123 \
-H 'X-Factory-Token: s3cr3t' -H 'content-type: application/json' \
-d '{"title":"Investigate 500s on /checkout","description":"Sentry issue 4411"}'

Webhook runs are deduplicated by key per trigger, like tracker issues: a second request with the same key doesn’t start another run. Omit key to start a run for every request (each one gets a fresh HOOK-… key). See Polling & dedup.

Generic webhook triggers don’t write back anywhere; use a shell step or a notification channel to report results.

Provider webhooks for Linear, Jira and GitHub

Section titled “Provider webhooks for Linear, Jira and GitHub”

A Linear, Jira or GitHub trigger’s editor shows Instant webhook (optional — in addition to polling) with the trigger’s URL and a Webhook secret field. Point the platform’s webhook at that URL and the trigger starts runs as soon as events arrive. Polling keeps running as a safety net.

Trigger Verification Secret field
Linear Linear-Signature header: hex HMAC-SHA256 of the raw body Linear webhook signing secret
GitHub X-Hub-Signature-256 header: sha256= + hex HMAC-SHA256 the secret you entered on GitHub
Jira shared token: ?token=, X-Factory-Token or Bearer (the copied URL includes ?token=) any random string

Signatures are compared in constant time. If no secret is set, requests aren’t verified.

How each platform’s events are filtered is described on its page: Linear, Jira, GitHub. In short, webhook events are checked against some but not all of the trigger’s filters, and Jira webhook events aren’t checked against the JQL.

Status Body Meaning
201 {"issue": "OPS-981", "created": true} A run was started.
200 {"issue": "OPS-981", "created": false} Accepted but no run (already processed, or not re-triggered).
202 {"ignored": true} Not an event this trigger handles (no title, a filtered-out label, a pull request, …).
400 {"error": "…"} The provider payload couldn’t be parsed.
401 {"error": "invalid signature"} Wrong or missing secret / signature.
404 {"error": "unknown trigger"} No trigger with that id.
409 {"error": "trigger disabled"} The trigger is switched off.

Linear, GitHub and Jira Cloud can’t reach 127.0.0.1. Keep Listen address on 127.0.0.1 and put a tunnel in front of it. Then use the tunnel’s public URL plus /hooks/<trigger-id> (and ?token=… where needed) as the webhook URL.

Terminal window
ngrok http 7717
# Forwarding https://ab12-34-56.ngrok-free.app -> http://localhost:7717

Webhook URL: https://ab12-34-56.ngrok-free.app/hooks/trg_0abc123. Free ngrok URLs change on every restart unless you reserve a domain, so you’d have to update the webhooks each time.