Ticket Sync¶
Path: Evidence request detail (the Ticket Sync section)
Ticket Sync gives every evidence request an AuditBoard-style two-way link to an external ticketing system. From the evidence request you can open a ticket in your tracker, close it, and hold a single comment thread that stays in sync in both directions: an internal comment is pushed out to the ticket, and an external reply posted on the ticket flows back in as an inbound webhook and appears in the same thread. The provider is pluggable — the demo runs a self-contained mock; for real deployments a generic HMAC-signed webhook and a real Jira provider (Jira Cloud / Server-DC, REST v3) sit behind the same interface. ServiceNow is not yet supported (see below).
Prerequisites — a real provider is buyer-configured
Two-way ticket sync against a real tracker is not turnkey out of the
box. It needs a provider that you configure. A stock Community Edition
install does not have Jira/webhook credentials set, so the only
out-of-the-box behavior is the mock provider (a self-contained
demonstration that mints a synthetic mock-TCK-<n> and echoes a reply — it
contacts no external system). The demo's working ticket flow is the mock,
not a live integration.
To enable a real integration, set TICKET_PROVIDER and the matching
credentials:
webhook—TICKET_WEBHOOK_URL+TICKET_WEBHOOK_SECRETjira—JIRA_BASE_URL+JIRA_API_TOKEN+JIRA_PROJECT_KEY(plusJIRA_USER_EMAILfor Jira Cloud Basic auth; leave it empty to send the token as a Bearer PAT on Jira Server / Data Center)
servicenow is not yet supported — selecting it always returns 503,
even with SERVICENOW_* set (those settings are reserved for a future
implementation).
Without credentials, selecting a real provider leaves it gated: every
ticket operation raises ProviderNotConfigured, surfaced as HTTP 503
naming the keys to set (the inbound webhook similarly stays closed without
TICKET_WEBHOOK_SECRET). The in-app Setup readiness checklist flags
Ticket Sync accordingly. Full key list in Configuration
below.
Key Elements¶
- Ticket Sync section — On the evidence request detail modal. Shows the linked ticket (provider, external id, deep link, sync state) and the comment thread.
- Comment thread — Interleaves internal comments (left in DefendFlow) and external comments (arrived from the ticket), each labelled by source.
- Provider — The org-configured ticketing backend. The demo default is the mock provider; real deployments point at a webhook or Jira.
Creating and Closing a Ticket¶
A ticket is created from one evidence request, using the org-configured provider:
POST /api/v1/orgs/{org_id}/evidence-requests/{request_id}/ticket # create + link
GET /api/v1/orgs/{org_id}/evidence-requests/{request_id}/ticket # read the link
POST /api/v1/orgs/{org_id}/evidence-requests/{request_id}/ticket/close # close it
DELETE /api/v1/orgs/{org_id}/evidence-requests/{request_id}/ticket # unlink it
Create takes no body — the provider derives the ticket title/description from the evidence
request. The response is the ticket link: its provider, external_id, the
external_url deep link, the sync_state (synced / pending / error), and timestamps.
- Open the evidence request and find the Ticket Sync section.
- Create ticket — the provider mints the external ticket and the link appears with a deep link to it.
- Close when the work is done, or Unlink to detach without closing.
An evidence request holds one ticket link at a time; creating a second returns
409 Conflict.
Two-Way Comment Sync¶
The comment thread is the heart of the integration. Both endpoints live under the evidence request:
GET /api/v1/orgs/{org_id}/evidence-requests/{request_id}/comments # the thread
POST /api/v1/orgs/{org_id}/evidence-requests/{request_id}/comments # add an internal comment
Outbound — Posting a comment (body: { "body": "..." }) records it as an
internal comment. If a ticket is linked, the same comment is pushed to the provider, and
the provider's external comment id is stamped onto the internal comment for de-duplication.
With the mock provider the call also returns an echo: a synthetic external reply, so
you can see the round trip on the demo without any external system.
Inbound — When someone comments on the real ticket, the provider calls back:
The handler resolves the event to a request via the external_id of a known ticket link
(the wire never supplies a raw org or request id), then records the external comment as a
external comment in the same thread and can apply an optional PBC status update. Inbound
comments are de-duplicated on external_comment_id, so a redelivered webhook does not
double-post.
Provider Matrix¶
The active provider is chosen by the ticket_provider setting; an unknown value falls back
to the mock provider.
| Provider | Value | Behavior | Use when |
|---|---|---|---|
| Mock | mock |
Mints a synthetic mock-TCK-<n> ticket and echoes an external reply when you comment, so the two-way flow is visible end-to-end. Contacts no external system. |
The default on the demo and any offline environment. |
| Webhook | webhook |
Generic provider: outbound events are HMAC-signed POSTs to your endpoint; inbound replies arrive via the inbound webhook. |
You want to integrate any tracker via a small adapter you host. |
| Jira | jira |
Real Jira integration (REST API v3): creates issues in your project, transitions them to a Done status on close, and posts comments. | You run Jira Cloud (email + API token) or Jira Server/DC (PAT). |
| ServiceNow | servicenow |
Not yet supported. Always returns 503 with an honest "not yet supported" message — even when SERVICENOW_* credentials are set. |
Don't — use jira, or bridge ServiceNow yourself via the webhook provider. |
Demo default is Mock
The demo runs the mock provider. Creating a ticket returns a synthetic
mock-TCK-<n> id, and posting a comment immediately echoes an external reply into
the thread — so the round-trip sync is demonstrable with no external tracker wired up.
Jira is configure-your-own; ServiceNow is not implemented
The Jira provider is real but not active on the demo: until
JIRA_BASE_URL, JIRA_API_TOKEN and JIRA_PROJECT_KEY are set, every ticket
operation raises ProviderNotConfigured, surfaced as HTTP 503 with a message
naming the keys to set. If Jira itself fails or rejects a call, the endpoints return
HTTP 502 with the upstream error rather than fabricating success.
ServiceNow has no working implementation in this build. Selecting it always returns 503; setting its credentials does not change that.
The Jira Provider¶
With TICKET_PROVIDER=jira configured, the three outbound operations call the Jira
REST API v3:
- Create —
POST /rest/api/3/issueinJIRA_PROJECT_KEYwith issue typeJIRA_ISSUE_TYPE(defaultTask). The evidence-request title becomes the summary; the description carries the request description plus control / framework / due-date / owner context. The returned issue key (e.g.GRC-42) is theexternal_idand the deep link is<base>/browse/<key>. The due date is recorded in the description text only — theduedatefield is not set because many Jira create screens do not include it (setting it would fail the whole create). - Close — reads the issue's available transitions and applies the first one whose
target status category is Done (falling back to well-known names such as
Done/Closed/Resolved). If the workflow has no such transition, the close fails with an explicit 502 naming the transitions that do exist. - Comment —
POST /rest/api/3/issue/{key}/comment; the Jira comment id is stamped onto the internal comment for de-duplication.
Authentication: with JIRA_USER_EMAIL set, the token is sent as Basic auth
(email:api_token — Jira Cloud). With it empty, the token is sent as a Bearer
personal access token (Jira Server / Data Center).
Inbound replies from Jira are not polled: point a Jira automation rule / outgoing
webhook at POST /api/v1/webhooks/tickets/inbound using the shared
TICKET_WEBHOOK_SECRET HMAC scheme described below.
The Generic Webhook Provider¶
The webhook provider lets you integrate any tracker without a bespoke backend. Outbound
ticket events (ticket.create, ticket.close, ticket.comment) are sent as JSON POSTs
to your ticket_webhook_url, and your system sends external comments and status changes
back to the inbound webhook.
HMAC signature scheme¶
Both directions are authenticated with an HMAC over the raw request body:
- Header:
X-Signature - Algorithm: HMAC-SHA256, formatted as
sha256=<hex digest> - Signed value: the exact raw body bytes (outbound events serialize the JSON deterministically — compact separators, sorted keys — so the signature is stable)
- Secret: the shared
ticket_webhook_secret
Outbound, the provider sets X-Signature on every POST it sends you. Inbound, the
webhook handler reads X-Signature, recomputes the expected signature over the raw body,
and compares it in constant time. A missing or mismatched signature is rejected with
401. If no shared secret is configured, inbound sync is closed by default and the
endpoint returns 503.
Inbound payload¶
The inbound webhook body is a JSON object keyed by the ticket's external_id:
{
"external_id": "mock-TCK-1",
"comment": {
"external_comment_id": "ext-42",
"author": "Jane (ext)",
"body": "Attached the SOC 2 report."
},
"status": "submitted"
}
external_id is required and resolves the event to a linked ticket. The optional comment
becomes an external comment (de-duplicated on external_comment_id), and the optional
status updates the PBC request. The response is { "success": true, "applied": ... }.
Configuration¶
All keys are environment-driven settings:
| Key | Purpose |
|---|---|
TICKET_PROVIDER |
Active provider: mock (default), webhook, or jira (servicenow is accepted but not yet supported — always 503) |
TICKET_WEBHOOK_URL |
Outbound endpoint for the generic webhook provider |
TICKET_WEBHOOK_SECRET |
Shared HMAC secret for outbound signing and inbound verification |
JIRA_BASE_URL |
Jira instance base URL, e.g. https://your-org.atlassian.net |
JIRA_API_TOKEN |
Jira API token (Cloud) or personal access token (Server/DC) |
JIRA_USER_EMAIL |
Atlassian account email for Jira Cloud Basic auth; leave empty on Server/DC (token sent as Bearer) |
JIRA_PROJECT_KEY |
Project key tickets are created in, e.g. GRC |
JIRA_ISSUE_TYPE |
Issue type name for created tickets (default Task) |
SERVICENOW_BASE_URL |
Reserved — ServiceNow sync is not yet supported; setting this activates nothing |
SERVICENOW_API_TOKEN |
Reserved — ServiceNow sync is not yet supported; setting this activates nothing |
A provider is considered configured only when its credentials are present; until then it
stays gated and any ticket operation returns 503. The inbound webhook is similarly gated on
TICKET_WEBHOOK_SECRET — without it, inbound sync stays closed.
Who Can Do What¶
| Action | Required permission |
|---|---|
| View the ticket link and comment thread | Org read |
| Add a comment | Org read |
| Create / close / unlink a ticket | Org configure |
| Inbound webhook | None — authorized by the HMAC X-Signature, not an org login |
Reading the thread and leaving a comment need only read access, so anyone working the evidence request can participate. Creating, closing, or unlinking the ticket requires the org configure permission. The inbound webhook is a separate, unauthenticated-by-login path: it is authorized solely by the HMAC signature over the raw body.