> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heliosai.health/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive analysis results and verify webhook signatures

Helios delivers analysis results to your webhook URL asynchronously. This guide covers webhook payloads, signature verification, and best practices.

## How Webhooks Work

When you submit a request to the Helios API:

1. You receive an immediate response with a `runId`
2. Analysis runs in the background (5-8 minutes for Light Agent, 10-13 minutes for Deep Agent, 3-6 minutes for Lab Results Agent)
3. Results are delivered to your `webhookUrl` via HTTP POST
4. Your endpoint should return `200 OK` to acknowledge receipt

## Webhook Headers

Every webhook includes these headers for security and tracking:

| Header               | Description              | Example             |
| -------------------- | ------------------------ | ------------------- |
| `X-Helios-Signature` | HMAC-SHA256 signature    | `sha256=a1b2c3...`  |
| `X-Helios-Timestamp` | Unix timestamp (seconds) | `1705320000`        |
| `X-Helios-Run-ID`    | Unique run identifier    | `550e8400-e29b-...` |
| `Content-Type`       | Always JSON              | `application/json`  |

## Webhook Payloads

### Success Payload (Light Agent)

When Light Agent analysis completes successfully:

```json theme={null}
{
  "runId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "agent": "light",
  "timestamp": "2025-01-15T12:01:30.000Z",
  "sequenceNumber": 7,
  "result": {
    "selectedElements": [
      {
        "item": "Hemoglobin A1c",
        "category": "Laboratory Value",
        "patient_value": "7.2%",
        "matching_reason": "Elevated HbA1c indicates suboptimal glycemic control"
      }
    ],
    "patientResponse": "Your recent lab results show...",
    "clinicianResponse": "The patient presents with...",
    "citations": {
      "patient": {
        "1": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/12345678/",
          "title": "Glycemic Control in Type 2 Diabetes",
          "metadata": { "type": "pubmed" }
        },
        "2": {
          "url": "https://doi.org/10.2337/dc24-S006",
          "title": "ADA Standards of Care in Diabetes — Glycemic Goals and Hypoglycemia",
          "metadata": {
            "type": "guideline",
            "source": "ADA",
            "author": "American Diabetes Association",
            "doi": "10.2337/dc24-S006",
            "pubdate": "2024-01-01",
            "abstract_snippet": "Glycemic management is primarily assessed with the A1C test...",
            "specialties": ["Endocrinology"]
          }
        }
      },
      "clinician": {
        "1": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/87654321/",
          "title": "ADA Standards of Care 2025",
          "metadata": { "type": "pubmed" }
        },
        "2": {
          "url": "https://doi.org/10.2337/dc24-S009",
          "title": "ADA Standards of Care — Pharmacologic Approaches to Glycemic Treatment",
          "metadata": {
            "type": "guideline",
            "source": "ADA",
            "doi": "10.2337/dc24-S009",
            "specialties": ["Endocrinology"]
          }
        }
      }
    }
  }
}
```

### Success Payload (Deep Agent)

Deep Agent responses include additional research task information:

```json theme={null}
{
  "runId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "agent": "deep",
  "timestamp": "2025-01-15T12:15:30.000Z",
  "sequenceNumber": 9,
  "result": {
    "selectedElements": [
      {
        "item": "Hemoglobin A1c",
        "category": "Laboratory Value",
        "patient_value": "7.2%",
        "matching_reason": "Elevated HbA1c indicates suboptimal glycemic control"
      }
    ],
    "patientResponse": "Your recent lab results show...",
    "clinicianResponse": "The patient presents with...",
    "researchTasks": [
      "Research current ADA guidelines for glycemic control in Type 2 Diabetes",
      "Investigate medication intensification strategies for patients with HbA1c > 7%",
      "Analyze cardiovascular risk factors and preventive measures"
    ],
    "citations": {
      "patient": {
        "1": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/12345678/",
          "title": "Glycemic Control in Type 2 Diabetes",
          "metadata": { "type": "pubmed" }
        },
        "2": {
          "url": "https://doi.org/10.2337/dc24-S006",
          "title": "ADA Standards of Care — Glycemic Goals and Hypoglycemia",
          "metadata": {
            "type": "guideline",
            "source": "ADA",
            "doi": "10.2337/dc24-S006",
            "specialties": ["Endocrinology"]
          }
        }
      },
      "clinician": {
        "1": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/87654321/",
          "title": "ADA Standards of Care 2025",
          "metadata": { "type": "pubmed" }
        },
        "2": {
          "url": "https://doi.org/10.1161/CIR.0000000000001168",
          "title": "2023 AHA/ACC Guideline for Heart Failure Management",
          "metadata": {
            "type": "guideline",
            "source": "ACC/AHA",
            "doi": "10.1161/CIR.0000000000001168",
            "specialties": ["Cardiology"]
          }
        }
      }
    }
  }
}
```

