> ## Documentation Index
> Fetch the complete documentation index at: https://api.idunox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook: result.ready

> Outbound callback the platform sends to your registered HTTPS URL when a result is ready.

This is **not a route on the Idunox API**. It documents the `POST` request the platform sends to **your** registered webhook URL when a result becomes ready.

## Registering a webhook URL

Contact `partner@idunox.com` to register your HTTPS callback URL.

## Webhook headers

The platform sends the following headers on every delivery:

| Header                 | Description                                                       |
| ---------------------- | ----------------------------------------------------------------- |
| `X-Idunox-Event`       | Always `result.ready` for this event                              |
| `X-Idunox-Delivery-Id` | UUID for this delivery attempt (use for idempotency on your side) |
| `X-Idunox-Timestamp`   | Unix time in seconds (integer as decimal string)                  |
| `X-Idunox-Signature`   | HMAC-SHA256 signature formatted as `v1=<hex>`                     |

## Verifying the signature

Verify every delivery before processing:

1. Concatenate: `X-Idunox-Timestamp + "." + raw_request_body`
2. Compute HMAC-SHA256 over that string using your webhook signing secret.
3. Compare to the hex digest in `X-Idunox-Signature` (after the `v1=` prefix).
4. Reject deliveries where the timestamp is more than 5 minutes old.

```python theme={null}
import hmac, hashlib, time

def verify(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
  signed_payload = timestamp.encode() + b"." + body
  mac = hmac.new(secret.encode(), signed_payload, hashlib.sha256)
    expected = "v1=" + mac.hexdigest()
    return hmac.compare_digest(expected, signature)
```

## Responding to the webhook

Return any `2xx` status to acknowledge the delivery. Non-2xx responses and transport failures may be retried by the platform.

## Webhook body

The body identifies the completed result so you can retrieve it:

```json theme={null}
{
  "eventType": "result.ready",
  "resultId": "db2550ba-4c7f-4e7f-95d8-0a064f368f14",
  "submissionId": "8d70fed5-8f01-410d-87bd-8755725d7c6f",
  "partnerSubmissionId": "your-ref-001",
  "reportId": "8b37c3c5-a223-456b-9c96-ad2d0e2e2ce6",
  "status": "completed",
  "statusLabel": "Completed",
  "completedAt": "2026-04-27T00:49:32.389Z"
}
```

Use the `resultId` to call `GET /v1/results/{resultId}` and retrieve the full result.


## OpenAPI

````yaml POST /_reference/outbound-webhooks/result_ready
openapi: 3.1.0
info:
  title: IduScore Partner & Platform API
  description: >-
    Partner-facing JSON API for de-identified clinical submissions (canonical
    Type A, not FHIR at ingestion).


    **Authentication:** Send `X-Api-Key: <key>` **or** `Authorization: Bearer
    <key>` on every `/v1/*` request (except preflight `OPTIONS`).


    **Writes (`POST`):** Client MUST send non-empty `x-correlation-id` and
    `Idempotency-Key`. The server does not synthesize these from the request id.


    **Tenant scope:** Credentials resolve to a tenant; all IDs are tenant-scoped
    (cross-tenant access returns `404` where applicable).


    **Request id:** Responses may echo `x-request-id` when the client sent it;
    otherwise the server generates one (see error envelope `requestId`).


    **Partner responses:** Read APIs return **IduScore wellbeing** language only
    — stable `wellbeing.*` outcome ids,

    plain-English labels, and optional `notices[].message` lines (no internal
    warning codes or model identifiers).

    Use **`GET /openapi.yaml`** for request/response examples aligned with this
    contract.



    ---

    **Merged spec (this file):** includes **Platform** and **Diagnostics** paths
    from the service implementation in addition to the **Type A** `/v1` product
    routes. The slimmer `type-a-partner-v1.*` files are the same contract for
    submissions, jobs, and results.
  version: 0.1.0
  license:
    name: Proprietary
servers:
  - url: https://api.idunox.com
    description: Production partner API (documented base URL for examples and playground)
security: []
tags:
  - name: Submissions
    description: Ingestion and submission reads
  - name: Jobs
    description: Processing job status
  - name: Results
    description: Inference / scoring results
  - name: Webhooks
    description: >
      Outbound HTTPS callbacks the platform may POST to a URL you register (not
      paths on the IduScore Partner API;

      `/_reference/...` entries document payload and headers for your
      implementer only).
  - name: Platform
    description: >-
      Liveness, readiness, and OpenAPI document discovery. **No API key**
      required.
  - name: Diagnostics
    description: >-
      Validates the same **partner API key** and (for `POST`) write headers as
      other `/v1` routes. Returns `204` when valid.
