IsomeraDocs

API Version v1

api.isomera.ai/v1

Webhooks

Real-time event notifications for voice conversations, prior authorization updates, adverse events, and patient interactions. Configure webhooks to integrate Isomera events with your EHR, CRM, or operational systems.

Overview

Webhooks deliver HTTP POST requests to your configured endpoints when events occur in the Isomera platform. All payloads are JSON-encoded and include standard metadata for event identification and deduplication.

Event Categories

CategoryEvents
Voice Callscall.started, call.completed, call.failed
Patient Safetyadverse_event.reported, adverse_event.escalated
Prior Authorizationpa.required, pa.submitted, pa.decision
Benefitsbenefits.verified, coverage.confirmed
Casescase.created, conversation.created
HealthConnect Demobenefits.edi.completed, payer_call.requested
Patient Actionscallback.requested, patient.verified

Payload Structure

All webhook payloads follow a consistent structure:

Standard webhook payload
{
  "id": "evt_7x8y9z0a1b2c3d4e",
  "event": "call.completed",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:35:00.000Z",
  "data": {
    "object": "voice_call",
    // Event-specific data
  },
  "livemode": true
}

HealthConnect demo events

When using the HealthConnect widget demo, Isomera can emit outgoing integration webhooks for key workflow steps. These are best-effort and are only sent when configured.

Configuration

Set OUTGOING_WEBHOOK_URL (and optionally OUTGOING_WEBHOOK_SECRET) in the Isomera backend.

Events

  • case.created — a Case is created when widget chat starts
  • conversation.created — the canonical Conversation is created (chat or voice)
  • benefits.edi.completed — EDI benefits snapshot completed (via /verify)
  • payer_call.requested — follow-up action created to call the payer (via /start_payer_call)
case.created payload
{
  "id": "evt_8f0f9c8f1f2a4b0bb5f3eae7c9d12b34",
  "event": "case.created",
  "api_version": "2025-12-26",
  "created_at": "2025-12-26T07:40:00.000Z",
  "data": {
    "object": "case",
    "id": "f7c2a5c6-3c2a-4c43-8e92-1a2b3c4d5e6f",
    "status": "open",
    "client_slug": "healthconnect",
    "widget_client_id": "0c7b3c4d-1a2b-4c43-8e92-3c2a5c6f7d8e"
  },
  "livemode": false
}

call.started

Triggered when a voice conversation begins. Use this to update real-time dashboards or initiate parallel processes like patient record lookups.

Payload Fields
data.idinteger

Unique voice call ID in the Isomera system.

data.call_idstring

External conversation identifier for tracking.

data.phone_numberstring

Caller's phone number in E.164 format.

data.directionstring

Call direction: inbound or outbound.

data.call_typestring

Type of call: patient_inbound or insurance_outbound.

data.agent_typestring

AI agent handling the call: benefits, insurance, medical.

call.started payload
{
  "id": "evt_a1b2c3d4e5f6g7h8",
  "event": "call.started",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:30:00.000Z",
  "data": {
    "object": "voice_call",
    "id": 1847,
    "call_id": "conv_8701kb8rek5dfb7sc9qm",
    "phone_number": "+14155551234",
    "direction": "inbound",
    "call_type": "patient_inbound",
    "agent_type": "benefits",
    "status": "in_progress",
    "started_at": "2024-12-18T10:30:00.000Z"
  },
  "livemode": true
}

call.completed

Sent when a voice conversation ends successfully. Includes complete call analysis, transcript summary, patient satisfaction, and any follow-up actions identified during the call.

Key Payload Fields
data.patient_idinteger

Linked patient ID after verification, or null if unverified.

data.duration_secondsinteger

Total call duration in seconds.

data.call_outcomestring

Primary outcome: coverage_confirmed, pa_required,callback_scheduled, verification_failed.

data.patient_satisfaction_scoreinteger

Patient rating from 1-5, collected at end of call.

data.topics_discussedarray

Topics covered: benefits_verification, pa_requirements, cost_sharing, etc.

