Send Notifications
Asynchronous Notifications
Webhook endpoint for receiving asynchronous notifications from Suki platform
Listen to this pageabout 1 min
POST
/
webhooks
/
notification
Receive asynchronous notifications (partner webhook)
curl --request POST \
--url https://sdp.suki.ai/webhooks/notification \
--header 'Content-Type: application/json' \
--data '
{
"encounter_id": "29de56bc-960a-4cd5-b18f-79a798d62874",
"error_code": "ERROR_CODE_TRANSCRIPTION",
"error_detail": "Error in transcription",
"session_id": "20965414-929a-4f71-a3e5-b92bec07d086",
"status": "FAILURE"
}
'import requests
url = "https://sdp.suki.ai/webhooks/notification"
payload = {
"encounter_id": "29de56bc-960a-4cd5-b18f-79a798d62874",
"error_code": "ERROR_CODE_TRANSCRIPTION",
"error_detail": "Error in transcription",
"session_id": "20965414-929a-4f71-a3e5-b92bec07d086",
"status": "FAILURE"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
encounter_id: '29de56bc-960a-4cd5-b18f-79a798d62874',
error_code: 'ERROR_CODE_TRANSCRIPTION',
error_detail: 'Error in transcription',
session_id: '20965414-929a-4f71-a3e5-b92bec07d086',
status: 'FAILURE'
})
};
fetch('https://sdp.suki.ai/webhooks/notification', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sdp.suki.ai/webhooks/notification",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'encounter_id' => '29de56bc-960a-4cd5-b18f-79a798d62874',
'error_code' => 'ERROR_CODE_TRANSCRIPTION',
'error_detail' => 'Error in transcription',
'session_id' => '20965414-929a-4f71-a3e5-b92bec07d086',
'status' => 'FAILURE'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sdp.suki.ai/webhooks/notification"
payload := strings.NewReader("{\n \"encounter_id\": \"29de56bc-960a-4cd5-b18f-79a798d62874\",\n \"error_code\": \"ERROR_CODE_TRANSCRIPTION\",\n \"error_detail\": \"Error in transcription\",\n \"session_id\": \"20965414-929a-4f71-a3e5-b92bec07d086\",\n \"status\": \"FAILURE\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sdp.suki.ai/webhooks/notification")
.header("Content-Type", "application/json")
.body("{\n \"encounter_id\": \"29de56bc-960a-4cd5-b18f-79a798d62874\",\n \"error_code\": \"ERROR_CODE_TRANSCRIPTION\",\n \"error_detail\": \"Error in transcription\",\n \"session_id\": \"20965414-929a-4f71-a3e5-b92bec07d086\",\n \"status\": \"FAILURE\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sdp.suki.ai/webhooks/notification")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"encounter_id\": \"29de56bc-960a-4cd5-b18f-79a798d62874\",\n \"error_code\": \"ERROR_CODE_TRANSCRIPTION\",\n \"error_detail\": \"Error in transcription\",\n \"session_id\": \"20965414-929a-4f71-a3e5-b92bec07d086\",\n \"status\": \"FAILURE\"\n}"
response = http.request(request)
puts response.read_bodyThis response has no body data.{
"code": 400,
"message": "invalid request"
}{
"code": 401,
"message": "invalid token"
}{
"code": 500,
"message": "internal server error"
}- Notification Webhooks are available through the Partner APIs. Sessions created with the Web SDK, Mobile SDK, or Headless Web SDK use the same platform Webhook callbacks.
- Webhooks are available for ambient session and CKG data ingestion events.
Code examples
- Python
- TypeScript
import hashlib
import hmac
import json
from flask import Flask, jsonify, request
app = Flask(__name__)
secret_key = "<partner_secret_key>"
def print_links(label, links):
if not links:
return
print(f" {label}:")
for link in links:
print(f" {link.get('method')} {link.get('href')} - {link.get('name')}")
@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")
state = data.get("state")
if status == "SUCCESS":
session_id = data.get("session_id")
encounter_id = data.get("encounter_id")
sessions = data.get("sessions") or []
additional_info = data.get("additional_info")
print(f"Session {session_id} completed successfully")
print(f"Encounter ID: {encounter_id}")
print(f"Total sessions: {len(sessions)}")
if additional_info:
print(f"Additional info: {additional_info}")
links = data.get("_links") or {}
print("Available links:")
print_links("Session contents", links.get("contents"))
print_links("Encounter content", links.get("encounter_content"))
print_links("Structured data", links.get("structured_data"))
print_links("Encounter structured data", links.get("encounter_structured_data"))
print_links("Section structured data", links.get("section_structured_data"))
print_links(
"Encounter section structured data",
links.get("encounter_section_structured_data"),
)
print_links("Transcripts", links.get("transcripts"))
print_links("Status", links.get("status"))
return jsonify({"message": "Notification received"}), 200
if status == "FAILURE":
print(f"Session {data.get('session_id')} failed")
print(f"Encounter ID: {data.get('encounter_id')}")
print(f"Error Code: {data.get('error_code')}")
print(f"Error Detail: {data.get('error_detail')}")
return jsonify({"message": "Failure notification received"}), 200
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')}")
return jsonify({"message": "CKG notification received"}), 200
return jsonify({"error": "Unknown status"}), 400
if __name__ == "__main__":
app.run(port=3000)
import crypto from "crypto";
import express from "express";
const app = express();
const secretKey = "<partner_secret_key>";
type ResourceLink = { method?: string; href?: string; name?: string };
function logLinks(label: string, links?: ResourceLink[]) {
if (!links?.length) {
return;
}
console.log(` ${label}:`);
links.forEach((link) => {
console.log(` ${link.method} ${link.href} - ${link.name}`);
});
}
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 status = data.status;
const state = data.state;
if (status === "SUCCESS") {
const sessions = data.sessions ?? [];
console.log(`Session ${data.session_id} completed successfully`);
console.log(`Encounter ID: ${data.encounter_id}`);
console.log(`Total sessions: ${sessions.length}`);
if (data.additional_info) {
console.log("Additional info:", data.additional_info);
}
const links = data._links ?? {};
console.log("Available links:");
logLinks("Session contents", links.contents);
logLinks("Encounter content", links.encounter_content);
logLinks("Structured data", links.structured_data);
logLinks("Encounter structured data", links.encounter_structured_data);
logLinks("Section structured data", links.section_structured_data);
logLinks("Encounter section structured data", links.encounter_section_structured_data);
logLinks("Transcripts", links.transcripts);
logLinks("Status", links.status);
return res.status(200).json({ message: "Notification received" });
}
if (status === "FAILURE") {
console.log(`Session ${data.session_id} failed`);
console.log(`Encounter ID: ${data.encounter_id}`);
console.log(`Error Code: ${data.error_code}`);
console.log(`Error Detail: ${data.error_detail}`);
return res.status(200).json({ message: "Failure notification received" });
}
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}`);
}
return res.status(200).json({ message: "CKG notification received" });
}
return res.status(400).json({ error: "Unknown status" });
});
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});
Body
application/json
Webhook payload Suki sends when session processing fails.
Id of the encounter to which the payload belongs.
Example:
"29de56bc-960a-4cd5-b18f-79a798d62874"
Error code.
Example:
"ERROR_CODE_TRANSCRIPTION"
Details of the error, if any.
Example:
"Error in transcription"
Id of the session that failed.
Example:
"20965414-929a-4f71-a3e5-b92bec07d086"
Example:
"FAILURE"
Response
OK
Last modified on October 16, 2026
Was this page helpful?
Receive asynchronous notifications (partner webhook)
curl --request POST \
--url https://sdp.suki.ai/webhooks/notification \
--header 'Content-Type: application/json' \
--data '
{
"encounter_id": "29de56bc-960a-4cd5-b18f-79a798d62874",
"error_code": "ERROR_CODE_TRANSCRIPTION",
"error_detail": "Error in transcription",
"session_id": "20965414-929a-4f71-a3e5-b92bec07d086",
"status": "FAILURE"
}
'import requests
url = "https://sdp.suki.ai/webhooks/notification"
payload = {
"encounter_id": "29de56bc-960a-4cd5-b18f-79a798d62874",
"error_code": "ERROR_CODE_TRANSCRIPTION",
"error_detail": "Error in transcription",
"session_id": "20965414-929a-4f71-a3e5-b92bec07d086",
"status": "FAILURE"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
encounter_id: '29de56bc-960a-4cd5-b18f-79a798d62874',
error_code: 'ERROR_CODE_TRANSCRIPTION',
error_detail: 'Error in transcription',
session_id: '20965414-929a-4f71-a3e5-b92bec07d086',
status: 'FAILURE'
})
};
fetch('https://sdp.suki.ai/webhooks/notification', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sdp.suki.ai/webhooks/notification",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'encounter_id' => '29de56bc-960a-4cd5-b18f-79a798d62874',
'error_code' => 'ERROR_CODE_TRANSCRIPTION',
'error_detail' => 'Error in transcription',
'session_id' => '20965414-929a-4f71-a3e5-b92bec07d086',
'status' => 'FAILURE'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sdp.suki.ai/webhooks/notification"
payload := strings.NewReader("{\n \"encounter_id\": \"29de56bc-960a-4cd5-b18f-79a798d62874\",\n \"error_code\": \"ERROR_CODE_TRANSCRIPTION\",\n \"error_detail\": \"Error in transcription\",\n \"session_id\": \"20965414-929a-4f71-a3e5-b92bec07d086\",\n \"status\": \"FAILURE\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sdp.suki.ai/webhooks/notification")
.header("Content-Type", "application/json")
.body("{\n \"encounter_id\": \"29de56bc-960a-4cd5-b18f-79a798d62874\",\n \"error_code\": \"ERROR_CODE_TRANSCRIPTION\",\n \"error_detail\": \"Error in transcription\",\n \"session_id\": \"20965414-929a-4f71-a3e5-b92bec07d086\",\n \"status\": \"FAILURE\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sdp.suki.ai/webhooks/notification")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"encounter_id\": \"29de56bc-960a-4cd5-b18f-79a798d62874\",\n \"error_code\": \"ERROR_CODE_TRANSCRIPTION\",\n \"error_detail\": \"Error in transcription\",\n \"session_id\": \"20965414-929a-4f71-a3e5-b92bec07d086\",\n \"status\": \"FAILURE\"\n}"
response = http.request(request)
puts response.read_bodyThis response has no body data.{
"code": 400,
"message": "invalid request"
}{
"code": 401,
"message": "invalid token"
}{
"code": 500,
"message": "internal server error"
}