> For the complete documentation index, see [llms.txt](https://dataqueue.gitbook.io/voicehub-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dataqueue.gitbook.io/voicehub-docs/api-reference/call-webhook-1.md).

# Call Studio Webhook

### Configuring Call Studio Webhook through Voicehub

In voicehub, each agent can be configured to send webhook requests per calls studio job. This configuration can be found under Call Studio Sidebar -> Configuration -> APIs tab

<figure><img src="/files/MT9XE4HN8B5uEFSLJA5X" alt=""><figcaption></figcaption></figure>

Configure your webhook settings in your Call Studio workspace settings. The webhook will be called whenever your Call Studio jobs have their status changed or analysis complete.

1. **Webhook URL:** URL of endpoint on your server to handle Call Studio job-related events
2. **Secret**: Secret key to authenticate the webhook call. This is sent through the header `x-webhook-secret`
3. **Retrying Aattempts**: Number of retries to send webhook events in case of failures (default: 3)
4. **Timeout**: Request to your Call Studio webhook timeout in milliseconds (default: 10000ms)
5. **Retry Delay MS**: Delay in milliseconds between two retries in case of failures (default: 1000ms)

### Events sent through Call Studio webhook

VoiceHub sends webhook events for the following Call Studio job-related events:

#### 1. CallStudioJobStatusChanged

Triggered when a Call Studio job's status changes (e.g., when transcription starts, completes, or fails).

**Event Type**: `CallStudioJobStatusChanged`

**Payload Structure**:

```json
{
  "eventType": "CallStudioJobStatusChanged",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "jobId": "job_123456",
  "agentId": "workspace_789",
  "status": "completed"
}
```

**Payload Fields**:

* `eventType` (string): Always `"CallStudioJobStatusChanged"`
* `timestamp` (string): ISO 8601 timestamp of when the event occurred
* `eventId` (string): Unique identifier for this webhook event
* `jobId` (string): Unique identifier of the Call Studio job
* `agentId` (string): Workspace ID where the job was created
* `status` (string): Current status of the job. Possible values:
  * `pending`: Job is pending processing (transcription/analysis)
  * `completed`: Job has completed successfully
  * `failed`: Job processing failed

#### 2. CallStudioJobAnalysisResultReady

Triggered when Call Studio job analysis is completed and ready.

**Event Type**: `CallStudioJobAnalysisResultReady`

**Payload Structure**:

```json
{
  "eventType": "CallStudioJobAnalysisResultReady",
  "timestamp": "2024-01-15T10:40:00.000Z",
  "eventId": "550e8400-e29b-41d4-a716-446655440002",
  "jobId": "job_123456",
  "agentId": "workspace_789",
  "analysisId": "analysis_789012",
  "analysisResultUrl": "https://voicehub.dataqueue.ai/api/v1/call-analysis/callstudio/job_123456"
}
```

**Payload Fields**:

* `eventType` (string): Always `"CallStudioJobAnalysisResultReady"`
* `timestamp` (string): ISO 8601 timestamp of when the event occurred
* `eventId` (string): Unique identifier for this webhook event
* `jobId` (string): Unique identifier of the Call Studio job
* `agentId` (string): Workspace ID where the job was created
* `analysisId` (string, optional): Unique identifier of the analysis
* `analysisResultUrl` (string, optional): URL to access the Call Studio job analysis results, you can use this url [like this](#fetching-call-studio-analysis-result) to fetch the full analysis

#### 3. CallStudioJobTranscriptReady

Triggered when Call Studio job transcripts are ready but analysis is disabled.

**Event Type**: `CallStudioJobTranscriptReady`

**Payload Structure**:

```json
{
  "eventType": "CallStudioJobTranscriptReady",
  "timestamp": "2024-01-15T10:35:00.000Z",
  "eventId": "550e8400-e29b-41d4-a716-446655440001",
  "jobId": "job_123456",
  "agentId": "workspace_789",
  "transcriptUrl": "https://voicehub.dataqueue.ai/api/v1/playground/job_123456"
}
```

**Payload Fields**:

* `eventType` (string): Always `"CallStudioJobTranscriptReady"`
* `timestamp` (string): ISO 8601 timestamp of when the event occurred
* `eventId` (string): Unique identifier for this webhook event
* `jobId` (string): Unique identifier of the Call Studio job
* `agentId` (string): Workspace ID where the job was created
* `transcriptUrl` (string, optional): URL to access the Call Studio job transcript, you can use this url [like this](#fetching-call-studio-job-transcript) to fetch the full transcripts

### Webhook Request Details

#### HTTP Method

`POST`

#### Headers

VoiceHub includes the following headers in webhook requests:

* `Content-Type`: `application/json`
* `X-Event-Type`: The type of event (e.g., `CallStudioJobStatusChanged`, `CallStudioJobAnalysisResultReady`)
* `X-Event-ID`: Unique identifier for this webhook event
* `X-Timestamp`: ISO 8601 timestamp of when the event occurred
* `x-webhook-secret` (optional): Secret key for webhook authentication (if configured)

#### Request Body

The request body contains the event payload as JSON, matching the structure described for each event type above.

### Retry Logic

If your webhook endpoint returns a non-2xx HTTP status code or times out, VoiceHub will automatically retry the webhook request according to your configured settings:

* **Retry Attempts**: Number of times to retry (default: 3)
* **Retry Delay**: Time to wait between retries in milliseconds (default: 1000ms)
* **Timeout**: Maximum time to wait for a response in milliseconds (default: 10000ms)

### Best Practices

1. **Idempotency**: Use the `eventId` field to ensure you don't process the same event twice
2. **Response Time**: Respond quickly (within the configured timeout) to avoid retries
3. **Status Codes**: Return `200 OK` or any `2xx` status code to indicate successful processing
4. **Security**: Always validate the `x-webhook-secret` header to ensure requests are from VoiceHub
5. **Error Handling**: Log errors appropriately and return appropriate HTTP status codes

### Example Webhook Handler

```javascript
app.post('/webhooks/call-studio', (req, res) => {
  // Verify webhook secret
  const secret = req.headers['x-webhook-secret']
  if (secret !== process.env.WEBHOOK_SECRET) {
    return res.status(401).send('Unauthorized')
  }

  const { eventType, eventId, jobId, timestamp } = req.body

  // Check if event was already processed (idempotency)
  if (isEventProcessed(eventId)) {
    return res.status(200).send('Event already processed')
  }

  // Process the event based on type
  switch (eventType) {
    case 'CallStudioJobStatusChanged':
      handleJobStatusChanged(req.body)
      break
    case 'CallStudioJobAnalysisResultReady':
      handleAnalysisResultReady(req.body)
      break
    default:
      console.warn('Unknown event type:', eventType)
  }

  // Mark event as processed
  markEventAsProcessed(eventId)

  res.status(200).send('OK')
})
```

### Fetching Call Studio Analysis Result

Once you receive the `CallStudioJobAnalysisResultReady` event, you can fetch the analysis result using the `analysisResultUrl` provided in the webhook payload.

This is a GET request that requires your agent API key as a header: `x-dq-api-key`

**Using the analysisResultUrl from webhook payload** (recommended):

```bash
curl --location '<analysisResultUrl>' \
--header 'x-dq-api-key: <api-key>'
```

**Or construct the URL using jobId**:

```bash
curl --location 'https://voicehub.dataqueue.ai/api/v1/call-analysis/callstudio/<jobId>' \
--header 'x-dq-api-key: <api-key>'
```

Where `<jobId>` is the `jobId` value from the webhook payload.

**Example Analysis Result Response**:

```json
{
  "callId": null,
  "transcriptAnalysis": [
    {
      "text": "string",
      "speaker": "user",
      "sentiment": {
        "score": 0,
        "label": "positive",
        "positiveKeywords": ["string"],
        "negativeKeywords": ["string"]
      },
      "start": 0,
      "end": 0,
      "duration": 0,
      "wps": 0
    }
  ],
  "summary": "string",
  "userSummary": {
    "totalSpeechDuration": 0,
    "totalSilenceDuration": 0,
    "totalInterruptionDuration": 0,
    "wps": 0,
    "sentiment": {
      "score": 0,
      "label": "positive",
      "positiveKeywords": ["string"],
      "negativeKeywords": ["string"]
    },
    "keyPhrases": {
      "positive": ["string"],
      "negative": ["string"]
    }
  },
  "assistantSummary": {
    "totalSpeechDuration": 0,
    "totalSilenceDuration": 0,
    "totalInterruptionDuration": 0,
    "wps": 0,
    "sentiment": {
      "score": 0,
      "label": "positive",
      "positiveKeywords": ["string"],
      "negativeKeywords": ["string"]
    },
    "keyPhrases": {
      "positive": ["string"],
      "negative": ["string"]
    }
  },
  "totalSummary": {
    "totalSpeechDuration": 0,
    "totalSilenceDuration": 0,
    "totalInterruptionDuration": 0,
    "wps": 0,
    "sentiment": {
      "score": 0,
      "label": "positive",
      "positiveKeywords": ["string"],
      "negativeKeywords": ["string"]
    },
    "keyPhrases": {
      "positive": ["string"],
      "negative": ["string"]
    }
  },
  "sentiment": {
    "score": 0,
    "label": "positive",
    "positiveKeywords": ["string"],
    "negativeKeywords": ["string"]
  },
  "duration": 0,
  "recordingUrls": ["string"],
  "mainTopics": ["string"],
  "loyaltyIndicators": ["string"],
  "extractedVariables": {},
  "empathyScore": 0,
  "resolutionStatus": "solved",
  "userInterruptionsCount": 0,
  "createdAt": "2025-09-23T08:48:59.619Z",
  "updatedAt": "2025-09-23T08:48:59.619Z"
}
```

**Note**: For Call Studio jobs, the `callId` field will be `null` or `undefined` since these are uploaded audio recordings, not live calls.<br>

### Fetching Call Studio Job Transcript

Once you receive the `CallStudioJobTranscriptReady` event, you can fetch the transcript using the `transcriptUrl` provided in the webhook payload.

This is a GET request that requires your agent API key as a header: `x-dq-api-key`

**Using the transcriptUrl from webhook payload** (recommended):

```bash
curl --location '<transcriptUrl>' \
--header 'x-dq-api-key: <api-key>'
```

**Or construct the URL using jobId**:

```bash
curl --location 'https://voicehub.dataqueue.ai/api/v1/playground/<jobId>' \
--header 'x-dq-api-key: <api-key>'
```

Where `<jobId>` is the `jobId` value from the webhook payload.

**Example Transcript Response**:

```json
{
  "_id": "job_123456",
  "workspaceId": "workspace_789",
  "title": "Call Recording",
  "status": "completed",
  "url": "https://storage.example.com/recording.mp3",
  "file": "recording.mp3",
  "callId": null,
  "analysisId": null,
  "readyForAnalysis": true,
  "resolutionStatus": null,
  "sentiment": null,
  "durationInSeconds": 120.5,
  "totalCreditsUsed": 0,
  "usageBreakdown": [],
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:35:00.000Z",
  "transcripts": [
    {
      "speaker": "SPEAKER_00",
      "transcription": "Hello, how can I help you today?",
      "userName": null,
      "start": 0.0,
      "end": 2.5,
      "words": [
        {
          "word": "Hello",
          "start": 0.0
        },
        {
          "word": "how",
          "start": 0.5
        }
      ]
    },
    {
      "speaker": "SPEAKER_01",
      "transcription": "I need help with my account.",
      "userName": null,
      "start": 2.5,
      "end": 5.0,
      "words": [
        {
          "word": "I",
          "start": 2.5
        },
        {
          "word": "need",
          "start": 2.8
        }
      ]
    }
  ],
  "createdBy": "user_123"
}
```

**Response Fields**:

* `_id` (string): Unique identifier of the Call Studio job
* `workspaceId` (string): Workspace ID where the job was created
* `title` (string): Title of the Call Studio job
* `status` (string): Current status of the job (`pending`, `completed`, `failed`)
* `url` (string, optional): Signed URL for uploading the audio file
* `file` (string, optional): Name of the audio file
* `callId` (string, optional): Call ID if this job is associated with a call (null for uploaded recordings)
* `analysisId` (string, optional): Analysis ID if analysis has been completed
* `readyForAnalysis` (boolean, optional): Whether the job is ready for analysis
* `resolutionStatus` (string, optional): Resolution status label
* `sentiment` (string, optional): Sentiment label
* `durationInSeconds` (number, optional): Duration of the audio in seconds
* `totalCreditsUsed` (number): Total credits used for this job
* `usageBreakdown` (array): Breakdown of credit usage by type
* `createdAt` (string): ISO 8601 timestamp of when the job was created
* `updatedAt` (string): ISO 8601 timestamp of when the job was last updated
* `transcripts` (array, optional): Array of transcript segments with the following structure:
  * `speaker` (string): Speaker identifier (e.g., `SPEAKER_00`, `SPEAKER_01`)
  * `transcription` (string): The transcribed text for this segment
  * `userName` (string, optional): Name of the user if available
  * `start` (number): Start time of the segment in seconds
  * `end` (number): End time of the segment in seconds
  * `words` (array, optional): Array of word-level timestamps with:
    * `word` (string): The word text
    * `start` (number): Start time of the word in seconds
* `createdBy` (string, optional): ID of the user who created the job

**Note**: The `transcripts` array contains the full transcript with speaker diarization and timing information. Each segment represents a continuous speech from a single speaker.