data.medications_discussedarray

Medications mentioned during the call.

data.analysisobject

AI-generated analysis including summary, key findings, and recommended actions.

data.follow_up_actionsarray

List of required follow-up actions with priority and due dates.

data.compliance_scorenumber

Call compliance percentage (0-100) against configured SOP rules.

call.completed payload
{
  "id": "evt_b2c3d4e5f6g7h8i9",
  "event": "call.completed",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:35:00.000Z",
  "data": {
    "object": "voice_call",
    "id": 1847,
    "call_id": "conv_8701kb8rek5dfb7sc9qm",
    "phone_number": "+14155551234",
    "direction": "inbound",
    "status": "completed",
    "patient_id": 456,
    "duration_seconds": 312,
    "call_outcome": "pa_required",
    "patient_satisfaction_score": 4,
    "verified": true,
    "verification_method": "name_dob_memberid",
    "topics_discussed": [
      "benefits_verification",
      "pa_requirements",
      "cost_sharing"
    ],
    "medications_discussed": ["Rixomab"],
    "jcodes_discussed": {
      "Rixomab": "J0000"
    },
    "analysis": {
      "summary": "Patient called to verify coverage for Rixomab. Prior authorization is required. Deductible has been met, patient has 20% coinsurance.",
      "key_findings": [
        "Prior authorization required for Rixomab",
        "Annual deductible of $1,500 has been met",
        "20% coinsurance applies after deductible",
        "PA can be submitted via fax or portal"
      ],
      "next_steps_for_patient": "Provider will submit PA request. Patient will be notified within 5-7 business days.",
      "internal_notes": "Patient was well-informed and satisfied with the information provided."
    },
    "follow_up_actions": [
      {
        "action": "Submit prior authorization",
        "assigned_to": "provider",
        "priority": "high",
        "status": "pending",
        "due_date": "2024-12-21"
      }
    ],
    "compliance_score": 94.5,
    "callback_requested": false,
    "email_summary_requested": true,
    "callback_email": "patient@email.com",
    "audio_url": "/api/v1/calls/1847/audio",
    "started_at": "2024-12-18T10:30:00.000Z",
    "completed_at": "2024-12-18T10:35:12.000Z"
  },
  "livemode": true
}

adverse_event.reported

Sent when a patient reports an adverse event during a call. Contains structured adverse event data for pharmacovigilance workflows and FDA reporting requirements.

Critical Safety Events

When severity is severe or life_threatening, the requires_immediate_action flag is true. Configure your integration to trigger immediate alerts to medical safety staff.

Payload Fields
data.case_numberstring

Unique adverse event case identifier for tracking.

data.severitystring

Event severity: mild, moderate, severe,life_threatening.

data.symptomsarray

List of reported symptoms.

data.medication_namestring

Medication associated with the adverse event.

data.occurrence_datestring

When the adverse event occurred (ISO 8601 date).

data.requires_immediate_actionboolean

True for severe/life-threatening events requiring urgent response.

adverse_event.reported payload
{
  "id": "evt_c3d4e5f6g7h8i9j0",
  "event": "adverse_event.reported",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:33:00.000Z",
  "data": {
    "object": "adverse_event",
    "id": 234,
    "case_number": "AE-456-1734517980",
    "voice_call_id": 1847,
    "patient_id": 456,
    "patient": {
      "id": 456,
      "first_name": "John",
      "last_name": "Doe",
      "mrn": "MRN1456"
    },
    "severity": "moderate",
    "requires_immediate_action": false,
    "description": "Patient reported persistent headache and mild fatigue starting 3 days after infusion",
    "medication_name": "Rixomab",
    "symptoms": [
      "headache",
      "fatigue",
      "mild nausea"
    ],
    "occurrence_date": "2024-12-15",
    "reported_date": "2024-12-18T10:33:00.000Z",
    "status": "pending_review",
    "medical_team_notified": false
  },
  "livemode": true
}
Life-threatening event (urgent)
{
  "id": "evt_urgent123456789",
  "event": "adverse_event.reported",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T14:22:00.000Z",
  "data": {
    "object": "adverse_event",
    "case_number": "AE-789-1734531720",
    "severity": "life_threatening",
    "requires_immediate_action": true,
    "description": "Patient reporting difficulty breathing and chest tightness",
    "medication_name": "Humira",
    "symptoms": [
      "dyspnea",
      "chest_tightness",
      "facial_swelling"
    ],
    "status": "pending_review",
    "medical_team_notified": true
  },
  "livemode": true
}

