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

# CKG Ingestion Asynchronous Notifications

> Webhook endpoint for receiving a notification when a FHIR push (CKG data ingestion) job reaches a final state

Use this endpoint to implement an HTTP endpoint in your application that receives Webhook (A mechanism that allows Suki to send real-time event notifications to your application's server.) requests from Suki.
This endpoint should be hosted by your application to receive a notification when a FHIR bundle you uploaded with the [CKG Data Ingestion APIs](https://developer.suki.ai/patient-summary-api-reference/ckg-data-ingestion.md) finishes loading into the Clinical Knowledge Graph (CKG).

> [!NOTE]
>
> Host this endpoint on your server. Suki sends a **POST** request when ingestion reaches a final state. The payload has a top-level `state` of `COMPLETED`, `FAILED`, or `ABORTED`, not a `status`, and `additional_info.session_type` is always **`CKG_INGESTION`**. Match the notification to your upload with the `transaction_id` returned from [Request FHIR bundle upload URL](https://developer.suki.ai/patient-summary-api-reference/ckg-data-ingestion/upload.md). The payload has no `_links`.

Learn more about how webhooks work in [Notification webhook for partners](https://developer.suki.ai/documentation/webhook/overview.md). For every field in the payload, see [CKG ingestion webhook payload](https://developer.suki.ai/documentation/webhook/ckg-ingestion/payload.md). For what to do with each state, see [Handle CKG ingestion results](https://developer.suki.ai/documentation/webhook/ckg-ingestion/handle-results.md).

## Code examples

**Language tabs (agents):** Equivalent code samples are available in: Python, TypeScript. Humans see one language at a time. Use the variant that matches the user's stack; behavior is the same across tabs.

> [tab] Python
>
> ```python theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> import hashlib
> import hmac
> import json
>
> from flask import Flask, jsonify, request
>
> app = Flask(__name__)
> secret_key = "<partner_secret_key>"
>
>
> @app.route("/webhooks/notification", methods=["POST"])
> def handle_webhook():
>     raw_body = request.get_data(as_text=True)
>     timestamp = request.headers.get("generated-at", "")
>     expected_signature_hex = request.headers.get("X-API-Key", "")
>     computed_signature_hex = hmac.new(
>         secret_key.encode("utf-8"),
>         f"{timestamp}:{raw_body}".encode("utf-8"),
>         hashlib.sha256,
>     ).hexdigest()
>
>     if not expected_signature_hex or not hmac.compare_digest(
>         computed_signature_hex, expected_signature_hex
>     ):
>         return jsonify({"error": "Invalid signature"}), 401
>
>     try:
>         data = json.loads(raw_body)
>     except json.JSONDecodeError:
>         return jsonify({"error": "Invalid request"}), 400
>
>     state = data.get("state")
>     if state in ("COMPLETED", "FAILED", "ABORTED"):
>         print(f"CKG ingestion {data.get('transaction_id')} {state}")
>         error = data.get("error") or {}
>         if error:
>             print(
>                 f"  Error: {error.get('code')} - {error.get('message')} "
>                 f"(retryable: {error.get('retryable')})"
>             )
>         return jsonify({"message": "CKG notification received"}), 200
>
>     return jsonify({"message": "Ignored"}), 200
>
>
> if __name__ == "__main__":
>     app.run(port=3000)
> ```

> [tab] TypeScript
>
> ```typescript theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> import crypto from "crypto";
> import express from "express";
>
> const app = express();
> const secretKey = "<partner_secret_key>";
>
> app.post("/webhooks/notification", express.raw({ type: "application/json" }), (req, res) => {
>   const rawBody = Buffer.isBuffer(req.body) ? req.body.toString("utf8") : String(req.body ?? "");
>   const timestamp = String(req.headers["generated-at"] ?? "");
>   const expectedSignatureHex = String(req.headers["x-api-key"] ?? "");
>   const computedSignatureHex = crypto
>     .createHmac("sha256", secretKey)
>     .update(`${timestamp}:${rawBody}`, "utf8")
>     .digest("hex");
>
>   const signaturesMatch =
>     Boolean(expectedSignatureHex) &&
>     computedSignatureHex.length === expectedSignatureHex.length &&
>     crypto.timingSafeEqual(
>       Buffer.from(computedSignatureHex, "utf8"),
>       Buffer.from(expectedSignatureHex, "utf8"),
>     );
>
>   if (!signaturesMatch) {
>     return res.status(401).json({ error: "Invalid signature" });
>   }
>
>   let data: Record<string, any>;
>   try {
>     data = JSON.parse(rawBody);
>   } catch {
>     return res.status(400).json({ error: "Invalid request" });
>   }
>
>   const state = data.state;
>   if (state === "COMPLETED" || state === "FAILED" || state === "ABORTED") {
>     console.log(`CKG ingestion ${data.transaction_id} ${state}`);
>     if (data.error) {
>       console.log(
>         `  Error: ${data.error.code} - ${data.error.message} (retryable: ${data.error.retryable})`,
>       );
>     }
>     return res.status(200).json({ message: "CKG notification received" });
>   }
>
>   return res.status(200).json({ message: "Ignored" });
> });
>
> app.listen(3000, () => {
>   console.log("Webhook server listening on port 3000");
> });
> ```


## OpenAPI

````yaml POST /webhooks/ckg-ingestion
openapi: 3.0.1
info:
  title: Suki Developer Platform
  description: >-
    REST and WebSocket APIs for the Suki Developer Platform. Authenticate with
    Login or Register to obtain a Suki access token, then integrate ambient
    clinical documentation, form filling, transcription, content rewrite, and
    reference metadata endpoints.
  contact: {}
  version: '1.0'
servers:
  - url: https://sdp.suki.ai
    description: >-
      Production base URL for Suki Developer Platform REST APIs. WebSocket
      endpoints use the same host with `wss://`.
security:
  - SukiTokenAuth: []
paths:
  /webhooks/ckg-ingestion:
    post:
      tags:
        - /webhooks
      summary: Receive CKG ingestion notifications (partner webhook)
      description: >-
        Specification for the webhook endpoint your application hosts to receive
        a notification when a FHIR push (CKG Data Push) ingestion job reaches a
        final state. Suki sends a POST request to your configured notification
        URL. state is COMPLETED, FAILED, or ABORTED. error is present on FAILED
        and ABORTED when one was recorded. additional_info.session_type is
        CKG_INGESTION. The payload has no _links.
      requestBody:
        required: false
        description: CKG ingestion notification payload.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/controllers.CKGIngestionNotification'
            examples:
              completed:
                summary: COMPLETED
                value:
                  transaction_id: 46b88f6d-...
                  organization_id: your-org-id
                  correlation_id: 2e3a1724-...
                  state: COMPLETED
                  created_at: '2026-07-09T10:12:43Z'
                  completed_at: '2026-07-09T10:13:11Z'
                  additional_info:
                    session_type: CKG_INGESTION
              failed:
                summary: FAILED
                value:
                  transaction_id: 46b88f6d-...
                  organization_id: your-org-id
                  correlation_id: 2e3a1724-...
                  state: FAILED
                  created_at: '2026-07-09T10:12:43Z'
                  completed_at: '2026-07-09T10:13:11Z'
                  error:
                    code: BUNDLE_PARSE_ERROR
                    message: invalid bundle
                    retryable: false
                  additional_info:
                    session_type: CKG_INGESTION
              aborted:
                summary: ABORTED
                value:
                  transaction_id: 46b88f6d-...
                  organization_id: your-org-id
                  correlation_id: 2e3a1724-...
                  state: ABORTED
                  created_at: '2026-07-09T10:12:43Z'
                  completed_at: '2026-07-09T10:13:11Z'
                  error:
                    code: UPLOAD_ABORTED
                    message: Upload was not received within the allowed window.
                    retryable: true
                  additional_info:
                    session_type: CKG_INGESTION
      responses:
        '200':
          description: OK
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/controllers.BadRequestError'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/controllers.AuthenticationError'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/controllers.InternalServerError'
      security: []
components:
  schemas:
    controllers.CKGIngestionNotification:
      description: >-
        Notification for a CKG Data Push. additional_info.session_type is
        CKG_INGESTION.
      type: object
      properties:
        transaction_id:
          description: Transaction identifier returned when you requested the upload URL.
          type: string
          example: 46b88f6d-...
        organization_id:
          description: >-
            Partner-facing organization identifier, not the internal Suki
            organization id.
          type: string
          example: your-org-id
        correlation_id:
          description: >-
            Correlation identifier for the upload. Suki generates one when you
            do not send it.
          type: string
          example: 2e3a1724-...
        state:
          description: State of the CKG Data Push. Aborted uploads are reported as ABORTED.
          type: string
          enum:
            - COMPLETED
            - FAILED
            - ABORTED
          example: COMPLETED
        created_at:
          description: >-
            When the ingestion job was created, in UTC. Omitted when
            unavailable.
          type: string
          example: '2026-07-09T10:12:43Z'
        completed_at:
          description: >-
            When the ingestion job reached its final state, in UTC. Omitted when
            unavailable.
          type: string
          example: '2026-07-09T10:13:11Z'
        error:
          description: Present on FAILED and ABORTED payloads when an error was recorded.
          allOf:
            - $ref: '#/components/schemas/controllers.CKGIngestionError'
        additional_info:
          description: Partner-level context. session_type is CKG_INGESTION.
          allOf:
            - $ref: '#/components/schemas/controllers.WebhookAdditionalInfo'
    controllers.BadRequestError:
      description: Bad Request Response
      type: object
      properties:
        code:
          type: integer
          example: 400
        message:
          type: string
          example: invalid request
    controllers.AuthenticationError:
      description: Authentication Failure Response
      type: object
      properties:
        code:
          type: integer
          example: 401
        message:
          type: string
          example: invalid token
    controllers.InternalServerError:
      description: Internal Server Error Response
      type: object
      properties:
        code:
          type: integer
          example: 500
        message:
          type: string
          example: internal server error
    controllers.CKGIngestionError:
      description: Error object on a failed or aborted CKG Data Push notification.
      type: object
      properties:
        code:
          description: Machine-readable error code.
          type: string
          example: BUNDLE_PARSE_ERROR
        message:
          description: Human-readable description of the error.
          type: string
          example: invalid bundle
        retryable:
          description: true when uploading the bundle again may succeed.
          type: boolean
          example: false
    controllers.WebhookAdditionalInfo:
      description: >-
        Partner-level context on notification and CKG payloads. session_type
        distinguishes the flow when a partner serves every webhook from one URL.
      type: object
      properties:
        practice_id:
          description: Partner-facing practice identifier.
          type: string
          example: practice-xyz
        session_type:
          description: Flow the payload came from.
          type: string
          enum:
            - AMBIENT
            - FORM_FILLING
            - EM_CODES
            - CKG_INGESTION
          example: AMBIENT
  securitySchemes:
    SukiTokenAuth:
      type: apiKey
      in: header
      name: sdp_suki_token
      description: >-
        Suki access token (`suki_token`) from Login or Register. Expires after
        one hour.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.