Skip to main content

Asynchronous Processing Overview

Asynchronous processing allows you to upload lab report documents and receive immediate responses while the AI analysis happens in the background. This approach provides better user experience for interactive applications and enables you to handle multiple requests efficiently.

How Asynchronous Processing Works

  1. Upload Document — send a POST request with wait_on_process: false (default)
  2. Immediate Response — receive a response with status: "processing", and a record_id
  3. Background Processing — AI models analyze the document (typically 10–60 seconds)
  4. Get Results — receive webhook notification with an analysis report when finished

Getting Results

Webhooks

Configure a webhook URL to receive real-time notifications when analysis completes: Request:
{
  "mime_type": "application/pdf",
  "body": "base64-encoded-document-data",
  "filename": "lab_report_2024_01_15.pdf",
  "wait_on_process": false
}
Immediate Response:
{
  "lab_report": {
    "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "status": "processing",
    "uploaded_at": "2024-01-15T10:30:04.521Z"
  }
}
Webhook Notification (when analysis completes):
{
  "application_user_id": "end_user_id1",
      "record_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "status": "completed",
      "collection_date": "2024-01-15",
      "result_date": "2024-01-16",
      "sections": [
        {
          "loinc_code": "58410-2",
          "loinc_common_name": "CBC panel - Blood by Automated count",
          "original_section_name": "HEMATOLOGY",
          "results": [
            {
              "loinc_code": "6690-2",
              "loinc_common_name": "Leukocytes [#/volume] in Blood",
              "original_test_name": "White Blood Count",
              "original_unit_name": "K/uL",
              "standard_unit_name": "K/uL",
              "value": 7.2,
              "within_normal_range": true
            }
          ]
        }
      ],
      "uploaded_at": "2024-01-15T10:30:04.521Z",
      "modified_at": "2024-01-15T10:30:12.132Z"
}

Alternative: Manual Checking

If webhooks are not available in your environment, you can check the status using the GET /lab_reports/{lab_report_id} endpoint:
GET /lab_reports/6ba7b810-9dad-11d1-80b4-00c04fd430c8

Webhook Configuration

Setup

Configure your webhook URL through the admin console. Your endpoint must:
  • Respond with HTTP 200 to acknowledge receipt
  • Handle POST requests with JSON payloads
  • Verify HMAC signatures for security
If requests fail, the system retries up to 5 times with exponential backoff.

Security

All webhook requests include HMAC SHA256 signature verification:
  • Headerx-body-signature contains the hex-encoded HMAC signature
  • Key — uses your application’s webhook signature key (configured in the admin console)
  • AlgorithmHMAC-SHA256(webhook_signature_key, request_body)
See Webhook Signature for more details.

Status Handling

Your webhook handler should handle different status values:
  • completed — analysis successful, lab report data are available
  • failed — analysis failed, check parsing_error field for details
When status is failed, check the parsing_error field for specific details about what prevented successful analysis.

Implementation Examples

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

const webhookSignatureKey = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE"

// LabReport represents the webhook payload structure
type LabReport struct {
	ApplicationUserID string    `json:"application_user_id"`
	RecordID          string    `json:"record_id"`
	Status            string    `json:"status"`
	ParsingError      string    `json:"parsing_error,omitempty"`
	CollectionDate    string    `json:"collection_date,omitempty"`
	UploadedAt        time.Time `json:"uploaded_at"`
	ModifiedAt        time.Time `json:"modified_at"`
	// Add other fields as needed
}

func main() {
	http.HandleFunc("/lab-reports-webhook", func(w http.ResponseWriter, r *http.Request) {
		// Verify HMAC signature
		signature := r.Header.Get("x-body-signature")
		if signature == "" {
			http.Error(w, "Missing signature", http.StatusBadRequest)
			return
		}

		// Read the request body
		body, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Failed to read body", http.StatusInternalServerError)
			return
		}

		// Calculate and verify HMAC
		hm := hmac.New(sha256.New, []byte(webhookSignatureKey))
		hm.Write(body)
		expectedSignature := hex.EncodeToString(hm.Sum(nil))
		
		if signature != expectedSignature {
			http.Error(w, "Invalid signature", http.StatusUnauthorized)
			return
		}

		// Parse lab report
		var record LabReport
		if err := json.Unmarshal(body, &record); err != nil {
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
			return
		}

		// Process the lab report
		fmt.Printf("Received lab report analysis: %s for user %s\n", record.Status, record.ApplicationUserID)
		if record.Status == "completed" {
			fmt.Printf("Lab report completed for record %s\n", record.RecordID)
			// Update your application with the results
		} else if record.Status == "failed" {
			fmt.Printf("Analysis failed: %s\n", record.ParsingError)
			// Handle failure case
		}

		// Respond with success
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})

	fmt.Println("Starting lab reports webhook server on port 8000")
	http.ListenAndServe(":8000", nil)
}
const crypto = require("crypto");
const express = require("express");

const WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE";
const app = express();

// Middleware to capture raw body for signature verification
app.use('/lab-reports-webhook', express.raw({type: 'application/json'}));