### Success Payload (Lab Results Agent)

Lab Results Agent responses include the analyzed labs and metadata:

```json theme={null}
{
  "runId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "agent": "lab-results",
  "timestamp": "2025-01-15T12:05:00.000Z",
  "sequenceNumber": 8,
  "result": {
    "labResults": [
      {
        "name": "Creatinine",
        "value": "1.8 mg/dL",
        "flag": "High",
        "referenceRange": "0.7-1.3 mg/dL",
        "date": "2025-01-15",
        "category": "Renal"
      }
    ],
    "patientResponse": "Your kidney function tests show...",
    "clinicianResponse": "The patient demonstrates Stage 3b CKD...",
    "citations": {
      "patient": {
        "1": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/12345678/",
          "title": "KDIGO Guidelines for CKD Management",
          "metadata": { "type": "pubmed" }
        },
        "2": {
          "url": "https://doi.org/10.1016/j.kint.2024.01.006",
          "title": "KDIGO 2024 Clinical Practice Guideline for CKD Evaluation and Management",
          "metadata": {
            "type": "guideline",
            "source": "KDIGO",
            "doi": "10.1016/j.kint.2024.01.006",
            "specialties": ["Nephrology"]
          }
        },
        "3": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/34567890/",
          "title": "Creatinine Clearance as a Marker of GFR in CKD",
          "metadata": {
            "type": "insight",
            "source": "pubmed.ncbi.nlm.nih.gov",
            "author": "Published in Kidney International",
            "pubdate": "2023"
          }
        }
      },
      "clinician": {
        "1": {
          "url": "https://pubmed.ncbi.nlm.nih.gov/87654321/",
          "title": "CKD Progression Risk Factors",
          "metadata": { "type": "pubmed" }
        },
        "2": {
          "url": "https://doi.org/10.1016/j.kint.2024.01.006",
          "title": "KDIGO 2024 Clinical Practice Guideline for CKD Evaluation and Management",
          "metadata": {
            "type": "guideline",
            "source": "KDIGO",
            "doi": "10.1016/j.kint.2024.01.006",
            "specialties": ["Nephrology"]
          }
        }
      }
    },
    "metadata": {
      "labsReceived": 15,
      "labsAnalyzed": 15,
      "insightsMatched": 8
    }
  }
}
```

<Note>
  Lab Results Agent does not include `selectedElements` since all provided labs are analyzed directly. Instead, it includes `labResults` (the labs that were analyzed) and `metadata` with processing statistics.
</Note>

### Error Payload

When analysis fails:

```json theme={null}
{
  "runId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "error",
  "timestamp": "2025-01-15T12:01:30.000Z",
  "sequenceNumber": 3,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid health data format"
  }
}
```

### Status Update Payload

During processing, you receive status updates at each step:

```json theme={null}
{
  "runId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "processing",
  "agent": "light",
  "step": 2,
  "stepName": "Analyzing health data",
  "timestamp": "2025-01-15T12:00:45.000Z",
  "sequenceNumber": 2
}
```

**Important:** The `sequenceNumber` field is a monotonically increasing number that allows you to order webhooks correctly. Webhooks may arrive out of order due to network conditions, but you can sort them by `sequenceNumber` to reconstruct the correct sequence. The `timestamp` field can also be used as a fallback for ordering.

### Processing Steps

The following steps are sent during processing:

**Light Agent Steps:**

| Step | Name                                                   |
| ---- | ------------------------------------------------------ |
| 1    | Processing started                                     |
| 2    | Analyzing health data                                  |
| 3    | Selecting relevant elements                            |
| 4    | Researching medical literature and clinical guidelines |
| 5    | Generating patient response                            |
| 6    | Generating clinician response                          |
| 7    | Finalizing results                                     |
| 8    | Delivering final results                               |

**Deep Agent Steps:**

