Form Filling Notifications
Form Filling Asynchronous Notifications
Webhook endpoint for receiving asynchronous notifications when Form filling session processing completes
Listen to this pageabout 1 min
POST
/
webhooks
/
notification
/
form-filling
curl --request POST \
--url https://sdp.suki.ai/webhooks/notification/form-filling \
--header 'Content-Type: application/json' \
--data '
{
"status": "SUCCESS",
"session_id": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"encounter_id": "4d753ce1-bbff-43e1-950a-82dea2d86873",
"sessions": [
"a953839a-ddcd-407d-b9b0-3ed4b6be4be2"
],
"additional_info": {
"session_type": "FORM_FILLING",
"practice_id": "your-practice-id"
},
"_links": {
"structured_data": [
{
"href": "/form-filling/session/a953839a-ddcd-407d-b9b0-3ed4b6be4be2/structured-data",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"status": [
{
"href": "/session/a953839a-ddcd-407d-b9b0-3ed4b6be4be2/status",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"contents": [],
"transcripts": [],
"section_structured_data": [],
"em_codes": [],
"diagnoses": [],
"orders": [],
"encounter_content": [],
"encounter_structured_data": [],
"encounter_section_structured_data": [],
"encounter_em_codes": [],
"encounter_diagnoses": [],
"encounter_orders": []
}
}
'
{
"code": 400,
"message": "invalid request"
}{
"code": 401,
"message": "invalid token"
}{
"code": 500,
"message": "internal server error"
}Use this endpoint to implement an HTTP endpoint in your application that receives requests from Suki.
This endpoint should be hosted by your application to receive notifications about Form filling completion or failure.
Learn more about how webhooks work in Notification webhook for partners. For every field and link key in the payload, see Form filling webhook payload. To read the filled forms from the links, see Retrieve Form filling results.
Host this endpoint on your server. Suki sends a POST when Form filling processing completes or fails. The payload
session_id is the Form filling session ID from Create Form filling session. On success, additional_info.session_type is FORM_FILLING. Failure payloads have no additional_info, so match their session_id against the Form filling session IDs you saved.Code examples
- Python
- TypeScript
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
status = data.get("status")
if status == "SUCCESS":
session_id = data.get("session_id")
encounter_id = data.get("encounter_id")
print(f"Form filling session {session_id} completed (encounter {encounter_id})")
links = data.get("_links") or {}
for link in links.get("structured_data") or []:
print(f" Structured data: {link.get('method')} {link.get('href')}")
for link in links.get("status") or []:
print(f" Status: {link.get('method')} {link.get('href')}")
return jsonify({"message": "Notification received"}), 200
if status == "FAILURE":
print(
f"Form filling session {data.get('session_id')} failed "
f"(encounter {data.get('encounter_id')}): "
f"{data.get('error_code')} - {data.get('error_detail')}"
)
return jsonify({"message": "Failure notification received"}), 200
return jsonify({"message": "Ignored"}), 200
if __name__ == "__main__":
app.run(port=3000)
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" });
}
if (data.status === "SUCCESS") {
console.log(
`Form filling session ${data.session_id} completed (encounter ${data.encounter_id})`,
);
const links = data._links ?? {};
(links.structured_data ?? []).forEach((link: { method?: string; href?: string }) => {
console.log(` Structured data: ${link.method} ${link.href}`);
});
(links.status ?? []).forEach((link: { method?: string; href?: string }) => {
console.log(` Status: ${link.method} ${link.href}`);
});
return res.status(200).json({ message: "Notification received" });
}
if (data.status === "FAILURE") {
console.log(
`Form filling session ${data.session_id} failed (encounter ${data.encounter_id}): ` +
`${data.error_code} - ${data.error_detail}`,
);
return res.status(200).json({ message: "Failure notification received" });
}
return res.status(200).json({ message: "Ignored" });
});
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});
Body
application/json
Form filling success or failure notification payload.
- Option 1
- Option 2
Success notification for a form filling session. Includes status and structured_data links (structured_data hrefs use /form-filling/session/{id}/structured-data). All other link kinds are sent as empty arrays. additional_info.session_type is FORM_FILLING.
Response
OK
Last modified on November 5, 2026
Was this page helpful?
curl --request POST \
--url https://sdp.suki.ai/webhooks/notification/form-filling \
--header 'Content-Type: application/json' \
--data '
{
"status": "SUCCESS",
"session_id": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"encounter_id": "4d753ce1-bbff-43e1-950a-82dea2d86873",
"sessions": [
"a953839a-ddcd-407d-b9b0-3ed4b6be4be2"
],
"additional_info": {
"session_type": "FORM_FILLING",
"practice_id": "your-practice-id"
},
"_links": {
"structured_data": [
{
"href": "/form-filling/session/a953839a-ddcd-407d-b9b0-3ed4b6be4be2/structured-data",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"status": [
{
"href": "/session/a953839a-ddcd-407d-b9b0-3ed4b6be4be2/status",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"contents": [],
"transcripts": [],
"section_structured_data": [],
"em_codes": [],
"diagnoses": [],
"orders": [],
"encounter_content": [],
"encounter_structured_data": [],
"encounter_section_structured_data": [],
"encounter_em_codes": [],
"encounter_diagnoses": [],
"encounter_orders": []
}
}
'
{
"code": 400,
"message": "invalid request"
}{
"code": 401,
"message": "invalid token"
}{
"code": 500,
"message": "internal server error"
}