Skip to content

Scribe Webhook

The Scribe Webhook pushes the finished clinical documentation from HAMI Scribe back to your system. HAMI sends it once the clinician has signed off the SOAP note and the after-visit summary has been generated. It is sent once per encounter, on the first sign-off; signing off again doesn’t send it a second time. It includes the transcript, the SOAP note, ICD and CPT codes, and the AI intake summary.

It is the clinician-side counterpart of the Intake Summary Webhook, which delivers the patient’s pre-visit summary.

Ingestion API ──► scribeLaunchUrl ──► Clinician uses Scribe ──► SOAP signed off + AVS generated ──► POST to your Scribe Webhook

The HAMI team configures the webhook for your group. Provide:

ItemDetails
Endpoint URLMust use https://. HAMI does not deliver to plain HTTP URLs.
API keyA secret HAMI sends in the X-API-KEY header so your endpoint can authenticate the call. Generate at least 32 random characters.

Method: POST
Content-Type: application/json
Timeout: HAMI waits up to 10 seconds for your response.

HeaderDescription
X-API-KEYThe API key you shared with HAMI. Reject the request if it does not match.
X-Hami-EventAlways SCRIBE_SESSION_COMPLETED
X-Hami-Idempotency-KeyThe encounterId. Stays the same across retries of the same delivery, so you can deduplicate.
Content-Typeapplication/json
FieldTypeDescription
encounterIdstringHAMI encounter identifier. Also sent as X-Hami-Idempotency-Key.
patientIdstringHAMI patient profile identifier
physicianIdstringHAMI identifier of the clinician who signed off
groupIdstringYour HAMI group identifier
sourcePatientIdstringOptional. Your patient MRN: the Patient.identifier with type code MR from the ingested bundle. Present when Scribe was opened from a scribeLaunchUrl.
sourceappointmentIdstringOptional. The fullUrl of the Appointment entry in the ingested bundle. Present when Scribe was opened from a scribeLaunchUrl and the bundle contained an Appointment. Note the lowercase a in appointment.
aiSummaryobject | nullThe patient’s AI intake summary for the encounter (a clinician-edited version, if there is one). Same section structure as the Intake Summary Webhook intakeSummary, but with all sections included.
soapNotestring | nullThe signed-off SOAP note
generatedNoteobject | nullThe note generated from your group’s encounter template, if one is used
transcriptstring | nullFull consultation transcript in chronological order, one recorded segment per line. Speakers are not labelled.
chiefComplaintstring | nullReserved. Currently always null; the chief complaint is in aiSummary and soapNote.
icdCodesarrayICD-10 coding sessions for the encounter (empty array if none). Each item has an IcdCode array of codes; see the example.
cptCodesarrayCPT coding sessions for the encounter (empty array if none). Each item has a CptCode array of codes; see the example.
vitalsobject | nullVitals recorded for the encounter
physicalExaminationobject | nullPhysical examination findings
assessmentobject | nullClinical assessment
treatmentPlanobject | nullTreatment plan
mdmobject | nullMedical decision-making (MDM) classification
afterVisitSummaryobject | nullPatient-facing after-visit summary

The values below are illustrative.

{
"encounterId": "enc_7c1d2e0a",
"patientId": "prf_41b9f8c3",
"physicianId": "prf_a02e77d1",
"groupId": "grp_5f3a9b2e",
"sourcePatientId": "MRN-000123",
"sourceappointmentId": "urn:uuid:3f6c1a52-8d7e-4b0f-9a1c-2e5d7b8c9f01",
"aiSummary": {
"Chief Complaint": "Sore throat and fever for 2 days",
"Summary": "Adult patient presenting with a 2-day history of sore throat and fever..."
},
"soapNote": "S: Sore throat and fever x2 days...\nO: T 38.2 °C...\nA: Acute pharyngitis...\nP: Supportive care...",
"generatedNote": null,
"transcript": "What brings you in today?\nMy throat has been sore since Monday and I had a fever last night...",
"chiefComplaint": null,
"icdCodes": [
{
"evaluate": "2026-09-30T10:15:00.000Z",
"IcdCode": [
{ "icd10Code": "J02.9", "icd10Description": "Acute pharyngitis, unspecified", "isPrimary": true, "isApproved": true }
]
}
],
"cptCodes": [
{
"evaluate": "2026-09-30T10:15:00.000Z",
"CptCode": [
{ "cptCode": "99213", "cptDescription": "Office/outpatient visit, established patient", "isPrimary": true, "isApproved": true }
]
}
],
"vitals": { "temperature": "38.2 °C", "heartRate": "92 /min" },
"physicalExamination": { "Throat": "Erythematous pharynx, no exudate" },
"assessment": { "Primary": "Acute pharyngitis" },
"treatmentPlan": { "Plan": "Fluids, rest, paracetamol as needed; return if worsening" },
"mdm": { "level": "Low" },
"afterVisitSummary": { "Instructions": "Rest, drink plenty of fluids..." }
}

Return any 2xx status as soon as you have stored the payload. Do your processing afterwards, asynchronously.

StatusMeaning to HAMI
2xxDelivered, no retry
Anything else, or no response within 10 sFailed, will be retried
  • HAMI makes up to 3 attempts: an initial attempt, a retry about 10 seconds later, and a final retry about 30 seconds after that.
  • Retries of the same delivery carry the same X-Hami-Idempotency-Key (encounterId). If you have already processed that key, return 200 and do nothing else.
  • Delivery happens in the background and never blocks the clinician. After the final failed attempt the delivery is dropped. Contact support to have it re-sent.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json({ limit: '5mb' }));
const SCRIBE_WEBHOOK_KEY = process.env.HAMI_SCRIBE_WEBHOOK_KEY;
function safeEqual(a = '', b = '') {
const ab = Buffer.from(a), bb = Buffer.from(b);
return ab.length === bb.length && crypto.timingSafeEqual(ab, bb);
}
app.post('/api/hami/scribe-webhook', async (req, res) => {
if (!safeEqual(req.header('X-API-KEY'), SCRIBE_WEBHOOK_KEY)) {
return res.status(401).json({ success: false, message: 'Unauthorized' });
}
const idempotencyKey = req.header('X-Hami-Idempotency-Key');
if (await alreadyProcessed(idempotencyKey)) {
return res.status(200).json({ success: true, duplicate: true });
}
// Persist first, process later
await saveScribeNote({
encounterId: req.body.encounterId,
mrn: req.body.sourcePatientId,
appointmentRef: req.body.sourceappointmentId,
payload: req.body,
});
res.status(200).json({ success: true });
});
app.listen(3000);
  • The payload contains PHI. Your endpoint must use TLS, check X-API-KEY on every request, and store the payload under your HIPAA controls.
  • Don’t log the full payload. Log encounterId and delivery status instead.
  • Rotate the API key through HAMI support if you think it has been exposed.