| Step | Name                        | Description                                          |
| ---- | --------------------------- | ---------------------------------------------------- |
| 1    | Processing started          | Request received and validated                       |
| 2    | Analyzing health data       | Extracting insights from EHR data                    |
| 3    | Selecting relevant elements | AI selecting important health elements               |
| 4    | Routing to Deep Agent       | Handing off to research agent                        |
| 5    | Starting research tasks     | Beginning parallel literature and guideline research |
| 6    | Research complete           | All research tasks finished                          |
| 7    | Generating responses        | Creating clinician and patient reports               |
| 8    | Finalizing results          | Preparing final output                               |

<Note>
  Steps 1-4 are sent by the initial API server. Steps 5-8 and the final result are sent by the Deep Agent research processor. This architecture allows the Deep Agent to run for up to 16 minutes without timeout constraints.
</Note>

**Lab Results Agent Steps:**

| Step | Name                                                 |
| ---- | ---------------------------------------------------- |
| 1    | Processing started                                   |
| 2    | Analyzing lab results                                |
| 3    | Prioritizing findings                                |
| 4    | Generating search queries                            |
| 5    | Searching medical literature and clinical guidelines |
| 6    | Processing search results                            |
| 7    | Generating responses                                 |
| 8    | Final webhook delivered                              |

## Error Codes

| Code                       | Description                               |
| -------------------------- | ----------------------------------------- |
| `VALIDATION_ERROR`         | Invalid request data                      |
| `AUTH_ERROR`               | Authentication failed                     |
| `RATE_LIMIT_ERROR`         | Too many requests                         |
| `CONCURRENCY_LIMIT_ERROR`  | Too many concurrent requests              |
| `VERTEX_AI_ERROR`          | AI service error                          |
| `LITSENSE_ERROR`           | Literature search error                   |
| `EXA_ERROR`                | Research search error                     |
| `FIREBASE_ERROR`           | Deep Agent processing error               |
| `ELEMENT_SELECTION_ERROR`  | Failed to select relevant health elements |
| `CLINICIAN_RESPONSE_ERROR` | Failed to generate clinician response     |
| `PATIENT_RESPONSE_ERROR`   | Failed to generate patient response       |
| `TIMEOUT_ERROR`            | Analysis timed out                        |
| `INTERNAL_ERROR`           | Server error                              |
| `NO_DATA`                  | Insufficient health data provided         |

## Signature Verification

<Warning>
  Always verify webhook signatures in production to ensure requests are authentic and haven't been tampered with.
</Warning>

### Verification Steps

1. **Get the raw request body** - Use the raw body string, not parsed JSON
2. **Check the timestamp** - Reject webhooks older than 5 minutes
3. **Compute the expected signature** - `sha256=HMAC-SHA256(webhook_secret, raw_body)`
4. **Compare signatures** - Use timing-safe comparison