paths:
  /_reference/outbound-webhooks/result_ready:
    post:
      tags:
        - Webhooks
      summary: result.ready (outbound to your registered HTTPS URL)
      description: >
        **This is not a route on IduScore.** It documents the `POST` the
        platform sends to **your** configured webhook

        callback URL when a result becomes ready. The path on your server is
        entirely under your control.


        **Request body:** `PartnerWebhookResultReadyBody` (JSON, UTF-8; exact
        bytes are included in the signature).


        **Headers:** IduScore sends `Content-Type: application/json;
        charset=utf-8` plus the `X-IduScore-*` headers below.

        Verify `X-IduScore-Signature` with your signing secret: HMAC-SHA256 over

        `X-IduScore-Timestamp + "." +` the raw request body (same string as on
        the wire). The header value is

        `v1=` followed by the hex digest. Optional per-endpoint static headers
        (not starting with `X-IduScore-`) may be merged in.


        **Response:** return any **2xx** to acknowledge; other statuses and
        transport failures may be retried per platform policy.
      operationId: referenceOutboundWebhookResultReady
      parameters:
        - name: X-IduScore-Event
          in: header
          required: true
          schema:
            type: string
            enum:
              - result.ready
          description: >-
            Always `result.ready` for this flow (duplicates `eventType` in the
            body for routing layers).
        - name: X-IduScore-Delivery-Id
          in: header
          required: true
          schema:
            type: string
            format: uuid
          description: Idempotency / support id for this delivery attempt.
        - name: X-IduScore-Timestamp
          in: header
          required: true
          schema:
            type: string
            pattern: ^[0-9]+$
          description: >-
            Unix time in seconds (integer as decimal string) used in the HMAC
            input.
        - name: X-IduScore-Signature
          in: header
          required: true
          schema:
            type: string
            pattern: ^v1=[0-9a-f]+$
          description: HMAC-SHA256 signature, formatted as `v1=<hex>`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PartnerWebhookResultReadyBody'
            example:
              completedAt: '2026-04-24T00:49:32.389Z'
              eventType: result.ready
              partnerSubmissionId: SYN-SUB-20260331-000001
              reportId: 8b37c3c5-a223-456b-9c96-ad2d0e2e2ce6
              resultId: db2550ba-4c7f-4e7f-95d8-0a064f368f14
              status: completed
              statusLabel: Completed
              submissionId: 8d70fed5-8f01-410d-87bd-8755725d7c6f
      responses:
        '200':
          description: Webhook received (any 2xx is treated as success)
        '400':
          description: >-
            Rejected or malformed handling on the receiver (non-2xx may be
            retried or logged per platform policy)
        '500':
          description: >-
            Transient or server-side failure on the receiver (non-2xx may be
            retried per platform policy)
        default:
          description: >
            Other non-success status codes; delivery may be retried or logged
            according to platform policy.
      security: []
components:
  schemas:
    PartnerWebhookResultReadyBody:
      description: >
        Outbound `result.ready` webhook JSON body (HTTPS POST). Minimal partner
        notification: correlate by ids and

        load full detail via GET result APIs. Does not include internal tenant
        UUIDs, job ids, model output, or

        correlation metadata (those remain in delivery records server-side).
        **Breaking change** from the prior

        envelope that included `inference`, `tenantId`, and `processingJobId`.

        Not an HTTP path on this API; documented here as the partner contract
        for callbacks.
      type: object
      additionalProperties: false
      required:
        - eventType
        - resultId
        - submissionId
        - partnerSubmissionId
        - status
        - statusLabel
        - completedAt
      properties:
        eventType:
          type: string
          enum:
            - result.ready
        resultId:
          type: string
          format: uuid
        submissionId:
          type: string
          format: uuid
        partnerSubmissionId:
          type:
            - string
            - 'null'
        status:
          $ref: '#/components/schemas/PartnerSubmissionPublicStatus'
        statusLabel:
          type: string
          minLength: 1
        completedAt:
          type: string
          format: date-time
        reportId:
          type: string
          format: uuid
          description: >
            Present when a report bundle artifact id is available on the result
            (prefers PDF, then JSON, then HTML).
      example:
        eventType: result.ready
        resultId: 10000000-0000-4000-8000-000000000001
        submissionId: e0000000-0000-4000-8000-000000000001
        partnerSubmissionId: partner-sub-owned
        status: completed
        statusLabel: Completed
        completedAt: '2026-04-12T16:00:00.000Z'
        reportId: 30000000-0000-4000-8000-000000000001
    PartnerSubmissionPublicStatus:
      type: string
      description: Stable public slug for submission pipeline state (partner JSON).
      enum:
        - received
        - validating
        - validated
        - processing
        - completed
        - failed
        - rejected

````