Skip to content

Jira

Jira works with pg_relay through the webhook transport, posting to Jira's REST create-issue endpoint — the same zero-Processor-code pattern as Slack. This chapter is about creating issues (tickets, tasks, bugs) in Jira Software or Jira Service Management projects; paging through JSM's Operations alerting is a different API with its own recipe in Incident Alerting Platforms.

The headline for a ticketing integration: the created issue's key (OPS-123) comes back in the response body, classify_webhook_response returns it as the provider reference, and it lands on the notification's status row via set_status — so the application that queued the event can read the ticket key back, correlated by the notifier pk. The reference is asynchronous by nature: pgrelay.notify() returns the queue row id immediately; the ticket key appears on the status row after the Processor's tick and the HTTP round trip.

One-time Jira setup

  1. Create (or pick) a service account for the integration and, signed in as it, create an API token at id.atlassian.com → Security → API tokens. Jira Cloud's Basic authentication is the account's email plus this token — never the account password.
  2. Note the project key (the prefix in issue keys, e.g. OPS) and the exact issue type names available in that project (Task, Bug, Incident, …) — the create call names both.
  3. If tickets should be assigned on creation, collect the assignees' account IDs. Jira Cloud identifies people only by opaque accountId (a GDPR-driven change) — an email address or username in the assignee field is rejected.

Export the token on the Processor host (for a service, in its environment file — see Keeping Secrets Out of the Database):

export JIRA_API_TOKEN=ATATT3xFfGF0...

Profile and message

The example uses REST API v2, which accepts the description as a plain string. v3 is identical except the description must be an Atlassian Document Format (ADF) object — see v2 or v3 below.

// profile
{
  "url": "https://your-site.atlassian.net/rest/api/2/issue",
  "auth": {"style": "basic_auth", "username": "[email protected]", "secret": "_env:JIRA_API_TOKEN"},
  "timeout_seconds": 30
}

// message — Jira's own create-issue shape, sent verbatim
{
  "fields": {
    "project": {"key": "OPS"},
    "issuetype": {"name": "Task"},
    "summary": "disk alert: volume is filling",
    "description": "relay_dev tablespace at 91% and growing.",
    "labels": ["pgrelay-33"],
    "priority": {"name": "High"}
  }
}

Because message is sent verbatim, anything the create-issue endpoint accepts works. Everything lives under the one top-level fields object:

Ticket concept message field Format and notes
Project fields.project {"key": "OPS"} — the project key
Issue type fields.issuetype {"name": "Task"} — must exist in that project's issue type scheme
Title fields.summary Plain text, one line, max 255 characters
Body fields.description v2: a plain string (wiki markup allowed); v3: an ADF document object
Priority fields.priority {"name": "High"} or {"id": "2"} — names from your priority scheme
Labels fields.labels Array of strings; a label cannot contain spaces — the natural home for the pgrelay-{pk} dedup marker
Assignee fields.assignee Cloud: {"id": "<accountId>"} — never an email or username; Data Center: {"name": "<username>"}
Components fields.components [{"name": "database"}] — must already exist in the project
Due date fields.duedate "YYYY-MM-DD"
Custom fields fields.customfield_10xxx Numeric id from the field's settings page; the value's shape depends on the field type (text is a string, select is {"value": ...})

Fields not listed on the project's create screen are rejected with a 400 naming the field — Jira validates against the screen configuration, not just the field's existence.

Interpreting the response

  • 201 is success. The body is small and exact: {"id": "10000", "key": "OPS-123", "self": "https://..."} — return key as the provider reference; it is the human-facing ticket identifier everything downstream will want.
  • 400 as failed — validation. The body's errors object maps each offending field name to a message ("issuetype": "issue type is required") and its errorMessages array carries screen-level complaints; either makes ideal detail text.
  • 401 (bad email/token pair) and 403 (the account lacks Create Issue permission in that project) as failed.
  • 404 as failed — almost always a wrong site URL or a project key that doesn't exist.
  • 429, any 5xx, and http_status = 0 (no response) as retry.

Duplicate tickets

pg_relay's delivery guarantee is at-least-once: a crash between Jira creating the issue and pg_relay's own commit re-sends the event. Unlike the alerting platforms, whose ingestion APIs are designed idempotent, Jira's create-issue endpoint has no idempotency key — a retried send creates a second issue. The mitigation is the pgrelay-{pk} label in the example: it makes duplicates identifiable (labels = "pgrelay-33" in JQL finds both copies, and an automation rule or a periodic query can close the younger one) without being able to prevent them. If duplicate tickets are operationally unacceptable, front Jira with an automation rule keyed on that label, or accept the alerting-platform pattern instead.

v2 or v3: the description field

Jira Cloud serves both API versions today. The only difference that matters here is description (and other rich-text fields): v2 takes a string, v3 requires an ADF document:

"description": {
  "type": "doc", "version": 1,
  "content": [{"type": "paragraph", "content": [{"type": "text", "text": "relay_dev tablespace at 91%"}]}]
}

For machine-generated tickets the v2 string is simpler and renders fine; choose v3 only if the tickets need rich formatting worth the ADF ceremony. Everything else in this chapter — auth, field names, response shape, classification — is identical across both.

Jira Data Center / Server

Self-hosted Jira swaps two things: authentication is a personal access token sent as a Bearer token ("auth": {"style": "bearer_header", "secret": "_env:JIRA_PAT"} — Basic with username/password also still works), and people are identified by name (username) rather than accountId. The endpoint is https://jira.example.com/rest/api/2/issue — Data Center has no v3.

Documentation-verified, not live-verified

Endpoints, auth mechanics, and response shapes in this chapter come from Atlassian's current documentation, not from live sends through pg_relay. Create one real ticket in a test project before relying on a profile in production.


Continue to ServiceNow — the same create-a-ticket pattern against the incident table, with a genuine dedup field Jira lacks.