### Implementation Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  import { createHmac, timingSafeEqual } from 'crypto';

  function verifyWebhook(rawBody, signature, timestamp, webhookSecret) {
    // Check timestamp is recent (within 5 minutes)
    const now = Math.floor(Date.now() / 1000);
    const webhookTime = parseInt(timestamp, 10);
    if (Math.abs(now - webhookTime) > 300) {
      console.error('Webhook timestamp too old');
      return false;
    }

    // Compute expected signature
    const expected = 'sha256=' + createHmac('sha256', webhookSecret)
      .update(rawBody)
      .digest('hex');

    // Timing-safe comparison
    if (expected.length !== signature.length) {
      return false;
    }

    return timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );
  }

  // Express.js example
  app.post('/webhook/helios', express.raw({ type: 'application/json' }), (req, res) => {
    const rawBody = req.body.toString();
    const signature = req.headers['x-helios-signature'];
    const timestamp = req.headers['x-helios-timestamp'];

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

    const payload = JSON.parse(rawBody);
    // Process webhook...
    
    res.status(200).send('OK');
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import time
  from flask import Flask, request

  def verify_webhook(raw_body, signature, timestamp, webhook_secret):
      # Check timestamp is recent (within 5 minutes)
      now = int(time.time())
      webhook_time = int(timestamp)
      if abs(now - webhook_time) > 300:
          print("Webhook timestamp too old")
          return False

      # Compute expected signature
      expected = "sha256=" + hmac.new(
          webhook_secret.encode(),
          raw_body,
          hashlib.sha256
      ).hexdigest()

      # Timing-safe comparison
      return hmac.compare_digest(expected, signature)

  @app.route('/webhook/helios', methods=['POST'])
  def webhook():
      raw_body = request.get_data()
      signature = request.headers.get('X-Helios-Signature', '')
      timestamp = request.headers.get('X-Helios-Timestamp', '')

      if not verify_webhook(raw_body, signature, timestamp, os.environ['WEBHOOK_SECRET']):
          return 'Invalid signature', 401

      payload = request.get_json()
      # Process webhook...

      return 'OK', 200
  ```

  ```go Go theme={null}
  package main

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "fmt"
      "math"
      "strconv"
      "time"
  )

  func verifyWebhook(rawBody []byte, signature, timestamp, webhookSecret string) bool {
      // Check timestamp is recent (within 5 minutes)
      now := time.Now().Unix()
      webhookTime, err := strconv.ParseInt(timestamp, 10, 64)
      if err != nil {
          return false
      }
      if math.Abs(float64(now-webhookTime)) > 300 {
          return false
      }

      // Compute expected signature
      mac := hmac.New(sha256.New, []byte(webhookSecret))
      mac.Write(rawBody)
      expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))

      // Timing-safe comparison
      return hmac.Equal([]byte(expected), []byte(signature))
  }
  ```
</CodeGroup>

## Retry Policy

Helios retries failed webhook deliveries with exponential backoff:

| Attempt | Delay      | Total Time |
| ------- | ---------- | ---------- |
| 1       | Immediate  | 0s         |
| 2       | 5 seconds  | 5s         |
| 3       | 15 seconds | 20s        |

After 3 failed attempts, the webhook is logged but not retried further.

<Tip>
  Your webhook endpoint should return a `2xx` status code within 30 seconds to be considered successful.
</Tip>

## Best Practices

<AccordionGroup>
  <Accordion title="Return 200 quickly" icon="bolt">
    Process webhooks asynchronously. Return `200 OK` immediately, then handle the data in a background job.

    ```javascript theme={null}
    app.post('/webhook/helios', (req, res) => {
      // Acknowledge immediately
      res.status(200).send('OK');
      
      // Process in background
      processWebhookAsync(req.body);
    });
    ```
  </Accordion>

  <Accordion title="Handle duplicates" icon="copy">
    In rare cases, you may receive the same webhook twice. Use the `runId` to deduplicate:

    ```javascript theme={null}
    const processedRuns = new Set();

    if (processedRuns.has(payload.runId)) {
      return; // Already processed
    }
    processedRuns.add(payload.runId);
    ```
  </Accordion>

  <Accordion title="Order webhooks correctly" icon="sort">
    Webhooks may arrive out of order due to network conditions. Use the `sequenceNumber` field to sort them correctly:

    ```javascript theme={null}
    const webhooks = [];

    // Store webhooks as they arrive
    app.post('/webhook/helios', (req, res) => {
      const payload = req.body;
      webhooks.push(payload);
      
      // Sort by sequenceNumber to get correct order
      webhooks.sort((a, b) => (a.sequenceNumber || 0) - (b.sequenceNumber || 0));
      
      res.status(200).send('OK');
    });
    ```

    The `sequenceNumber` starts at 1 and increments for each webhook sent during a run. The final `completed` or `error` webhook will have the highest `sequenceNumber`.
  </Accordion>

  <Accordion title="Log webhook events" icon="file-lines">
    Log all incoming webhooks for debugging and audit purposes. Store the `runId`, `status`, and timestamp.
  </Accordion>

  <Accordion title="Use HTTPS" icon="lock">
    Your webhook endpoint must use HTTPS in production. Self-signed certificates are not supported.
  </Accordion>
</AccordionGroup>

## Getting Your Webhook Secret

Your webhook secret is used to verify webhook signatures and ensure requests are authentic.

**To generate your webhook secret:**

1. Log in to your [Helios Dashboard](https://www.heliosai.health)
2. Go to **Settings** (API Keys page)
3. In the **Webhook Secret** section, click **Generate Secret**
4. Copy and securely store the secret—it will only be shown once

<Warning>
  If you regenerate your webhook secret, the previous secret is immediately invalidated. Make sure to update your server configuration before regenerating.
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="EHR Agent" icon="file-medical" href="/api-reference/ehr-agent">
    Full EHR Agent request and response reference.
  </Card>

  <Card title="Lab Results Agent" icon="flask-vial" href="/api-reference/lab-results">
    Full Lab Results Agent request and response reference.
  </Card>

  <Card title="API Overview" icon="code" href="/api-reference/introduction">
    Shared concepts: auth, errors, privacy.
  </Card>

  <Card title="Dashboard" icon="gauge" href="https://www.heliosai.health">
    View your API usage and logs.
  </Card>
</CardGroup>
