Callbacks

Scoring is asynchronous. When an evaluation completes, Vindex POSTs the full score record to the callback URL you registered so you can store it, render it in your own UI, or drop it back into Jira.

When callbacks fire

A callback is delivered once per successfully completed evaluation. Evaluations that fail at the model layer are not delivered to your webhook — only completed scores are posted.

The target URL is resolved per request from the webhook field on the evaluate request body (a plain string). If that is absent, Vindex falls back to the callback_urlyou registered for your integration. For connector integrations (Jira Forge, Asana, Azure DevOps) the URL is injected automatically from your installation record — you never supply it.

Payload

POST(your webhook URL)

The body is a compact score summary. The six INVEST dimensions are integers under dimensions, and per-dimension reasoning is included under dimensionDetails only when the model returned it.

POST {your_webhook_url}
payload
{
  "issueKey": "PROJ-101",
  "overallScore": 3.7,
  "recommendations": [
    "Define the export scope precisely: event types, fields, timezone, range.",
    "Complete acceptance criteria with concrete Given/When/Then scenarios."
  ],
  "dimensions": {
    "independent": 4,
    "negotiable": 5,
    "valuable": 6,
    "estimable": 2,
    "small": 3,
    "testable": 2
  },
  "dimensionDetails": {
    "estimable": {
      "reasoning": "Key scope drivers are missing (fields, date ranges, volume, format).",
      "improvementSuggestions": [
        "Specify the activity schema/fields to export.",
        "Define constraints: max rows, time range, async processing."
      ],
      "referenceContext": ["PROJ-101-description", "PROJ-101-acceptance-criteria"]
    }
    // ... one entry per dimension that has reasoning available
  }
}
  • overallScore is the aggregate out of 10; dimensionsvalues are integers 0–10. These are the stored values — user-facing surfaces always display them as whole percentages (a stored 7 displays as 70%).
  • dimensionDetails is optional and only includes dimensions for which the model produced reasoning. Each entry may contain reasoning, improvementSuggestions, and referenceContext.
  • • Connector integrations receive source-specific shapes — e.g. Asana wraps the score under data.evaluation, and Jira receives a formatted comment rather than this JSON. The shape above is the default (ADAM / direct webhook) and Jira Forge payload.

Verifying the caller

Callbacks are authenticated with a shared secret sent as a request header — there is no HMAC body signature. Compare the header against the secret Vindex issued you and reject any request that does not match before trusting the payload.

Request headers (direct / ADAM webhook)
headers
Content-Type: application/json
x-api-key: <shared secret Vindex issued you>

Jira Forge callbacks instead carry x-vindex-secret (your per-installation secret). No X-Vindex-Signature, X-Vindex-Timestamp, or job-id header is sent.

Your response and retry behaviour

  • Return any 2xx to acknowledge delivery. Each attempt uses a 10-second request timeout.
  • Any non-2xx response, a timeout, or a network error is retried — there is no special handling that distinguishes 4xx from 5xx.
  • Delivery is attempted up to 3 times with a backoff of 5s then 15s between attempts (backoff schedule 5, 15, 45 seconds). After the final attempt the score is still stored; only the callback delivery is abandoned.
  • The delivery outcome (status, HTTP status code, retry count, response time, error) is recorded on the stored score record. It is not sent back to your webhook.

Idempotency

Retries re-POST the identical body, and the payload does not include a delivery id or job_id. Dedupe on issueKey (plus your own received-at timestamp) and make your handler idempotent: if you have already applied a score for that ticket, respond 200 OK without re-running your side effects.

Continue reading