app.post('/lab-reports-webhook', (req, res) => {
  const signature = req.headers['x-body-signature'];
  
  if (!signature) {
    return res.status(400).send('Missing signature');
  }

  // Verify HMAC signature
  const hmac = crypto.createHmac('sha256', WEBHOOK_SIGNATURE_KEY);
  hmac.update(req.body);
  const expectedSignature = hmac.digest('hex');
  
  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }

  // Parse lab report
  try {
    const labReport = JSON.parse(req.body);
    
    console.log(`Received lab report analysis: ${labReport.status} for user ${labReport.application_user_id}`);
    
    if (labReport.status === 'completed') {
      console.log(`Lab report completed: ${labReport.record_id}`);
      
      // Update your application with the results
      updateLabReportData(labReport);
      
    } else if (labReport.status === 'failed') {
      console.log(`Analysis failed: ${labReport.parsing_error}`);
      
      // Handle failure case
      handleAnalysisFailure(labReport.record_id, labReport.application_user_id);
    }
    
    res.status(200).send('OK');
  } catch (error) {
    res.status(400).send('Invalid JSON');
  }
});

function updateLabReportData(labReport) {
  // Your application logic to store/process the lab report data
  console.log('Updating lab report data...');
}

function handleAnalysisFailure(recordId, applicationUserId) {
  // Your application logic to handle failed analysis
  console.log('Handling analysis failure...');
}

app.listen(8000, () => {
  console.log('Lab reports webhook server running on port 8000');
});
import hmac
import hashlib
import json
from datetime import datetime
from flask import Flask, request, Response

app = Flask(__name__)
WEBHOOK_SIGNATURE_KEY = "YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE"

def verify_signature(body: bytes, signature: str) -> bool:
    """Verify the HMAC signature of the request body."""
    expected_signature = hmac.new(
        WEBHOOK_SIGNATURE_KEY.encode('utf-8'),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected_signature)

@app.route('/lab-reports-webhook', methods=['POST'])
def handle_lab_report_webhook():
    # Get signature from headers
    signature = request.headers.get('x-body-signature')
    if not signature:
        return Response('Missing signature', status=400)
    
    # Get raw request body
    body = request.get_data()
    
    # Verify signature
    if not verify_signature(body, signature):
        return Response('Invalid signature', status=401)
    
    # Parse lab report
    try:
        lab_report = json.loads(body)
        
        print(f"Received lab report analysis: {lab_report['status']} for user {lab_report['application_user_id']}")
        
        if lab_report['status'] == 'completed':
            print(f"Lab report completed: {lab_report['record_id']}")
            
            # Update your application with the results
            update_lab_report_data(lab_report)
            
        elif lab_report['status'] == 'failed':
            print(f"Analysis failed: {lab_report.get('parsing_error', 'Unknown error')}")
            
            # Handle failure case
            handle_analysis_failure(lab_report['record_id'], lab_report['application_user_id'])
        
        return Response('OK', status=200)
        
    except json.JSONDecodeError:
        return Response('Invalid JSON', status=400)

def update_lab_report_data(lab_report):
    """Update your application with the lab report analysis results."""
    print("Updating lab report data...")
    # Your application logic here

def handle_analysis_failure(record_id, application_user_id):
    """Handle failed lab report analysis."""
    print("Handling analysis failure...")
    # Your application logic here

if __name__ == '__main__':
    print('Starting lab reports webhook server on port 8000')
    app.run(host='0.0.0.0', port=8000)
<?php

class LabReportWebhook {
    private const WEBHOOK_SIGNATURE_KEY = 'YOUR_WEBHOOK_SECRET_FROM_ADMIN_CONSOLE';
    
    public function handleRequest() {
        // Verify HMAC signature
        $signature = $_SERVER['HTTP_X_BODY_SIGNATURE'] ?? '';
        if (empty($signature)) {
            http_response_code(400);
            echo 'Missing signature';
            return;
        }

        // Read request body
        $body = file_get_contents('php://input');
        if ($body === false) {
            http_response_code(500);
            echo 'Failed to read body';
            return;
        }

        // Calculate and verify HMAC
        $expectedSignature = hash_hmac('sha256', $body, self::WEBHOOK_SIGNATURE_KEY);
        if ($signature !== $expectedSignature) {
            http_response_code(401);
            echo 'Invalid signature';
            return;
        }

        // Parse lab report
        $labReport = json_decode($body, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            http_response_code(400);
            echo 'Invalid JSON';
            return;
        }

        // Process the lab report
        echo "Received lab report analysis: {$labReport['status']} for user {$labReport['application_user_id']}\n";
        
        if ($labReport['status'] === 'completed') {
            echo "Lab report completed: {$labReport['record_id']}\n";
            
            // Update your application with the results
            $this->updateLabReportData($labReport);
            
        } elseif ($labReport['status'] === 'failed') {
            $parsingError = $labReport['parsing_error'] ?? 'Unknown error';
            echo "Analysis failed: {$parsingError}\n";
            
            // Handle failure case
            $this->handleAnalysisFailure($labReport['record_id'], $labReport['application_user_id']);
        }

        http_response_code(200);
        echo 'OK';
    }
    
    private function updateLabReportData($labReport) {
        // Your application logic to store/process the lab report data
        echo "Updating lab report data...\n";
    }
    
    private function handleAnalysisFailure($recordId, $applicationUserId) {
        // Your application logic to handle failed analysis
        echo "Handling analysis failure...\n";
    }
}

// Handle the webhook request
$webhook = new LabReportWebhook();
$webhook->handleRequest();
?>
For complete API specifications and additional configuration options, see the Implementation Guide.