E/M Coding Notifications
E/M Coding Asynchronous Notifications
Webhook endpoint for receiving asynchronous notifications when standalone E/M coding session processing completes
Listen to this pageabout 1 min
POST
/
webhooks
/
notification
/
em-codes
curl --request POST \
--url https://sdp.suki.ai/webhooks/notification/em-codes \
--header 'Content-Type: application/json' \
--data '
{
"status": "SUCCESS",
"session_id": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"encounter_id": "",
"sessions": [
"a953839a-ddcd-407d-b9b0-3ed4b6be4be2"
],
"additional_info": {
"session_type": "EM_CODES",
"practice_id": "your-practice-id"
},
"_links": {
"em_codes": [
{
"href": "/em-codes/a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"status": [
{
"href": "/em-codes/a953839a-ddcd-407d-b9b0-3ed4b6be4be2/status",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"contents": [],
"transcripts": [],
"structured_data": [],
"section_structured_data": [],
"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 standalone E/M coding completion or failure.
Learn more about how webhooks work in Notification webhook for partners. For every field in the success and failure payloads, see E/M Coding webhook payload. To read the codes from the links, see Retrieve E/M Coding results.
-
Host this endpoint on your server. Suki sends a POST request when standalone E/M coding completes or fails. On success,
additional_info.session_typeisEM_CODES, and_linksincludesstatusandem_codesretrieval links for the E/Msession_idreturned from Generate E/M codes. -
Failure payloads have no
additional_info, so match theirsession_idagainst the E/M Coding session IDs you saved.encounter_idis always an empty string.
Code examples
- Python
- TypeScript
import hashlib
import hmac
import json
from flask import Flask, jsonify, request
app = Flask(__name__)
secret_key = "<partner_secret_key>"
# E/M Coding session IDs saved from Generate E/M codes.
em_session_ids = set()
@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
additional_info = data.get("additional_info") or {}
session_type = additional_info.get("session_type")
status = data.get("status")
if session_type == "EM_CODES" and status == "SUCCESS":
session_id = data.get("session_id")
print(f"E/M coding session {session_id} completed")
links = data.get("_links") or {}
for link in links.get("em_codes") or []:
print(f" E/M codes: {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
# Failure payloads have no additional_info, so match the session ID instead.
if status == "FAILURE" and data.get("session_id") in em_session_ids:
print(
f"E/M coding session {data.get('session_id')} failed: "
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>";
// E/M Coding session IDs saved from Generate E/M codes.
const emSessionIds = new Set<string>();
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 sessionType = data.additional_info?.session_type;
if (sessionType === "EM_CODES" && data.status === "SUCCESS") {
console.log(`E/M coding session ${data.session_id} completed`);
const links = data._links ?? {};
(links.em_codes ?? []).forEach((link: { method?: string; href?: string }) => {
console.log(` E/M codes: ${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" });
}
// Failure payloads have no additional_info, so match the session ID instead.
if (data.status === "FAILURE" && emSessionIds.has(data.session_id)) {
console.log(
`E/M coding session ${data.session_id} failed: ${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
Standalone E/M coding success or failure notification payload.
- Option 1
- Option 2
Success notification for an em-codes session. Includes status (/em-codes/{id}/status) and em_codes (/em-codes/{id}) links. additional_info.session_type is EM_CODES.
Links to associated resources.
Show child attributes
Show child attributes
Partner-level context. session_type is EM_CODES; practice_id is included when resolved.
Show child attributes
Show child attributes
Always an empty string. Standalone E/M coding sessions are not tied to an encounter.
Example:
""
Id of the em-codes session.
Example:
"a953839a-ddcd-407d-b9b0-3ed4b6be4be2"
Sorted list of em-codes sessions, oldest first.
Example:
["a953839a-ddcd-407d-b9b0-3ed4b6be4be2"]
Example:
"SUCCESS"
Response
OK
Last modified on November 5, 2026
Was this page helpful?
curl --request POST \
--url https://sdp.suki.ai/webhooks/notification/em-codes \
--header 'Content-Type: application/json' \
--data '
{
"status": "SUCCESS",
"session_id": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"encounter_id": "",
"sessions": [
"a953839a-ddcd-407d-b9b0-3ed4b6be4be2"
],
"additional_info": {
"session_type": "EM_CODES",
"practice_id": "your-practice-id"
},
"_links": {
"em_codes": [
{
"href": "/em-codes/a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"status": [
{
"href": "/em-codes/a953839a-ddcd-407d-b9b0-3ed4b6be4be2/status",
"method": "GET",
"name": "a953839a-ddcd-407d-b9b0-3ed4b6be4be2",
"type": "application/json"
}
],
"contents": [],
"transcripts": [],
"structured_data": [],
"section_structured_data": [],
"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"
}