pa.required

Triggered when a prior authorization requirement is identified during a benefits verification call or insurance outreach. Includes submission details and turnaround expectations.

Payload Fields
data.medication_namestring

Drug requiring prior authorization.

data.payerobject

Insurance payer information.

data.submission_methodsarray

Available PA submission methods (fax, portal, phone).

data.turnaround_daysinteger

Expected business days for PA decision.

pa.required payload
{
  "id": "evt_d4e5f6g7h8i9j0k1",
  "event": "pa.required",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:34:00.000Z",
  "data": {
    "object": "pa_requirement",
    "voice_call_id": 1847,
    "patient_id": 456,
    "medication_name": "Rixomab",
    "j_code": "J0000",
    "payer": {
      "id": "aetna",
      "name": "Aetna"
    },
    "plan": {
      "id": "aetna_ppo_2024",
      "name": "Aetna PPO Select"
    },
    "pa_on_file": false,
    "submission_methods": [
      {
        "method": "fax",
        "fax_number": "1-800-555-0199",
        "preferred": true
      },
      {
        "method": "portal",
        "portal_url": "https://portal.aetna.com/pa"
      }
    ],
    "turnaround_days": 5,
    "required_documents": [
      "Clinical notes",
      "Lab results",
      "Treatment history"
    ],
    "step_therapy_required": false,
    "identified_at": "2024-12-18T10:34:00.000Z"
  },
  "livemode": true
}

callback.requested

Sent when a patient requests a callback from a human specialist or requests an email summary of their call. Use this to route to your call center queue or trigger automated email workflows.

Payload Fields
data.callback_typestring

Type of callback: phone or email.

data.reasonstring

Reason for the callback request.

data.prioritystring

Urgency level: low, normal, high, urgent.

data.preferred_timestring

Patient's preferred callback time: morning, afternoon, evening.

callback.requested (phone)
{
  "id": "evt_e5f6g7h8i9j0k1l2",
  "event": "callback.requested",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:34:30.000Z",
  "data": {
    "object": "callback_request",
    "voice_call_id": 1847,
    "patient_id": 456,
    "patient": {
      "id": 456,
      "first_name": "John",
      "last_name": "Doe",
      "phone": "+14155551234"
    },
    "callback_type": "phone",
    "callback_phone": "+14155551234",
    "reason": "Patient has questions about PA appeal process",
    "priority": "high",
    "preferred_time": "afternoon",
    "call_summary": "Patient verified coverage for Rixomab. PA is required. Patient wants to discuss appeal options if PA is denied.",
    "requested_at": "2024-12-18T10:34:30.000Z"
  },
  "livemode": true
}
callback.requested (email summary)
{
  "id": "evt_f6g7h8i9j0k1l2m3",
  "event": "callback.requested",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:35:00.000Z",
  "data": {
    "object": "callback_request",
    "callback_type": "email",
    "callback_email": "patient@email.com",
    "patient_id": 456,
    "reason": "Send written summary of benefits verification",
    "priority": "normal",
    "call_summary": "Coverage confirmed for Rixomab with 20% coinsurance. PA required - provider will submit.",
    "include_in_email": [
      "coverage_details",
      "pa_requirements",
      "cost_estimate",
      "next_steps"
    ]
  },
  "livemode": true
}

benefits.verified

Triggered when insurance benefits have been successfully verified. Benefits can be verified through multiple methods: EDI/clearinghouse, AI voice call, or a combination. The verification_sources array indicates which methods were used, and field_sources shows the provenance of each field.

