> ## 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.

# Retrieve Results from Form Filling Webhook

> Use Form filling webhook links to retrieve filled medical forms and check session status

When Suki sends a `SUCCESS` Form filling webhook, the payload includes links to the filled form output and the session status.

> [!NOTE]
>
> The webhook does not contain the filled form values. Your application must call the linked Form filling API endpoints to retrieve them.

Use the `structured_data` link for the session in the notification to load the filled forms. Check `generated_values` for forms Suki filled and `non_generated_values` for templates that still need manual entry. Call the Form filling status endpoint when you need to confirm the session state.

Each `href` is relative to `/api/v1`. Join your API base URL with that path for `structured_data`, and authenticate the request with your Suki Token. For status, use the Form filling status path described below instead of the raw `status` href.

## Prerequisites

Before you retrieve results, make sure you have:

* **A webhook receiver:** An endpoint that receives Suki webhooks and verifies their signatures. Refer to [Webhook quickstart](https://developer.suki.ai/documentation/webhook/quickstart.md) and [Signature verification](https://developer.suki.ai/documentation/webhook/signature-verification.md).
* **A valid Suki Token:** Authenticate Form filling API requests with the `sdp_suki_token` header. Include the `sdp_provider_id` header when your partnership requires it. Refer to [Form filling API quickstart](https://developer.suki.ai/form-filling-api-reference/quickstart.md).

## Build the full URL

Form filling webhook links are relative to `/api/v1`, not `/api/v1/ambient`.

### Structured data

Append `/api/v1` and the `structured_data` `href` to your API base URL.

For example, when the production base URL is `https://sdp.suki.ai` and the link contains:

```text theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
/form-filling/session/{session_id}/structured-data
```

the full request URL is:

```text theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
https://sdp.suki.ai/api/v1/form-filling/session/{session_id}/structured-data
```

For staging, use `https://sdp.suki-stage.com` as the API base URL.

### Session status

The webhook `status` href looks like `/session/{session_id}/status`. That path is the shared session-status shape. For a Form filling session, call the Form filling status endpoint instead:

```text theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
https://sdp.suki.ai/api/v1/form-filling/session/{session_id}/status
```

Do not call `/api/v1/session/{session_id}/status` for Form filling results.

### Available endpoints

| `_links` key | Endpoint to call | API reference |
| :- | :- | :- |
| `structured_data` | `GET /api/v1/form-filling/session/{session_id}/structured-data` | [Get Form filling structured data](https://developer.suki.ai/form-filling-api-reference/form-filling-sessions/structured-data.md) |
| `status` | `GET /api/v1/form-filling/session/{session_id}/status` | [Get Form filling session status](https://developer.suki.ai/form-filling-api-reference/form-filling-sessions/status.md) |

Every link object includes a `method` field. Use that method when you call the endpoint. For these links, the method is always `GET`.

Session-level arrays hold one link for every Form filling session in the encounter. Match the link's `name` to the webhook's `session_id` when you want results for the session that triggered the notification. Form filling has no encounter-level links.

> [!NOTE]
>
> Other `_links` keys are present on the Form filling webhook but are empty arrays. Ignore them. Refer to [Form filling webhook payload](https://developer.suki.ai/documentation/webhook/form-filling/payload.md#the-_links-object).

## Retrieve results step by step

Follow this sequence after your webhook endpoint receives a `SUCCESS` notification:

**1. Verify the Webhook and Return HTTP 200**

Verify the webhook signature and return HTTP **200** within 30 seconds. Do not wait for Form filling API calls to finish before you respond. Process the notification and retrieve results in a background job.

**2. Select the Structured Data Link**

Read `_links.structured_data` and choose the link whose `name` matches the webhook's `session_id`.

**3. Retrieve the Filled Forms**

Call the `structured_data` link. Read `structured_data.generated_values` for filled form instances and `structured_data.non_generated_values` for templates Suki could not fill.

**4. Check Session Status When Needed**

Call the Form filling status endpoint with the same `session_id` when you need to confirm the session state before you mark the visit done or surface an error.

> [!NOTE]
>
> A `SUCCESS` webhook means processing finished. It does not mean every requested template was filled. Always handle `non_generated_values` before you show forms to the clinician.

## Read the filled forms

| Field | Description |
| :- | :- |
| `structured_data.generated_values` | Filled medical form instances for this session. Each entry can include `id`, `form_template_id`, `data`, `status`, `title`, and related fields. |
| `structured_data.non_generated_values` | Form templates that you requested but that did not produce a filled instance. Each entry contains at least `form_template_id`. |

### Example response

```json expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
{
  "structured_data": {
    "generated_values": [
      {
        "id": "019d4cdc-9319-7d81-ae2e-fd6de7f1b4f0",
        "form_template_id": "019d4cdc-9319-7d81-ae2e-fd6de7f1b4f0-template",
        "correlation_id": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
        "patient_id": "patient-123",
        "status": "MEDICAL_FORM_STATUS_COMPLETED",
        "title": "Adult Vitals",
        "created_at": "2026-01-01T00:00:00Z",
        "data": {},
        "metadata": {}
      }
    ],
    "non_generated_values": [
      {
        "form_template_id": "019d4cdc-9319-7d81-ae2e-fd6de7f1b4f0"
      }
    ]
  }
}
```

## Check session status

Call [Get Form filling session status](https://developer.suki.ai/form-filling-api-reference/form-filling-sessions/status.md) when you need the current processing state.

| Status | Description |
| :- | :- |
| `created` | Session has been created but has not yet started. |
| `ready` | Session has been started. |
| `running` | Session is generating form content. |
| `paused` | Session is paused. |
| `aborted` | Session has been aborted by the client. |
| `failed` | Session failed due to an error. |
| `completed` | Session completed successfully. |

## Example: retrieve filled forms

The following examples retrieve structured data for the Form filling session that triggered the webhook.

Run this code after your webhook endpoint has verified the signature and returned HTTP **200**. The examples:

1. Read `session_id` and `_links` from the webhook payload.
2. Find the `structured_data` link for that session.
3. Retrieve the filled forms and print generated and non-generated templates.

The examples use the production API base URL. Replace it with the staging base URL when you test against staging.

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

### Code example

> [tab] Python
>
> ```python expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> import requests
>
> BASE_URL = "https://sdp.suki.ai"
> API_PATH = "/api/v1"
> HEADERS = {"sdp_suki_token": "<sdp_suki_token>"}
>
>
> def link_for(links, key, name):
>     for link in links.get(key) or []:
>         if link.get("name") == name:
>             return link
>     return None
>
>
> def load_form_filling_results(payload):
>     session_id = payload["session_id"]
>     links = payload.get("_links") or {}
>
>     structured_link = link_for(links, "structured_data", session_id)
>     if not structured_link:
>         raise ValueError(f"No structured_data link found for session {session_id}")
>
>     response = requests.get(
>         BASE_URL + API_PATH + structured_link["href"],
>         headers=HEADERS,
>     )
>     response.raise_for_status()
>     structured_data = response.json()["structured_data"]
>
>     for form in structured_data.get("generated_values") or []:
>         print(f"Filled: {form.get('title')} ({form.get('form_template_id')})")
>     for missing in structured_data.get("non_generated_values") or []:
>         print(f"Not filled: {missing.get('form_template_id')}")
>
>     return structured_data
> ```

> [tab] TypeScript
>
> ```typescript expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> const BASE_URL = "https://sdp.suki.ai";
> const API_PATH = "/api/v1";
> const HEADERS = { sdp_suki_token: "<sdp_suki_token>" };
>
> type Link = {
>   href: string;
>   method: string;
>   name: string;
>   type: string;
> };
>
> function linkFor(
>   links: Record<string, Link[]>,
>   key: string,
>   name: string
> ): Link | undefined {
>   return (links[key] ?? []).find((link) => link.name === name);
> }
>
> export async function loadFormFillingResults(payload: any) {
>   const sessionId: string = payload.session_id;
>   const links: Record<string, Link[]> = payload._links ?? {};
>
>   const structuredLink = linkFor(links, "structured_data", sessionId);
>   if (!structuredLink) {
>     throw new Error(`No structured_data link found for session ${sessionId}`);
>   }
>
>   const response = await fetch(BASE_URL + API_PATH + structuredLink.href, {
>     headers: HEADERS,
>   });
>   if (!response.ok) {
>     throw new Error(`Structured data call failed: ${response.status}`);
>   }
>
>   const { structured_data } = await response.json();
>
>   for (const form of structured_data.generated_values ?? []) {
>     console.log(`Filled: ${form.title} (${form.form_template_id})`);
>   }
>   for (const missing of structured_data.non_generated_values ?? []) {
>     console.log(`Not filled: ${missing.form_template_id}`);
>   }
>
>   return structured_data;
> }
> ```

> [tab] Go
>
> ```go expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> package main
>
> import (
> 	"encoding/json"
> 	"fmt"
> 	"io"
> 	"net/http"
> )
>
> const (
> 	baseURL   = "https://sdp.suki.ai"
> 	apiPath   = "/api/v1"
> 	sukiToken = "<sdp_suki_token>"
> )
>
> type link struct {
> 	Href   string `json:"href"`
> 	Method string `json:"method"`
> 	Name   string `json:"name"`
> 	Type   string `json:"type"`
> }
>
> func linkFor(links map[string][]link, key, name string) (link, bool) {
> 	for _, item := range links[key] {
> 		if item.Name == name {
> 			return item, true
> 		}
> 	}
> 	return link{}, false
> }
>
> func getJSON(path string) (map[string]any, error) {
> 	req, err := http.NewRequest(http.MethodGet, baseURL+apiPath+path, nil)
> 	if err != nil {
> 		return nil, err
> 	}
> 	req.Header.Set("sdp_suki_token", sukiToken)
>
> 	res, err := http.DefaultClient.Do(req)
> 	if err != nil {
> 		return nil, err
> 	}
> 	defer res.Body.Close()
>
> 	body, err := io.ReadAll(res.Body)
> 	if err != nil {
> 		return nil, err
> 	}
> 	if res.StatusCode < 200 || res.StatusCode >= 300 {
> 		return nil, fmt.Errorf("request failed: %s", res.Status)
> 	}
>
> 	var payload map[string]any
> 	if err := json.Unmarshal(body, &payload); err != nil {
> 		return nil, err
> 	}
> 	return payload, nil
> }
>
> func loadFormFillingResults(payload map[string]any) (map[string]any, error) {
> 	sessionID, _ := payload["session_id"].(string)
> 	rawLinks, _ := payload["_links"].(map[string]any)
>
> 	links := map[string][]link{}
> 	for key, value := range rawLinks {
> 		encoded, err := json.Marshal(value)
> 		if err != nil {
> 			return nil, err
> 		}
> 		var items []link
> 		if err := json.Unmarshal(encoded, &items); err != nil {
> 			return nil, err
> 		}
> 		links[key] = items
> 	}
>
> 	structuredLink, ok := linkFor(links, "structured_data", sessionID)
> 	if !ok {
> 		return nil, fmt.Errorf("no structured_data link found for session %s", sessionID)
> 	}
>
> 	response, err := getJSON(structuredLink.Href)
> 	if err != nil {
> 		return nil, err
> 	}
>
> 	structuredData, _ := response["structured_data"].(map[string]any)
> 	return structuredData, nil
> }
> ```

## Example: check session status

Build the Form filling status URL from `session_id`. Do not append the webhook `status` href to `/api/v1` unchanged.

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

### Code example

> [tab] Python
>
> ```python expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> def get_form_filling_status(session_id):
>     response = requests.get(
>         f"{BASE_URL}{API_PATH}/form-filling/session/{session_id}/status",
>         headers=HEADERS,
>     )
>     response.raise_for_status()
>     return response.json().get("status")
> ```

> [tab] TypeScript
>
> ```typescript expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> export async function getFormFillingStatus(sessionId: string) {
>   const response = await fetch(
>     `${BASE_URL}${API_PATH}/form-filling/session/${sessionId}/status`,
>     { headers: HEADERS }
>   );
>   if (!response.ok) {
>     throw new Error(`Status call failed: ${response.status}`);
>   }
>   const data = await response.json();
>   return data.status as string;
> }
> ```

> [tab] Go
>
> ```go expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> func getFormFillingStatus(sessionID string) (string, error) {
> 	payload, err := getJSON("/form-filling/session/" + sessionID + "/status")
> 	if err != nil {
> 		return "", err
> 	}
> 	status, _ := payload["status"].(string)
> 	return status, nil
> }
> ```

## Responses from linked endpoints

When you call the Form filling API endpoints, Suki returns standard HTTP status codes.

| HTTP status | Meaning | Example response |
| :- | :- | :- |
| `200` | OK. The request succeeded. | |
| `400` | Bad Request. The request was invalid. | `{"code": 400, "message": "invalid request"}` |
| `401` | Unauthorized. The token is invalid or authentication failed. | `{"code": 401, "message": "invalid token"}` |
| `404` | Not Found. The requested resource does not exist. | `{"code": 404, "message": "not found"}` |
| `500` | Internal Server Error. The server encountered an error. | `{"code": 500, "message": "internal server error"}` |

* **`400 Bad Request`:** Fix the URL or request parameters, then send the request again.
* **`401 Unauthorized`:** Log in again for a new Suki Token, then retry with the updated token.
* **`404 Not Found`:** Confirm the Form filling `session_id` and endpoint path.
* **`5xx Server Error`:** Retry the request after a short delay.

## Next steps

&#x20;**[Form filling payload reference](https://developer.suki.ai/documentation/webhook/form-filling/payload.md)** - Every field and link key in the webhook body.

&#x20;**[Form filling templates](https://developer.suki.ai/documentation/concepts/form-filling/form-filling-templates.md)** - How templates map to filled forms.

&#x20;**[Form filling no results](https://developer.suki.ai/documentation/troubleshooting/form-filling-no-results.md)** - What to check when no forms are filled.

&#x20;**[Webhook request and response](https://developer.suki.ai/documentation/webhook/payload-and-response.md)** - The 200 response, retries, and duplicate deliveries.


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