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.
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.
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.
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.