Key Payload Fields
data.verification_sourcesarray

Methods used for verification: edi, voice, pdf. Multiple sources may be used for comprehensive verification.

data.voice_call_idinteger | null

Voice call ID if voice verification was used, otherwise null.

data.field_sourcesobject

Maps each benefit field to its source method (edi, voice, pdf) for audit trails.

Verification Methods

MethodSpeedData Available
edi~2 secondsEligibility, deductibles, copays, network status
voice3-8 minutesPA requirements, accumulators, step therapy, submission details
pdf~10 secondsPlan details, tier copays, formulary status
EDI-only verification (fast path)
{
  "id": "evt_g7h8i9j0k1l2m3n4",
  "event": "benefits.verified",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:36:00.000Z",
  "data": {
    "object": "benefit_verification",
    "id": 892,
    "patient_id": 456,
    "payer": {
      "id": "aetna",
      "name": "Aetna"
    },
    "plan": {
      "id": "aetna_ppo_2024",
      "name": "Aetna PPO Select"
    },
    "verification_sources": ["edi"],
    "voice_call_id": null,
    "status": "completed",
    "coverage_status": "active",
    "in_network": true,
    "deductible_individual": 1500,
    "deductible_met_ytd": 1500,
    "out_of_pocket_max": 6000,
    "out_of_pocket_met_ytd": 2340,
    "copay_specialist": 40,
    "coinsurance_percent": 20,
    "pa_required": null,
    "field_sources": {
      "coverage_status": "edi",
      "deductible_individual": "edi",
      "deductible_met_ytd": "edi",
      "copay_specialist": "edi",
      "coinsurance_percent": "edi"
    },
    "verified_at": "2024-12-18T10:36:02.000Z",
    "duration_seconds": 2
  },
  "livemode": true
}
Voice call verification (PA details)
{
  "id": "evt_h8i9j0k1l2m3n4o5",
  "event": "benefits.verified",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:41:00.000Z",
  "data": {
    "object": "benefit_verification",
    "patient_id": 456,
    "medication_name": "Rixomab",
    "verification_sources": ["voice"],
    "voice_call_id": 1848,
    "status": "completed",
    "pa_required": true,
    "pa_on_file": false,
    "pa_submission_method": "fax",
    "pa_fax_number": "1-800-555-0199",
    "pa_turnaround_days": 5,
    "step_therapy_required": false,
    "in_network": true,
    "coinsurance_percent": 20,
    "deductible_met": true,
    "reference_number": "REF-2024121800456",
    "rep_name": "Sarah M.",
    "field_sources": {
      "pa_required": "voice",
      "pa_submission_method": "voice",
      "pa_fax_number": "voice",
      "coinsurance_percent": "voice",
      "step_therapy_required": "voice"
    },
    "verified_at": "2024-12-18T10:41:00.000Z",
    "duration_seconds": 312
  },
  "livemode": true
}
Multi-method verification (EDI + Voice)
{
  "id": "evt_i9j0k1l2m3n4o5p6",
  "event": "benefits.verified",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T10:45:00.000Z",
  "data": {
    "object": "benefit_verification",
    "patient_id": 456,
    "medication_name": "Rixomab",
    "verification_sources": ["edi", "voice"],
    "voice_call_id": 1848,
    "status": "completed",
    "coverage_status": "active",
    "in_network": true,
    "deductible_individual": 1500,
    "deductible_met_ytd": 1500,
    "out_of_pocket_max": 6000,
    "out_of_pocket_met_ytd": 2340,
    "copay_specialist": 40,
    "coinsurance_percent": 20,
    "pa_required": true,
    "pa_on_file": false,
    "pa_submission_method": "fax",
    "pa_fax_number": "1-800-555-0199",
    "pa_turnaround_days": 5,
    "step_therapy_required": false,
    "reference_number": "REF-2024121800456",
    "rep_name": "Sarah M.",
    "field_sources": {
      "coverage_status": "edi",
      "deductible_individual": "edi",
      "deductible_met_ytd": "edi",
      "copay_specialist": "edi",
      "coinsurance_percent": "edi",
      "pa_required": "voice",
      "pa_submission_method": "voice",
      "pa_fax_number": "voice",
      "step_therapy_required": "voice"
    },
    "method_results": [
      {
        "method": "edi",
        "status": "completed",
        "duration_seconds": 2,
        "completed_at": "2024-12-18T10:36:02.000Z"
      },
      {
        "method": "voice",
        "status": "completed",
        "duration_seconds": 312,
        "completed_at": "2024-12-18T10:45:00.000Z"
      }
    ],
    "verified_at": "2024-12-18T10:45:00.000Z"
  },
  "livemode": true
}

