Streaming & Webhooks
Webhook Integration
Receive real-time push notifications when notes are generated or fail, eliminating the need for polling.
Updated March 19, 2026
Webhooks push events to your server when SOAP notes complete or fail. Instead of polling GET /v1/audio/status in a loop, your endpoint receives the result the moment processing finishes. This is the recommended pattern for production audio integrations.
When to use webhooks
- Audio processing -- audio-to-note takes 10-60 seconds. Webhooks deliver the result instantly without polling.
- High volume -- polling 100 concurrent notes means 100 polling loops. Webhooks scale to any volume with a single endpoint.
- Backend integrations -- your server receives a POST, processes it, and stores the note. No client-side code needed.
Step 1: Configure your webhook URL
In your SOAPNoteAPI dashboard, go to Settings > Webhooks. Enter your HTTPS endpoint URL and enable webhooks. When you save, a signing secret (whsec_...) is generated and shown once -- copy it immediately.
Your webhook URL must use HTTPS. SOAPNoteAPI will POST JSON payloads to this URL with a 10-second timeout.
Step 2: Handle webhook events
SOAPNoteAPI sends two event types:
- note.generated -- the note was successfully created. The data field includes the full SOAP note (subjective, objective, assessment, plan, transcript, billing_codes, patient_summary, expires_at). No separate GET request needed.
- note.failed -- processing failed (e.g., audio too short, corrupted file, transcription error). The payload includes the note_id and status but no data field.
- test -- a synthetic event sent when you click "Send test event" in the dashboard. Use it to verify your endpoint is reachable and signature verification works.
Event payload format
// note.generated event
{
"event": "note.generated",
"note_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"created_at": "2026-03-19T15:30:00.000Z",
"data": {
"noteId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subjective": "Patient presents with a 3-day history of...",
"objective": "Vital signs: BP 120/80, HR 72, Temp 98.6F...",
"assessment": "1. Acute upper respiratory infection...",
"plan": "1. Supportive care with rest and fluids...",
"transcript": "The transcribed audio content...",
"billing_codes": {
"icd10": [{ "code": "J06.9", "description": "Acute upper respiratory infection" }],
"cpt": [{ "code": "99213", "description": "Office visit, established patient" }]
},
"patient_summary": "You visited your doctor today for cold symptoms...",
"expires_at": "2026-03-20T15:30:00.000Z"
}
}
// note.failed event
{
"event": "note.failed",
"note_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "failed",
"created_at": "2026-03-19T15:30:00.000Z"
}Step 3: Verify webhook signatures
Every webhook includes two headers for signature verification: X-SoapNoteAPI-Signature (hex HMAC-SHA256) and X-SoapNoteAPI-Timestamp (Unix epoch seconds). Always verify signatures before processing events to prevent spoofed requests.
The signed content is: timestamp + "." + raw JSON payload. Compute the HMAC-SHA256 of this string using your webhook secret, then compare with the signature header using a timing-safe comparison.
Node.js verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
const signedContent = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signedContent)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
// In your Express/Next.js handler:
app.post('/webhooks/soapnoteapi', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-soapnoteapi-signature'];
const ts = req.headers['x-soapnoteapi-timestamp'];
const body = req.body.toString();
if (!verifyWebhook(body, sig, ts, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(body);
console.log('Received:', event.event, event.note_id);
// Process asynchronously, return 200 quickly
processEvent(event).catch(console.error);
res.status(200).json({ received: true });
});Python verification
import hmac
import hashlib
def verify_webhook(payload: str, signature: str, timestamp: str, secret: str) -> bool:
signed_content = f"{timestamp}.{payload}"
expected = hmac.new(
secret.encode(),
signed_content.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# Flask example:
@app.route('/webhooks/soapnoteapi', methods=['POST'])
def handle_webhook():
sig = request.headers.get('X-SoapNoteAPI-Signature')
ts = request.headers.get('X-SoapNoteAPI-Timestamp')
payload = request.get_data(as_text=True)
if not verify_webhook(payload, sig, ts, WEBHOOK_SECRET):
return jsonify(error='Invalid signature'), 401
event = request.get_json()
if event['event'] == 'note.generated':
save_note(event['note_id'], event['data'])
elif event['event'] == 'note.failed':
handle_failure(event['note_id'])
return jsonify(received=True), 200Testing webhooks
The SOAPNoteAPI dashboard has a built-in "Send test event" button that delivers a synthetic test event to your configured URL. Use this to verify your endpoint is reachable and your signature verification is working.
The test event has event type "test" and a synthetic note_id. Your handler should accept it (return 200) but can skip processing.
// Test event payload
{
"event": "test",
"note_id": "test_1710784200000",
"status": "test",
"created_at": "2026-03-18T15:30:00.000Z"
}Retry policy
Webhook delivery is best-effort. If your endpoint returns a non-2xx status or times out (10-second limit), the delivery is logged as failed. SOAPNoteAPI does not currently retry failed deliveries -- your endpoint should be reliable and return 200 quickly.
Rotating your webhook secret
You can rotate your signing secret at any time from the dashboard (Settings > Webhooks > Rotate secret). The old secret is immediately invalidated. Update your server environment variable with the new secret before rotating to avoid rejecting valid events during the transition.
Need help? Contact support@soapnoteapi.com