TheLunarEngine
Journal

Automation7 min read

From a Jira transition to an automated workflow

Webhooks in, a durable queue, idempotent workers, retries with backoff, and an audit trail: the pattern behind most of the automation we ship.

  • Jira
  • Node.js
  • PostgreSQL
  • Python

A legal request moves to “Ready for review” in Jira. Within a minute the attached contract has been read, the case record updated, the reviewer notified, and the issue commented with what was found. This is the pattern behind most of the automation we ship, and the parts that make it trustworthy are the ones nobody sees.

Event-driven Jira automationJira sends a webhook when an issue is transitioned. The Node.js webhook endpoint verifies the signature, stores the event in a PostgreSQL queue table, and answers 202 Accepted at once. A Node.js worker claims the event, asks a Python document service to extract fields, receives structured data, writes the case update and an audit entry to PostgreSQL, and finally comments on the Jira issue and moves it to the next status through the Jira REST API.JiraWebhookEvent queueWorkerDocumentsissue transitionedverify signature, store event202 Acceptedclaim next eventextract fieldsstructured datacase update + audit entrycomment, next transition (REST)
Accept fast, work durably: the webhook only verifies and stores the event; a worker does the real work, and can retry it.

Accept fast, then work

Jira expects a quick answer to its webhooks. Our Node.js endpoint does three things only: verify the request is genuine, store the event, and reply 202 Accepted. Anything slow (reading documents, calling other systems) happens later, in a worker.

adapters/http/jira-webhook.ts
router.post("/hooks/jira", async (req, res) => {
  if (!verifySignature(req.rawBody, req.header("X-Hub-Signature"), env.JIRA_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  const event = JiraEvent.parse(req.body);

  // The event ID is unique: a redelivered webhook is stored once.
  await events.insertIfAbsent({
    id: `jira:${event.webhookEvent}:${event.issue.id}:${event.timestamp}`,
    type: "issue.transitioned",
    payload: event,
  });
  res.status(202).end();
});

A queue you can query

For most workflow volumes, the queue is a table in the PostgreSQL database the system already uses. Workers claim events with FOR UPDATE SKIP LOCKED, so several can run side by side without taking the same one. Pending, failed, and finished events are rows you can read, count, and replay.

queue/claim.sql
UPDATE events SET status = 'running', attempts = attempts + 1, claimed_at = now()
WHERE id = (
  SELECT id FROM events
  WHERE status = 'pending' AND run_after <= now()
  ORDER BY created_at
  FOR UPDATE SKIP LOCKED
  LIMIT 1
)
RETURNING id, type, payload, attempts;

Idempotent workers

Every step may run twice: after a timeout, a deploy, or a redelivered webhook. So each step checks before it acts (“is there already a comment for this event?”), writes are keyed by the event ID, and the case update and its audit entry commit in one transaction.

Retries with backoff, and a place for failures

Temporary failures (a rate limit, a network blip) go back to the queue with a growing delay. After a set number of attempts the event is marked failed, an alert goes out, and the event waits, intact, to be replayed once the cause is fixed. Nothing disappears silently.

The right language for each step

The orchestration is Node.js and TypeScript. Document work (extracting fields from a PDF, checking a signature page) is a small Python service behind its own port, because that is where the best libraries for it are. The worker does not care which language answers; it cares about the contract.

Closing the loop in Jira

Finally the worker writes back through the Jira REST API: a comment with what was found and, if the rules allow, the next transition. The tracker stays the place where people see status, and the automation never becomes a black box. The Jira side is just another adapter (see hexagonal architecture).

Want this in your systems?

Tell us about the workflow; we reply with questions or a proposal for a discovery call.

Discuss a project

More from the journal