Webhook Configuration

Register and manage webhook endpoints through the API. You can subscribe to specific event types and configure separate endpoints for different event categories.

Register a webhook

POST/webhooks
Body Parameters
urlstringrequired

HTTPS endpoint URL to receive webhook events. Must use TLS 1.2+.

eventsarrayrequired

List of event types to subscribe to. Use ["*"] for all events.

descriptionstringoptional

Human-readable description of the webhook purpose.

metadataobjectoptional

Arbitrary key-value pairs for your reference.

List webhooks

GET/webhooks

Retrieve a webhook

GET/webhooks/{webhook_id}

Update a webhook

PATCH/webhooks/{webhook_id}

Delete a webhook

DELETE/webhooks/{webhook_id}
Register webhook request
curl https://api.isomera.ai/v1/webhooks \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/isomera",
    "events": [
      "call.completed",
      "adverse_event.reported",
      "pa.required"
    ],
    "description": "Production EHR integration"
  }'
Response
{
  "id": "wh_1a2b3c4d5e6f7g8h",
  "object": "webhook",
  "url": "https://your-server.com/webhooks/isomera",
  "events": [
    "call.completed",
    "adverse_event.reported",
    "pa.required"
  ],
  "description": "Production EHR integration",
  "status": "active",
  "secret": "whsec_7x8y9z0a1b2c3d4e5f6g7h8i9j0k",
  "created_at": "2024-12-18T10:00:00.000Z"
}
Subscribe to all events
{
  "url": "https://your-server.com/webhooks",
  "events": ["*"]
}

Signature Verification

Every webhook request includes a signature for verifying authenticity. Always validate signatures before processing webhook payloads to prevent spoofing attacks.

Request Headers

HeaderDescription
X-Isomera-SignatureHMAC-SHA256 signature of the request body
X-Isomera-TimestampUnix timestamp when the request was sent
X-Isomera-EventEvent type (e.g., call.completed)
X-Isomera-Delivery-IdUnique delivery ID for idempotency

Signature Format

The signature is computed as sha256=HMAC-SHA256(timestamp.payload, secret). The signed payload is the timestamp concatenated with the raw request body.

Prevent replay attacks

Reject requests where X-Isomera-Timestamp is more than 5 minutes old to prevent replay attacks.

Python signature verification
import hmac
import hashlib
import time

def verify_webhook(
    payload: bytes,
    signature: str,
    timestamp: str,
    secret: str,
    tolerance: int = 300  # 5 minutes
) -> bool:
    """Verify Isomera webhook signature."""

    # Check timestamp to prevent replay attacks
    current_time = int(time.time())
    request_time = int(timestamp)
    if abs(current_time - request_time) > tolerance:
        return False

    # Compute expected signature
    signed_payload = f"{timestamp}.".encode() + payload
    expected = hmac.new(
        secret.encode(),
        signed_payload,
        hashlib.sha256
    ).hexdigest()

    # Compare signatures
    return hmac.compare_digest(
        f"sha256={expected}",
        signature
    )

