# 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&#x20;

#### 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dataqueue.gitbook.io/voicehub-docs/api-reference/call-webhook-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