# Flask example
@app.route("/webhooks/isomera", methods=["POST"])
def handle_webhook():
    payload = request.get_data()
    signature = request.headers.get("X-Isomera-Signature")
    timestamp = request.headers.get("X-Isomera-Timestamp")
    delivery_id = request.headers.get("X-Isomera-Delivery-Id")

    if not verify_webhook(payload, signature, timestamp, WEBHOOK_SECRET):
        return "Invalid signature", 401

    # Check for duplicate delivery
    if is_duplicate(delivery_id):
        return "Already processed", 200

    event = json.loads(payload)

    # Route to handler based on event type
    match event["event"]:
        case "call.completed":
            handle_call_completed(event["data"])
        case "adverse_event.reported":
            handle_adverse_event(event["data"])
        case "pa.required":
            handle_pa_required(event["data"])

    return "OK", 200
Node.js signature verification
const crypto = require('crypto');

function verifyWebhook(payload, signature, timestamp, secret) {
  // Check timestamp (5 minute tolerance)
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
    return false;
  }

  // Compute expected signature
  const signedPayload = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`),
    Buffer.from(signature)
  );
}

// Express example
app.post('/webhooks/isomera', (req, res) => {
  const signature = req.headers['x-isomera-signature'];
  const timestamp = req.headers['x-isomera-timestamp'];

  if (!verifyWebhook(req.rawBody, signature, timestamp, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;
  console.log(`Received ${event.event}`);

  // Process event...
  res.status(200).send('OK');
});

Retry Policy

Failed webhook deliveries are automatically retried with exponential backoff. A delivery is considered failed if your endpoint returns a non-2xx status code or doesn't respond within 30 seconds.

Retry Schedule

AttemptDelay After Failure
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 hours

After 5 failed attempts, the webhook delivery is marked as failed and won't be retried automatically. You can manually retry failed deliveries through the API.

View delivery attempts

GET/webhooks/{webhook_id}/deliveries

Retry a failed delivery

POST/webhooks/deliveries/{delivery_id}/retry
Best practices
1. RESPOND QUICKLY
   Return 2xx status within 30 seconds. Process
   events asynchronously in a background queue.

2. HANDLE DUPLICATES
   Use X-Isomera-Delivery-Id for idempotency.
   Store processed IDs to detect duplicates.

3. VERIFY SIGNATURES
   Always validate HMAC signature before processing.
   Never trust unverified payloads.

4. CHECK TIMESTAMPS
   Reject requests with timestamps > 5 minutes old
   to prevent replay attacks.

5. MONITOR FAILURES
   Set up alerts for repeated delivery failures.
   Review webhook logs regularly.

6. USE HTTPS
   Webhook URLs must use HTTPS with TLS 1.2+.
   Self-signed certificates are not supported.
View failed deliveries
curl https://api.isomera.ai/v1/webhooks/wh_abc123/deliveries \
  -H "Authorization: Bearer sk_live_..." \
  -G -d "status=failed" -d "limit=10"
Failed delivery response
{
  "data": [
    {
      "id": "del_x1y2z3a4b5c6d7e8",
      "webhook_id": "wh_abc123",
      "event_id": "evt_a1b2c3d4e5f6g7h8",
      "event_type": "call.completed",
      "status": "failed",
      "attempts": 5,
      "last_attempt_at": "2024-12-19T10:30:00.000Z",
      "last_error": "Connection timeout",
      "next_retry_at": null
    }
  ],
  "has_more": false
}

Testing Webhooks

Use the webhook testing endpoint to send sample events to your endpoint during development. Test events are clearly marked with livemode: false.

Send a test event

POST/webhooks/{webhook_id}/test
Body Parameters
event_typestringrequired

Event type to simulate (e.g., "call.completed").

Send test event
curl -X POST https://api.isomera.ai/v1/webhooks/wh_abc123/test \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"event_type": "call.completed"}'
Test event payload (livemode: false)
{
  "id": "evt_test_1234567890",
  "event": "call.completed",
  "api_version": "2024-12-01",
  "created_at": "2024-12-18T12:00:00.000Z",
  "data": {
    "object": "voice_call",
    "id": 99999,
    "call_id": "conv_test_abcdefghij",
    "status": "completed",
    "patient_id": null,
    "duration_seconds": 180,
    "call_outcome": "coverage_confirmed"
  },
  "livemode": false
}