# Overview
Source: https://docs.dograh.com/api-reference/agents
Create and manage voice agents (workflows) via the API
In Dograh, a **voice agent** is called a **workflow** in the API. A workflow defines the conversation flow, LLM configuration, voice settings, and tools available to your agent.
| Method | Endpoint | Quick Link |
| - | - | - |
| `POST` | `/workflow/create/definition` | [Create from definition](/api-reference/agents/create-from-definition) |
| `POST` | `/workflow/create/template` | [Create from template](/api-reference/agents/create-from-template) |
| `GET` | `/workflow/fetch` | [List agents](/api-reference/agents/list) |
| `GET` | `/workflow/count` | [Get agent count](/api-reference/agents/count) |
| `GET` | `/workflow/fetch/{workflow_id}` | [Get an agent](/api-reference/agents/get) |
| `PUT` | `/workflow/{workflow_id}` | [Update an agent](/api-reference/agents/update) |
| `PUT` | `/workflow/{workflow_id}/status` | [Archive an agent](/api-reference/agents/archive) |
| `POST` | `/workflow/{workflow_id}/validate` | [Validate a workflow](/api-reference/agents/validate) |
| `POST` | `/workflow/{workflow_id}/runs` | [Create test run](/api-reference/agents/runs/create) |
| `GET` | `/workflow/{workflow_id}/runs` | [List runs](/api-reference/agents/runs/list) |
| `GET` | `/workflow/{workflow_id}/runs/{run_id}` | [Get a run](/api-reference/agents/runs/get) |
# Archive Agent
Source: https://docs.dograh.com/api-reference/agents/archive
PUT /api/v1/workflow/{workflow_id}/status
Archive or restore a voice agent
Send `{"status": "archived"}` to deactivate an agent, or `{"status": "active"}` to restore it. Archived agents cannot receive calls but their history and definition are preserved.
# Get Agent Count
Source: https://docs.dograh.com/api-reference/agents/count
GET /api/v1/workflow/count
Get the total number of agents broken down by status
Returns totals broken down by status — useful for dashboards or quota checks.
```json theme={null}
{
"total": 5,
"active": 4,
"archived": 1
}
```
# Create from Definition
Source: https://docs.dograh.com/api-reference/agents/create-from-definition
POST /api/v1/workflow/create/definition
Create a voice agent from an explicit workflow definition
Creates an agent from a node and edge graph you provide directly. Use this when you want full control over the workflow structure, or when programmatically generating agents.
The `workflow_definition` object contains `nodes` (the steps in the conversation) and `edges` (transitions between steps). See [Editing a Workflow](/voice-agent/editing-a-workflow) for the full schema.
# Create from Template
Source: https://docs.dograh.com/api-reference/agents/create-from-template
POST /api/v1/workflow/create/template
Generate a voice agent from a natural language description
Dograh uses an LLM to generate the initial workflow definition from your description. The result is a fully editable agent — use [Update](/api-reference/agents/update) to refine it after creation.
This is the fastest way to get a working agent, especially for common use cases like appointment booking, lead qualification, or customer support.
# Get Agent
Source: https://docs.dograh.com/api-reference/agents/get
GET /api/v1/workflow/fetch/{workflow_id}
Retrieve a single agent by ID, including its full workflow definition
Returns the full agent including `workflow_definition` (nodes and edges), `template_context_variables`, `workflow_configurations`, and run statistics.
# List Agents
Source: https://docs.dograh.com/api-reference/agents/list
GET /api/v1/workflow/fetch
Retrieve all agents in your organization
Returns all agents (workflows) in your organization, including both active and archived. Each item includes summary fields — use [Get an agent](/api-reference/agents/get) to retrieve the full workflow definition.
# Create Test Run
Source: https://docs.dograh.com/api-reference/agents/runs/create
POST /api/v1/workflow/{workflow_id}/runs
Execute a workflow without placing a real phone call
Creates a test execution of the workflow. No outbound call is placed — this is useful for validating agent behavior, checking prompt outputs, and testing tool integrations before going live.
# Get Run
Source: https://docs.dograh.com/api-reference/agents/runs/get
GET /api/v1/workflow/{workflow_id}/runs/{run_id}
Retrieve a single workflow run by ID
Returns the full run record including status, transcript, recording URL, gathered context, and usage/cost info. Use `recording_url` and `transcript_url` to download artifacts, or use the [Download endpoint](/api-reference/runs/download) for time-limited public URLs.
# List Runs
Source: https://docs.dograh.com/api-reference/agents/runs/list
GET /api/v1/workflow/{workflow_id}/runs
Retrieve all runs for a workflow
Returns all runs for the given workflow, including both test runs and live call runs.
# Update Agent
Source: https://docs.dograh.com/api-reference/agents/update
PUT /api/v1/workflow/{workflow_id}
Update an agent's name, workflow definition, or configuration
All fields are optional — only include the fields you want to change. Updating `workflow_definition` creates a new versioned definition while preserving the history of previous versions.
# Validate Workflow
Source: https://docs.dograh.com/api-reference/agents/validate
POST /api/v1/workflow/{workflow_id}/validate
Validate a workflow definition without executing it
Checks the current workflow definition for structural errors — missing required fields, invalid node configurations, broken edge references — without placing a call or creating a run.
If invalid, the response includes a list of errors each with a `kind` (`node`, `edge`, or `workflow`), the offending `id`, the `field`, and a human-readable `message`. See [Errors](/api-reference/errors) for the full error schema.
# Overview
Source: https://docs.dograh.com/api-reference/api-keys
Create and manage API keys for programmatic access
API keys authenticate requests from your applications and services. Each key is scoped to your organization — all API calls made with a key create and access resources within that organization.
| Method | Endpoint | Quick Link |
| - | - | - |
| `POST` | `/user/api-keys` | [Create an API key](/api-reference/api-keys/create) |
| `GET` | `/user/api-keys` | [List API keys](/api-reference/api-keys/list) |
| `DELETE` | `/user/api-keys/{api_key_id}` | [Archive an API key](/api-reference/api-keys/archive) |
| `PUT` | `/user/api-keys/{api_key_id}/reactivate` | [Reactivate an API key](/api-reference/api-keys/reactivate) |
## Best practices
* **Use one key per environment** — separate keys for development, staging, and production make rotation easy and limit blast radius if a key is compromised.
* **Use one key per service** — this allows you to revoke a single service's access without affecting others.
* **Rotate keys regularly** — create a new key, update your secret store, then archive the old key.
* **Never hardcode keys** — use environment variables or a secrets manager. Never commit keys to version control.
* **Monitor `last_used_at`** — keys with no recent activity may be safe to archive.
# Archive API Key
Source: https://docs.dograh.com/api-reference/api-keys/archive
DELETE /api/v1/user/api-keys/{api_key_id}
Deactivate an API key by ID
Archiving immediately revokes the key. Any request using the key after archiving returns `401`. Use [Reactivate](/api-reference/api-keys/reactivate) to restore access.
# Create API Key
Source: https://docs.dograh.com/api-reference/api-keys/create
POST /api/v1/user/api-keys
Create a new API key for programmatic access
The full key is only returned once at creation. Store it immediately in a secrets manager or environment variable — it cannot be retrieved again.
## Authentication
This endpoint requires a valid user session token. If you do not yet have an API key, obtain a session token by logging in first and pass it as a `Bearer` token in the `Authorization` header.
**Step 1 — Log in to get a session token**
```bash theme={null}
curl -X POST https://your-dograh-instance/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "your-password"}'
```
The response contains a `token` field. Use it in the next step.
**Step 2 — Create an API key**
```bash theme={null}
curl -X POST https://your-dograh-instance/api/v1/user/api-keys \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"name": "my-api-key"}'
```
Once you have an API key, you can use `X-API-Key: ` in place of `Authorization: Bearer` for all subsequent requests.
# List API Keys
Source: https://docs.dograh.com/api-reference/api-keys/list
GET /api/v1/user/api-keys
Retrieve all API keys for your organization
The `key` field is never returned in list responses — only `key_prefix` (the first 8 characters) is shown for identification. To include archived keys, pass `?include_archived=true`.
# Reactivate API Key
Source: https://docs.dograh.com/api-reference/api-keys/reactivate
PUT /api/v1/user/api-keys/{api_key_id}/reactivate
Reactivate a previously archived API key
Restores a previously [archived](/api-reference/api-keys/archive) key. The key resumes accepting requests immediately.
# Authentication
Source: https://docs.dograh.com/api-reference/authentication
How to authenticate requests to the Dograh API
## API key authentication
API keys are the recommended way to authenticate programmatic requests. Pass your key in the `X-API-Key` request header.
```bash theme={null}
curl https://your-dograh-instance/api/v1/workflow/fetch \
-H "X-API-Key: dg_your_api_key"
```
API keys are scoped to an organization. All resources created or accessed using a key belong to that organization.
### Create an API key
Create keys in the dashboard under **Settings → API Keys**. The full key is shown **only once** at creation time — store it immediately in a secrets manager or environment variable.
For self-hosted deployments using local auth, sign up and log in via the dashboard first, then create an API key there before making API calls.
### Manage API keys
| Action | Method | Path |
| - | - | - |
| List keys | `GET` | `/user/api-keys` |
| Create key | `POST` | `/user/api-keys` |
| Archive key | `DELETE` | `/user/api-keys/{api_key_id}` |
| Reactivate key | `PUT` | `/user/api-keys/{api_key_id}/reactivate` |
Archiving a key immediately revokes it. All subsequent requests using that key return `401`.
***
## Error responses
| Status | Cause |
| - | - |
| `401 Unauthorized` | Missing, invalid, or expired credentials |
| `403 Forbidden` | Valid credentials but insufficient permissions for the resource |
```json theme={null}
{
"detail": "Invalid or expired API key"
}
```
# Overview
Source: https://docs.dograh.com/api-reference/campaigns
Create and manage outbound calling campaigns
A **campaign** runs a workflow against a list of contacts. You upload a CSV of phone numbers, configure retry logic and scheduling, then start the campaign. Dograh dials contacts automatically up to your configured concurrency limit.
| Method | Endpoint | Quick Link |
| - | - | - |
| `POST` | `/s3/presigned-upload-url` | [Upload contacts CSV](/api-reference/campaigns/upload-contacts) |
| `POST` | `/campaign/create` | [Create a campaign](/api-reference/campaigns/create) |
| `GET` | `/campaign/` | [List campaigns](/api-reference/campaigns/list) |
| `GET` | `/campaign/{campaign_id}` | [Get a campaign](/api-reference/campaigns/get) |
| `PATCH` | `/campaign/{campaign_id}` | [Update a campaign](/api-reference/campaigns/update) |
| `POST` | `/campaign/{campaign_id}/start` | [Start](/api-reference/campaigns/start) |
| `POST` | `/campaign/{campaign_id}/pause` | [Pause](/api-reference/campaigns/pause) |
| `POST` | `/campaign/{campaign_id}/resume` | [Resume](/api-reference/campaigns/resume) |
| `GET` | `/campaign/{campaign_id}/progress` | [Get campaign progress](/api-reference/campaigns/progress) |
| `GET` | `/campaign/{campaign_id}/runs` | [Get campaign call runs](/api-reference/campaigns/runs) |
## Campaign status values
| Status | Description |
| - | - |
| `draft` | Created but not started |
| `running` | Actively dialing contacts |
| `paused` | Temporarily stopped; can be resumed |
| `completed` | All contacts processed |
| `failed` | Campaign encountered a fatal error |
# Create Campaign
Source: https://docs.dograh.com/api-reference/campaigns/create
POST /api/v1/campaign/create
Create a new outbound calling campaign
Before creating a campaign, [upload your contacts CSV](/api-reference/campaigns/upload-contacts) to get a `source_url`.
Set `telephony_configuration_id` to pin the campaign to a specific [telephony configuration](/api-reference/telephony-configs). If omitted, Dograh falls back to the organization's [default outbound configuration](/api-reference/telephony-configs/set-default-outbound) — supplying an id that doesn't exist in your organization returns `400 telephony_configuration_not_found`.
The `time_slots` field controls when Dograh is allowed to place calls. If omitted, calls can be placed at any time. The `timezone` field applies to all time slot windows.
```json theme={null}
{
"time_slots": [
{ "day": "monday", "start": "09:00", "end": "17:00" },
{ "day": "tuesday", "start": "09:00", "end": "17:00" }
]
}
```
Once created, the campaign is in `draft` status. Call [Start](/api-reference/campaigns/start) to begin dialing.
To split calls between agents or versions, send `traffic_split` instead of `workflow_id`:
```json theme={null}
{
"name": "Version comparison",
"source_type": "csv",
"source_id": "your-uploaded-csv-key",
"traffic_split": {
"variants": [
{ "workflow_id": 12, "workflow_definition_id": null, "weight": 50 },
{ "workflow_id": 12, "workflow_definition_id": 341, "weight": 50 }
]
}
}
```
You can specify one to five unique agent/version pairs. Weights must be whole percentages from 1 to 100 and total 100. All agents must belong to your organization, and your CSV must supply the template variables required by every selected version.
Omit `workflow_definition_id` or set it to `null` to use the latest published version at dial time. A definition ID pins a published or archived version; drafts cannot be selected. Calls already in progress keep their version.
You can change the split with `PATCH /api/v1/campaign/{campaign_id}` while a campaign is running or paused. Changes apply to the next batch, including retries. A contact stays on the same variant while the split is unchanged; changing weights or removing variants can move contacts. Reordering existing variants alone does not change allocation. Redial campaigns inherit the current split and its allocation seed.
The campaign detail page shows current target percentages and actual shares across all attempts, including retries and earlier splits. Shares are approximate, especially with small contact lists. `GET /api/v1/campaign/{campaign_id}/traffic-stats` returns the same counts, outcomes, and versions used, including variants removed from the current split.
# Get Campaign
Source: https://docs.dograh.com/api-reference/campaigns/get
GET /api/v1/campaign/{campaign_id}
Retrieve a single campaign by ID
# List Campaigns
Source: https://docs.dograh.com/api-reference/campaigns/list
GET /api/v1/campaign/
Retrieve all campaigns for your organization
# Pause Campaign
Source: https://docs.dograh.com/api-reference/campaigns/pause
POST /api/v1/campaign/{campaign_id}/pause
Temporarily stop a running campaign
Stops dispatching new calls. In-flight calls are not interrupted — they run to completion. Use [Resume](/api-reference/campaigns/resume) to continue from where the campaign paused.
# Get Campaign Progress
Source: https://docs.dograh.com/api-reference/campaigns/progress
GET /api/v1/campaign/{campaign_id}/progress
Get the current progress of a campaign
Returns a real-time snapshot of how many contacts have been processed.
| Field | Description |
| - | - |
| `total` | Total number of contacts in the campaign |
| `processed` | Contacts that have been attempted at least once |
| `completed` | Contacts with a successful call outcome |
| `failed` | Contacts that exhausted all retry attempts without success |
| `pending` | Contacts not yet attempted |
| `completion_percentage` | `processed / total × 100` |
# Resume Campaign
Source: https://docs.dograh.com/api-reference/campaigns/resume
POST /api/v1/campaign/{campaign_id}/resume
Resume a paused campaign
Resumes dispatching calls from where the campaign paused. Only valid on campaigns in `paused` status.
# Get Campaign Runs
Source: https://docs.dograh.com/api-reference/campaigns/runs
GET /api/v1/campaign/{campaign_id}/runs
Retrieve individual run records for each contact in a campaign
Returns the individual run records for each contact in the campaign. Each record includes the same fields as [agent run details](/api-reference/runs/get-run), including run status, duration, transcript, and recording URL.
# Start Campaign
Source: https://docs.dograh.com/api-reference/campaigns/start
POST /api/v1/campaign/{campaign_id}/start
Start dialing contacts in a campaign
Transitions the campaign from `draft` or `paused` to `running`. Dograh begins dialing contacts up to the configured `max_concurrency`.
# Update Campaign
Source: https://docs.dograh.com/api-reference/campaigns/update
PATCH /api/v1/campaign/{campaign_id}
Update campaign settings
You can update name, concurrency, time slots, and retry config. Updates are only allowed on campaigns in `draft` or `paused` status — [pause the campaign](/api-reference/campaigns/pause) first if it is currently running.
# Upload Contacts CSV
Source: https://docs.dograh.com/api-reference/campaigns/upload-contacts
POST /api/v1/s3/presigned-upload-url
Get a presigned S3 URL to upload a contacts CSV for a campaign
Uploading contacts is a two-step process. First call this endpoint to get a presigned upload URL, then PUT your CSV directly to that URL.
```bash theme={null}
# Step 1: Get upload URL
curl -X POST https://your-dograh-instance/api/v1/s3/presigned-upload-url \
-H "X-API-Key: dg_your_api_key"
# Response:
# { "upload_url": "https://...", "s3_key": "campaigns/..." }
# Step 2: Upload the CSV
curl -X PUT "https://presigned-url..." \
-H "Content-Type: text/csv" \
--data-binary @contacts.csv
```
Use the `s3_key` from the response as the `source_url` when [creating a campaign](/api-reference/campaigns/create).
### CSV format
The CSV must include a `phone_number` column. Any additional columns are passed as `initial_context` to each call, making them available as template variables in the workflow.
What counts as a valid `phone_number` depends on the telephony configuration the campaign dials with. Carriers require E.164 — a `+`, country code, then the number. An [Asterisk ARI](/integrations/telephony/asterisk-ari#campaign-contacts) configuration also accepts extensions, SIP URIs and dial strings, since those are what its PBX reaches. Either way, numbers must be unique within a campaign and cannot contain spaces, commas or ampersands.
```csv theme={null}
phone_number,customer_name,plan,greeting_override_type,greeting_override_text
+14155550100,Jane Smith,premium,text,Hello {{customer_name}}
+14155550101,Bob Jones,basic,text,Welcome back {{customer_name}}
```
To override the Start node greeting for a row, use these optional columns:
* Text: set `greeting_override_type` to `text` and put the message in `greeting_override_text`.
* Recording: set `greeting_override_type` to `audio` and put the public recording ID in `greeting_override_recording_id`.
Greeting text can reference other CSV columns, such as `Hello {{customer_name}}`. Empty override columns preserve the greeting configured in the Start node.
# Errors
Source: https://docs.dograh.com/api-reference/errors
HTTP status codes and error formats returned by the Dograh API
## Error format
All errors return a JSON body with a `detail` field:
```json theme={null}
{
"detail": "Human-readable error message"
}
```
For request validation failures (status `422`), `detail` is an array of field-level errors:
```json theme={null}
{
"detail": [
{
"loc": ["body", "phone_number"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
***
## HTTP status codes
| Status | Meaning | Common causes |
| - | - | - |
| `400` | Bad Request | Missing required fields, invalid parameter values, unsupported operation |
| `401` | Unauthorized | Missing auth header, invalid or expired API key or JWT token |
| `403` | Forbidden | Valid credentials but the resource belongs to a different organization |
| `404` | Not Found | Resource ID does not exist or is not accessible to your organization |
| `409` | Conflict | Duplicate resource (e.g., email already registered) |
| `422` | Unprocessable Entity | Request body or query parameter failed schema validation |
| `500` | Internal Server Error | Unexpected server-side failure |
| `501` | Not Implemented | Feature not supported in the current deployment |
***
## Workflow validation errors
When validating a workflow (or creating one with invalid nodes), the API returns structured errors that reference specific nodes, edges, or fields:
```json theme={null}
{
"errors": [
{
"kind": "node",
"id": "agent-1",
"field": "data.prompt",
"message": "Prompt cannot be empty"
},
{
"kind": "edge",
"id": "edge-3",
"field": null,
"message": "Edge target node does not exist"
},
{
"kind": "workflow",
"id": null,
"field": null,
"message": "Workflow must have exactly one Start Call node"
}
]
}
```
| Field | Type | Description |
| - | - | - |
| `kind` | `"node" \| "edge" \| "workflow"` | What the error applies to |
| `id` | string or null | Node or edge ID from the workflow definition |
| `field` | string or null | Dot-notation path to the specific field (e.g. `data.prompt`) |
| `message` | string | Human-readable description of the problem |
***
## Telephony errors
Telephony operations may fail with one of these named error types returned in the `detail` field:
| Error | Description |
| - | - |
| `PROVIDER_MISMATCH` | The request was routed to the wrong telephony provider |
| `WORKFLOW_NOT_FOUND` | The workflow ID in the inbound URL does not exist |
| `ACCOUNT_VALIDATION_FAILED` | Your telephony provider credentials are invalid |
| `PHONE_NUMBER_NOT_CONFIGURED` | The phone number is not set up in your telephony account |
| `SIGNATURE_VALIDATION_FAILED` | Webhook signature verification failed (possible spoofed request) |
| `QUOTA_EXCEEDED` | Your organization has exceeded its usage quota |
| `GENERAL_AUTH_FAILED` | Generic authentication failure with the telephony provider |
***
## Tips for handling errors
* **Retry on `500`** with exponential backoff — transient server errors can resolve on retry.
* **Do not retry `4xx`** — these indicate problems with your request that will not self-resolve.
* **Check `detail` carefully on `422`** — the `loc` array pinpoints exactly which field failed validation.
* **Store API keys securely** — a `401` on a previously working key likely means the key was archived.
# Overview
Source: https://docs.dograh.com/api-reference/overview
Interact with Dograh programmatically via the REST API
## Base URL
All API requests are made to:
```
https://your-dograh-instance/api/v1
```
If you are using the hosted version, the base URL is `https://app.dograh.com/api/v1`.
## Versioning
The current API version is `v1`. The version is included in the URL path.
## Request format
All request bodies must be sent as JSON with the `Content-Type: application/json` header.
```bash theme={null}
curl -X POST https://your-dograh-instance/api/v1/workflow/create/definition \
-H "Content-Type: application/json" \
-H "X-API-Key: dg_your_api_key" \
-d '{"name": "My Agent", "workflow_definition": {}}'
```
## Response format
All responses are JSON. Successful responses return the relevant resource object. Error responses follow a consistent shape:
```json theme={null}
{
"detail": "Error message describing what went wrong"
}
```
For validation errors (422), the detail may be a list:
```json theme={null}
{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
## Health check
Verify your instance is reachable before making API calls.
```bash theme={null}
curl https://your-dograh-instance/api/v1/health
```
```json theme={null}
{
"status": "ok",
"version": "1.0.0",
"backend_api_endpoint": "https://your-dograh-instance",
"deployment_mode": "oss",
"auth_provider": "local"
}
```
# Overview
Source: https://docs.dograh.com/api-reference/runs
Create and inspect agent runs via the API
| Method | Endpoint | Quick Link |
| - | - | - |
| `POST` | `/public/agent/{uuid}` | [Trigger an outbound agent run by API Trigger node](/api-reference/runs/trigger) |
| `POST` | `/public/agent/workflow/{workflow_uuid}` | [Trigger an outbound agent run by Agent UUID](/api-reference/runs/trigger-workflow) |
| `GET` | `/workflow/{workflow_id}/runs/{run_id}` | [Retrieve agent run details](/api-reference/runs/get-run) |
| `GET` | `/public/download/workflow/{token}/{artifact_type}` | [Download recordings and transcripts](/api-reference/runs/download) |
| `POST` | `/telephony/inbound/{workflow_id}` | [Inbound run webhook](/api-reference/runs/inbound) |
## Choose the right public run route
Dograh exposes two public outbound agent run route families. They are **not**
interchangeable, even though both path parameters look like UUIDs.
| Use this when | Production route | Test route | Identifier you pass |
| - | - | - | - |
| You added an **[API Trigger node](/voice-agent/api-trigger)** to the workflow and want to execute that trigger | `/public/agent/{uuid}` | `/public/agent/test/{uuid}` | The trigger UUID (`trigger_path`) from the API Trigger node |
| You want to execute the workflow by its stable **Agent UUID** instead of a trigger node | `/public/agent/workflow/{workflow_uuid}` | `/public/agent/test/workflow/{workflow_uuid}` | The workflow UUID from the agent's **[Agent UUID](/configurations/agent-uuid)** field |
Do not pass a workflow UUID to `/public/agent/{uuid}` and do not pass a trigger UUID
to `/public/agent/workflow/{workflow_uuid}`. Dograh treats these as different
identifier types and the request will fail if you mix them up.
Once Dograh resolves the target agent, both route families behave the same:
* They accept the same request body
* They return the same response shape
* They validate the same `X-API-Key` organization boundary
* They use the same telephony configuration selection rules
If you specifically need the API Trigger route, see [Trigger an outbound agent run by API Trigger node](/api-reference/runs/trigger). To execute by Agent UUID, see [Trigger an outbound agent run by Agent UUID](/api-reference/runs/trigger-workflow).
## Using initial context
`initial_context` passes runtime data into the agent at run time. Values are available as template variables in your agent's prompt using double-brace syntax.
```json theme={null}
{
"initial_context": {
"customer_name": "Jane",
"appointment_date": "March 15"
}
}
```
Your agent prompt can then reference `{{customer_name}}` and `{{appointment_date}}` and they will be substituted when the run starts.
## Run status values
| Status | Description |
| - | - |
| `pending` | Run queued but not yet connected |
| `in_progress` | Run is live |
| `completed` | Run ended normally |
| `failed` | Run failed before or during execution |
# Download Recordings and Transcripts
Source: https://docs.dograh.com/api-reference/runs/download
GET /api/v1/public/download/workflow/{token}/{artifact_type}
Download a recording or transcript using a time-limited public URL
`artifact_type` is either `recording` or `transcript`. The `token` is a time-limited download token — generate one from the run details before calling this endpoint.
# Retrieve Agent Run Details
Source: https://docs.dograh.com/api-reference/runs/get-run
GET /api/v1/workflow/{workflow_id}/runs/{run_id}
Get the details, transcript, and recording for an agent run
Returns the full run record including run status, duration, transcript URL, recording URL, gathered context, and usage/cost info.
Use the `recording_url` and `transcript_url` directly, or use the [Download endpoint](/api-reference/runs/download) to generate time-limited public URLs for sharing.
# Inbound Run Webhook
Source: https://docs.dograh.com/api-reference/runs/inbound
POST /api/v1/telephony/inbound/{workflow_id}
Webhook endpoint that starts agent runs from inbound calls
Configure this URL in your telephony provider's dashboard (Twilio, Vonage, etc.) to start agent runs from inbound calls to a specific agent. The `workflow_id` determines which agent handles the call.
See [Inbound calls](/integrations/telephony/inbound) for full setup instructions per provider.
# List Runs with Usage
Source: https://docs.dograh.com/api-reference/runs/list-runs
GET /api/v1/organizations/usage/runs
Paginated list of workflow runs across the organization with usage and cost
Returns a paginated list of runs across all agents in your organization, including duration, cost, and usage details for each run.
Use `start_date` and `end_date` (ISO 8601) to scope the window, and `page` / `limit` to paginate. Pass `filters` as a JSON-encoded string to narrow results further.
To fetch the full transcript or recording for a specific run, use [Retrieve Agent Run Details](/api-reference/runs/get-run).
# Trigger an Outbound Agent Run by API Trigger Node
Source: https://docs.dograh.com/api-reference/runs/trigger
POST /api/v1/public/agent/{uuid}
Start an outbound agent run using an API Trigger node UUID
Use this endpoint when you want to start an agent run through an [API Trigger node](/voice-agent/api-trigger).
The `uuid` comes from the API Trigger node in your agent. Add the node to your workflow and copy its auto-generated `trigger_path`.
Use `workflow_run_id` from the response to later [retrieve run details](/api-reference/runs/get-run), recordings, and transcripts.
Pass `initial_context` to inject runtime data as template variables into the agent's prompt. See [Using initial context](/api-reference/runs#using-initial-context).
Pass `telephony_configuration_id` to route the call through a specific telephony configuration instead of your organization's default. The id is shown on each row in **Telephony configurations** (`https://app.dograh.com/telephony-configurations` for hosted or `http://localhost:3010/telephony-configurations` for local).
Pass `from_phone_number_id` to use a specific active caller ID in the resolved telephony configuration. The number ID must belong to that configuration. Retrieve number IDs with [List phone numbers](/api-reference/telephony-configs/phone-numbers/list).
This route expects an API Trigger node UUID (`trigger_path`). Do not pass a workflow UUID here. If you want to execute by Agent UUID, use [Trigger an outbound agent run by Agent UUID](/api-reference/runs/trigger-workflow) instead.
Your telephony provider must be configured before outbound calls will connect. See [Telephony](/integrations/telephony/overview) for setup instructions.
# Trigger an Outbound Agent Run by Agent UUID
Source: https://docs.dograh.com/api-reference/runs/trigger-workflow
POST /api/v1/public/agent/workflow/{workflow_uuid}
Start an outbound agent run using a workflow's stable Agent UUID
Use this endpoint when you want to start an agent run directly by its stable Agent UUID instead of through an API Trigger node.
The `workflow_uuid` is the workflow's Agent UUID. It is different from an API Trigger node's `trigger_path`.
To find and copy the Agent UUID in the UI, see [Agent UUID](/configurations/agent-uuid).
Use `workflow_run_id` from the response to later [retrieve run details](/api-reference/runs/get-run), recordings, and transcripts.
Pass `initial_context` to inject runtime data as template variables into the agent's prompt. See [Using initial context](/api-reference/runs#using-initial-context).
Pass `telephony_configuration_id` to route the call through a specific telephony configuration instead of your organization's default. The id is shown on each row in **Telephony configurations** (`https://app.dograh.com/telephony-configurations` for hosted or `http://localhost:3010/telephony-configurations` for local).
Pass `from_phone_number_id` to use a specific active caller ID in the resolved telephony configuration. The number ID must belong to that configuration. Retrieve number IDs with [List phone numbers](/api-reference/telephony-configs/phone-numbers/list).
This route expects a workflow UUID. Do not pass an API Trigger node UUID here. If you want to execute via an API Trigger node, use [Trigger an outbound agent run](/api-reference/runs/trigger) instead.
Your telephony provider must be configured before outbound calls will connect. See [Telephony](/integrations/telephony/overview) for setup instructions.
# Overview
Source: https://docs.dograh.com/api-reference/telephony-configs
Configure telephony providers and manage phone numbers
A **telephony configuration** binds an organization to a telephony provider (Twilio, Vonage, Plivo, Telnyx, etc.) along with the credentials needed to place and receive calls. Each configuration owns a set of phone numbers; campaigns and inbound routing pin to a specific configuration.
One configuration in your organization can be marked as the **default outbound** — campaigns and ad-hoc calls that omit `telephony_configuration_id` fall back to it.
| Method | Endpoint | Quick Link |
| - | - | - |
| `GET` | `/organizations/telephony-providers/metadata` | [List supported providers](/api-reference/telephony-configs/providers) |
| `GET` | `/organizations/telephony-configs` | [List configurations](/api-reference/telephony-configs/list) |
| `POST` | `/organizations/telephony-configs` | [Create a configuration](/api-reference/telephony-configs/create) |
| `GET` | `/organizations/telephony-configs/{config_id}` | [Get a configuration](/api-reference/telephony-configs/get) |
| `PUT` | `/organizations/telephony-configs/{config_id}` | [Update a configuration](/api-reference/telephony-configs/update) |
| `DELETE` | `/organizations/telephony-configs/{config_id}` | [Delete a configuration](/api-reference/telephony-configs/delete) |
| `POST` | `/organizations/telephony-configs/{config_id}/set-default-outbound` | [Set as default outbound](/api-reference/telephony-configs/set-default-outbound) |
## Phone numbers
Phone numbers are nested under a configuration:
| Method | Endpoint | Quick Link |
| - | - | - |
| `GET` | `/organizations/telephony-configs/{config_id}/phone-numbers` | [List phone numbers](/api-reference/telephony-configs/phone-numbers/list) |
| `POST` | `/organizations/telephony-configs/{config_id}/phone-numbers` | [Add a phone number](/api-reference/telephony-configs/phone-numbers/create) |
| `GET` | `/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}` | [Get a phone number](/api-reference/telephony-configs/phone-numbers/get) |
| `PUT` | `/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}` | [Update a phone number](/api-reference/telephony-configs/phone-numbers/update) |
| `DELETE` | `/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}` | [Delete a phone number](/api-reference/telephony-configs/phone-numbers/delete) |
| `POST` | `/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}/set-default-caller` | [Set as default caller](/api-reference/telephony-configs/phone-numbers/set-default-caller) |
## Sensitive credentials
Provider credentials (API keys, auth tokens, etc.) are masked in `GET` responses. When updating a configuration, you can re-submit the masked value as-is — Dograh detects the mask and preserves the original secret.
# Create Configuration
Source: https://docs.dograh.com/api-reference/telephony-configs/create
POST /api/v1/organizations/telephony-configs
Create a new telephony configuration
Call [List Supported Providers](/api-reference/telephony-configs/providers) first to discover the credential fields for the provider you want to configure — they vary per provider.
Configuration `name` must be unique within your organization. Set `is_default_outbound: true` to mark this configuration as the org-wide default for outbound calls (any existing default is unset). New configurations have no phone numbers; add them via [Add a phone number](/api-reference/telephony-configs/phone-numbers/create).
# Delete Configuration
Source: https://docs.dograh.com/api-reference/telephony-configs/delete
DELETE /api/v1/organizations/telephony-configs/{config_id}
Delete a telephony configuration
Returns `409 Conflict` if the configuration is referenced by any campaign or phone number. Detach those resources first, then retry.
# Get Configuration
Source: https://docs.dograh.com/api-reference/telephony-configs/get
GET /api/v1/organizations/telephony-configs/{config_id}
Retrieve a telephony configuration by id
Sensitive credential fields (API keys, auth tokens) are returned in masked form.
# List Configurations
Source: https://docs.dograh.com/api-reference/telephony-configs/list
GET /api/v1/organizations/telephony-configs
List all telephony configurations in your organization
# Add Phone Number
Source: https://docs.dograh.com/api-reference/telephony-configs/phone-numbers/create
POST /api/v1/organizations/telephony-configs/{config_id}/phone-numbers
Attach a new phone number to a telephony configuration
Set `inbound_workflow_id` to bind incoming calls on this number to a specific agent — Dograh will register the inbound webhook with the provider and the response will include a `provider_sync` block reporting the result. Omit it to add the number for outbound use only.
The `(provider, account_id, address)` tuple must be globally unique across all Dograh organizations, since inbound dispatch keys on it. Attempting to add a number that another organization already owns returns `409`.
# Delete Phone Number
Source: https://docs.dograh.com/api-reference/telephony-configs/phone-numbers/delete
DELETE /api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}
Remove a phone number from a telephony configuration
# Get Phone Number
Source: https://docs.dograh.com/api-reference/telephony-configs/phone-numbers/get
GET /api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}
Retrieve a phone number by id
# List Phone Numbers
Source: https://docs.dograh.com/api-reference/telephony-configs/phone-numbers/list
GET /api/v1/organizations/telephony-configs/{config_id}/phone-numbers
List phone numbers attached to a telephony configuration
# Set Default Caller
Source: https://docs.dograh.com/api-reference/telephony-configs/phone-numbers/set-default-caller
POST /api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}/set-default-caller
Designate a phone number as the configuration's default outbound caller id
Outbound calls placed through this configuration that don't specify a `from_number` use the default caller. Setting a new default automatically clears the previous one — at most one phone number per configuration can be the default caller.
# Update Phone Number
Source: https://docs.dograh.com/api-reference/telephony-configs/phone-numbers/update
PUT /api/v1/organizations/telephony-configs/{config_id}/phone-numbers/{phone_number_id}
Update label, inbound binding, or activation state of a phone number
Changing `inbound_workflow_id` re-syncs the inbound webhook with the provider; the response includes a `provider_sync` block with the result.
To remove an existing inbound binding, send `clear_inbound_workflow: true`. Sending `inbound_workflow_id: null` alone is treated as "no change".
# List Supported Providers
Source: https://docs.dograh.com/api-reference/telephony-configs/providers
GET /api/v1/organizations/telephony-providers/metadata
Discover available telephony providers and the credential fields each one requires
Use this endpoint to discover which telephony providers Dograh supports and the exact credential fields each one expects. The response drives the configuration form in the dashboard, and is the recommended way to build a provider-agnostic integration.
# Set Default Outbound
Source: https://docs.dograh.com/api-reference/telephony-configs/set-default-outbound
POST /api/v1/organizations/telephony-configs/{config_id}/set-default-outbound
Designate this configuration as the org-wide default for outbound calls
Campaigns and ad-hoc calls that omit `telephony_configuration_id` use the default outbound configuration. Setting a new default automatically clears the previous one — at most one configuration can be the default at a time.
# Update Configuration
Source: https://docs.dograh.com/api-reference/telephony-configs/update
PUT /api/v1/organizations/telephony-configs/{config_id}
Rename a configuration or rotate its credentials
The `provider` cannot be changed once a configuration exists — create a new configuration if you need to switch providers.
When rotating credentials, you only need to send the fields you're changing. Re-submitting a masked sensitive value preserves the original secret, so it's safe to round-trip a `GET` response back to a `PUT`.
# Agent UUID
Source: https://docs.dograh.com/configurations/agent-uuid
Find and use your workflow's stable Agent UUID
The **Agent UUID** is the workflow's stable identifier. In API routes and payloads you may also see it called `workflow_uuid`.
Unlike numeric workflow IDs, the Agent UUID is portable across environments and does not change when you publish new versions of the same workflow.
## Where to find it
You can copy the Agent UUID from two places in the dashboard.
### From the workflow editor
1. Open your agent in the workflow editor
2. Click the **⋮** menu in the top-right of the header
3. Click **Copy Agent UUID**
### From the agent's Settings page
1. Open the agent and go to **Settings**
2. Scroll to the **Agent UUID** section
3. Click the UUID code block, or use **Copy UUID**
## Where you use it
Use the Agent UUID for workflow-level routes such as:
* `POST /api/v1/public/agent/workflow/{workflow_uuid}`
* `POST /api/v1/public/agent/test/workflow/{workflow_uuid}`
* `wss:///api/v1/agent-stream/{provider}/{agent_uuid}`
Do not confuse the Agent UUID with an API Trigger node UUID (`trigger_path`).
* Use the Agent UUID for workflow-level routes like `/public/agent/workflow/{workflow_uuid}`
* Use the API Trigger node UUID for trigger routes like `/public/agent/{uuid}`
* The API Trigger node path can be edited if you want something more descriptive
* The Agent UUID is generated by Dograh and cannot be changed
See [API Trigger](/voice-agent/api-trigger) for trigger-node URLs.
# API Keys and Service Keys
Source: https://docs.dograh.com/configurations/api-keys
You can create API Keys to trigger Dograh Voice Agents and Service Keys to use with Inference Providers
The option to create the Keys are in [https://app.dograh.com/api-keys](https://app.dograh.com/api-keys) if you are using hosted version, or [http://localhost:3010/api-keys](http://localhost:3010/api-keys) if you are using the self hosted version.
### API Keys
API keys can be used to trigger a voice agent from an external system, like n8n or programatically from your other workflows. In order to generate that, you can go to `/api-keys` and create a new key.
Please note that you must copy and keep the API key secretly, since this is the only time that you would be able to copy it. If you lose it, you can always delete that, if its not being used anywhere, and create a new API key.
### Service Keys
Service Keys are the keys which you generate to be used in [Model Configurations](inference-providers). In order to generate that, you can go to `/api-keys` and create a new key.
You can use a Service Key created in Dograh Cloud (`https://app.dograh.com/api-keys`) in your self-hosted Dograh deployment. Create the Service Key from your Dograh Cloud account, then paste it into **Model Configurations** in your self-hosted instance to use Dograh-managed inference providers. You can purchase Dograh credits in Dograh Cloud; billing happens on the Cloud account that owns the Service Key.
Service Keys are scoped to the Dograh Cloud account that created them. You cannot use a Service Key from one cloud-hosted account in another cloud-hosted account; create a new Service Key from the account where you want to use it.
# Call events
Source: https://docs.dograh.com/configurations/call-events
Export per-call diagnostics to your organization's BigQuery table.
You need an existing BigQuery table with the schema below and a Google identity
that can read its schema and insert rows. Call event export is disabled until
you configure it in **Settings → Call events**.
1. Select **BigQuery** as the destination.
2. Enter the table as `project.dataset.table`.
3. Choose a service account and enter its email and PEM private key. On a
self-hosted installation, you can instead select **Deployment identity** to
use the Google credentials configured on your Dograh server.
4. Select **Test connection** to check authentication and table compatibility.
This reads table metadata without inserting events; successful exports also
require write permission.
5. Enable **Export call events** and select **Save**.
Each organization has its own destination and credentials. Saved private keys
are masked in the UI. Keep the masked value to retain your existing key, or
replace it to rotate the key.
**BigQuery schema**
Use these scalar columns with nullable fields; `ts` and `event` can also be
required. The adapter does not create or migrate tables.
| Column | Type |
| - | - |
| `ts` | TIMESTAMP |
| `event` | STRING |
| `run_id`, `org_id`, `workflow_id`, `turn` | INTEGER |
| `severity` | STRING |
| `value_ms` | FLOAT |
| `node_id`, `node_name` | STRING |
| `detail` | JSON (STRING also supported) |
You receive one row per diagnostic event. `ts` is when the event happened, and
`detail` contains JSON, stored as a native JSON value or serialized text according
to the column type. Events cover speech, accepted user turns,
mute state, tool execution, silence warnings, stage latencies, pipeline errors
and the final call summary. The transcription event records character counts
and empty/finalized flags; it does not contain transcript text or audio.
**Delivery behavior**
Events are sent after a call finishes, with their original timestamps.
Capture and export are separate from the real-time feedback logs.
Dograh stores the destination settings in its organization configuration;
event payloads are not persisted in Postgres or Redis.
Exports use bounded retries for retryable row and HTTP errors. BigQuery's
insertion IDs provide best-effort deduplication; duplicates can still occur.
Capture and pending export queues are bounded, and unsent events can be lost
on a process crash, queue overflow or exhausted retries. There is no durable
replay queue in this version. Export failures and dropped-event counts appear
in the API worker logs.
Table changes apply to new calls. An active call's batch is discarded if its
destination is changed or disabled before export, so it cannot accidentally
land in a different table. Credentials are read again before each delivery
attempt to support rotation. Requests already sent to BigQuery cannot be
recalled when you disable export.
# Model Configurations
Source: https://docs.dograh.com/configurations/inference-providers
Configure the speech-to-speech, Dograh-managed, or bring-your-own-key models your Dograh agents use.
## How model configurations work
Model Configurations define the default AI model setup for your organization. Agents use this configuration unless you set agent-level model overrides in the agent settings.
To configure models, open **Models** in your Dograh dashboard:
* **Hosted:** `https://app.dograh.com/model-configurations`
* **Self-hosted:** `http://localhost:3010/model-configurations`
* **Local development:** `http://localhost:3000/model-configurations`
The Models page has three top-level sections:
| Section | When to use it |
| - | - |
| **Speech to Speech** | Use a realtime speech-to-speech model for the live conversation. You still configure an LLM alongside it for variable extraction and QA. |
| **Dograh** | Use Dograh-managed LLM, voice, and transcriber models behind one Dograh Service Key. |
| **BYOK** | Bring your own provider keys and configure LLM, Voice, Transcriber, and Embedding models separately. |
Model settings are organization-scoped. If no agent-level override is set, every agent in the organization uses the saved global configuration.
## Speech to Speech
Use **Speech to Speech** when you want a realtime model to handle the live spoken conversation directly. In this mode, the realtime model handles speech input and speech output, so you do not configure separate Voice and Transcriber services.
The Speech to Speech section has nested tabs:
| Tab | What to configure |
| - | - |
| **Realtime Model** | The speech-to-speech provider, model, voice, and API key. |
| **LLM** | A standard LLM used for non-realtime tasks such as variable extraction and QA analysis. |
| **Embedding** | An embedding model used by features that need embeddings, such as retrieval from knowledge base content. |
An LLM is still required when you use Speech to Speech. The realtime model handles the live voice conversation, but Dograh uses the LLM for analysis tasks that happen outside the live audio stream.
## Dograh
Use **Dograh** when you want Dograh to manage the model providers for you. This path uses one Dograh Service Key for Dograh-managed models instead of separate provider keys for LLM, Voice, and Transcriber.
Configure:
| Field | What it controls |
| - | - |
| **Voice** | The Dograh-managed voice to use. |
| **Speed** | The voice playback speed. |
| **Language** | The language behavior, including multilingual auto-detect when available. |
| **API Key** | Your Dograh Service Key. Create Service Keys from **Developers**. |
For details on creating and using Service Keys, see [API Keys and Service Keys](api-keys#service-keys).
## BYOK
Use **BYOK** when you want to bring your own provider accounts and API keys. This gives you separate control over each model category.
When you use BYOK or external model providers, Dograh sends only the data required for the selected service to operate. Depending on the provider and service type, this may include prompts, conversation history, transcripts, audio, generated text, tool/function definitions, tool inputs or results, and request metadata.
Provider data handling varies. Review each provider's data processing, retention, model training, and regional hosting policies before using sensitive data.
The BYOK section has nested tabs:
| Tab | What to configure |
| - | - |
| **LLM** | The chat or reasoning model provider, model, optional base URL, and API key. |
| **Voice** | The text-to-speech provider, voice, model, speed, optional base URL, and API key. |
| **Transcriber** | The speech-to-text provider, model, language, and API key. |
| **Embedding** | The embedding provider, model, and API key. |
Provider-specific fields appear only when they apply. For example, OpenAI-compatible LLM providers can expose a **Base URL** field, ElevenLabs voices can expose a voice ID, and transcribers can expose language options.
## Agent-level model overrides
You can override the organization model configuration for an individual agent. This is useful when different agents need different models, voices, transcribers, or providers.
To configure an override:
1. Open the agent.
2. Go to **Settings**.
3. Open **Model Overrides**.
4. Enable the override for the service you want to customize.
5. Configure the provider, model, and keys for that service.
6. Save the agent settings.
Agent-level overrides are selective. For example, you can override only the Voice service for one agent while it continues to use the organization-level LLM and Transcriber configuration.
# Interruption Handling
Source: https://docs.dograh.com/configurations/interruption
Control whether users can interrupt the bot while it is speaking by configuring the Allow Interruption toggle on each node.
## Overview
Interruption handling controls whether the user can "barge in" and interrupt the bot while it is speaking. This is configured **per node** in the workflow editor, giving you fine-grained control over conversation flow.
## What is VAD?
VAD (Voice Activity Detection) is what makes barge-in possible — it's the model that listens to the incoming audio stream and decides whether the caller is currently speaking. Dograh's pipeline runs [Silero VAD](https://github.com/snakers4/silero-vad) by default to detect the start and end of user speech in real time. Some realtime providers (such as OpenAI Realtime, Azure Realtime, and Grok Voice Agent) supply their own VAD signals, so local Silero VAD is skipped for those calls.
When **Allow Interruption** is enabled on a node, VAD is what triggers the interrupt: the moment it detects the caller has started talking, the bot's speech is cut off and the pipeline starts processing the new input. When interruption is disabled, VAD output for the user's mic is ignored until the bot finishes speaking.
VAD also feeds turn-taking more generally — it's part of how Dograh decides a caller has finished a turn and the agent should respond, independent of whether interruption is enabled.
**Valid sample rates**: Dograh's pipeline runs VAD at either **8000 Hz** or **16000 Hz** — no other value is accepted. The overall pipeline sample rate is capped at 16 kHz to satisfy this. This only matters if you're integrating a [custom telephony provider](/integrations/telephony/custom#audio-format-considerations); telephony providers built into Dograh (Twilio, Vonage, Plivo, etc.) already negotiate a supported rate for you.
## How It Works
Each node in your workflow has an **Allow Interruption** toggle:
* **Disabled (default)** — The bot finishes its entire response before accepting user input. The user's microphone is temporarily muted while the bot speaks.
* **Enabled** — The bot stops speaking as soon as the user starts talking, and immediately processes their input. This creates a natural, conversational experience.
When interruption is disabled and the user tries to speak during bot speech, a one-time warning appears in the live transcript indicating that interruption is disabled for that step.
## When to Disable Interruption
Disabling interruption is useful when the bot needs to deliver a complete message without being cut off:
* **Legal disclaimers** — Ensure the full disclaimer is spoken before proceeding.
* **Critical instructions** — Step-by-step directions that lose meaning if partially heard.
* **Greeting or introduction** — Let the bot finish its opening before the user responds.
* **Confirmation summaries** — Read back important details (appointment times, order totals) in full.
## When to Enable Interruption
Keep interruption enabled for interactive conversation stages:
* **Q\&A or objection handling** — Let the user jump in naturally.
* **Open-ended discussion** — Feels more human when either party can interject.
* **Long responses** — Allow the user to redirect if the bot goes off track.
## Configuring Interruption
1. Open your workflow in the **Voice Agent Builder**.
2. Select the node you want to configure.
3. Toggle **Allow Interruption** on or off in the node settings panel.
4. Save your workflow.
You can set different interruption behavior for each node. For example, disable interruption on your Start Node greeting but enable it on all subsequent Agent Nodes.
## What the User Experiences
| Interruption | Bot Speaking | User Speaks | Result |
| - | - | - | - |
| Enabled | Yes | Yes | Bot stops, processes user input |
| Disabled | Yes | Yes | Bot continues, user input is ignored until bot finishes |
| Either | No | Yes | User input is processed normally |
When interruption is disabled, the platform mutes the user's audio input while the bot is speaking. Once the bot finishes, the microphone is automatically unmuted and the user can respond normally.
# LLM
Source: https://docs.dograh.com/configurations/llm
Voice Agents use LLM (Large Language Models), which are trained to understand the conversational context, and respond to users.
Dograh platform supports OpenAI, Google AI Studio, Google Vertex AI, Azure OpenAI, AWS Bedrock, Groq, Hopper, OpenRouter, Hugging Face, MiniMax, Sarvam, and Dograh-managed LLMs. There are some models provided by default for you to choose from the drop down.
LLM providers receive the data needed to generate responses, such as prompts, conversation history, model settings, and any configured tool/function definitions or tool call context. Review the provider's data processing, retention, model training, and regional hosting policies before using sensitive data.
For locally deployed or self-hosted LLMs, Dograh also supports OpenAI-compatible endpoints such as Ollama and vLLM.
If you don't find a model in the drop down, you can always add a model manually.
## OpenRouter provider routing
By default OpenRouter spreads requests across every provider that serves a model, weighted toward the cheapest. For voice agents that can add noticeable latency, because each turn may land on a slower provider.
Use **Provider order** to list the OpenRouter provider slugs to try first, in order, one slug per entry (for example, `groq`). The slugs are shown on each model's page on OpenRouter. If none of the listed providers is available, OpenRouter falls back to its other providers, so calls keep working.
# Tracing
Source: https://docs.dograh.com/configurations/tracing
Trace your agent runs to debug issues, understand LLM behavior, and refine your prompts.
## Overview
Tracing gives you full visibility into what happened during a test call or live call. It's one of the most powerful features for making your voice agents production-ready.
With traces, you can see:
* The exact conversation that occurred
* What prompts were sent to the LLM
* Which tools the LLM had access to (and which it called)
* Speech-to-text transcriptions of user input and Transcription errors
* Tool call requests and responses
## Accessing Traces
After a call ends, you'll see a call summary screen. Click the **Traces** tab to view the detailed trace log for that call.
## Understanding Trace Sections
Traces are organized by event type. The two most important sections to focus on are:
### STT (Speech-to-Text)
These entries capture the transcription of what the user said during the call. Each STT entry shows the text conversion of the user's speech at that point in the conversation.
**Example:**
STT: "Yeah, I would want to check what your operating hours are."
### LLM (Language Model Calls)
These entries show every call made to the LLM. Each LLM trace includes:
* **Prompt** - The full prompt sent to the LLM (combination of global prompt + node-specific prompt)
* **Conversation history** - The entire conversation up to that point
* **Available tools** - All tools the LLM can access for that node
* **Response** - What the LLM generated
## Tools in Traces
Each LLM call shows the tools available to the agent at that moment. These fall into three categories:
### System Tools (Default)
Every node includes these by default:
* **Safe Calculator** — Helps LLMs perform math accurately
* **Get Current Time** — Retrieves current time
* **Convert Time** — Converts time to a specific timezone
### Pathway Tools
These correspond to the pathways (node transitions) you've configured. For example, if your node has pathways to "End Call" and "Move to Summary," you'll see those as available tools.
The tool descriptions shown in traces match the descriptions you set in your pathway configuration.
### Custom Tools
Any external tools you've attached to the node (e.g. any custom tools you created for API endpoints for booking, order submission, etc.) will appear here. See the [custom tools documentation](/voice-agent/tools/http-api).
## Tool Calls in Traces
When the LLM decides to call a tool, you'll see:
1. **Tool call request** — The tool name and parameters sent
2. **Tool response** — The result returned (e.g., `{"status": "ok"}`)
The tool call and response also appear in subsequent conversation history, so the LLM knows the outcome.
**Example flow:**
**User:** "Can you book me a table for tomorrow at 7pm?"
**LLM calls:** book\_table(date: "tomorrow", time: "7pm", party\_size: 2)
**Tool response:** `{"status": "done", "confirmation": "Table booked"}`
**Agent:** "I've booked your table for tomorrow at 7pm."
## How Prompts Are Combined
The prompt sent to the LLM is a combination of:
1. **Global Prompt** — Defined in your Global Node settings
2. **Node Prompt** — Defined in the specific node being executed
If you disable the Global Node for a particular node, only the node-specific prompt will be used.
## Key Concept: Conversation History
Every time an LLM call is made, the **entire conversation history up to that point** is passed to the LLM. This means:
* The LLM always has full context
* You can see exactly what context the LLM had when it made a decision
* Useful for debugging unexpected responses
## Setting Up Langfuse Tracing
[Langfuse](https://langfuse.com/docs) is an open-source LLM observability platform — it stores and visualizes traces (prompts, responses, tool calls) so you can debug and iterate on LLM-powered applications outside of Dograh's own trace viewer.
We provide seamless integration with Langfuse for tracing if you want to use your own account. This enables you to use the [playground feature of Langfuse](https://langfuse.com/docs/prompt-management/features/playground). This works on both managed and self-hosted Dograh deployments.
**Setup steps:**
1. Sign up at [Langfuse](https://langfuse.com) and create API credentials
2. In the Dograh UI, go to **Platform Settings** (`/settings`) and enter your Langfuse host, public key, and secret key
3. Click **Save**
Once enabled, traces will be available for every completed call in Dograh.
### Trace visibility
Traces sent to your own Langfuse project are private by default — opening a trace link requires access to that project. **Make traces publicly viewable** in **Platform Settings** turns that off for your project only.
A public trace is readable by anyone holding its URL, with no Langfuse login, and it exposes the full call transcript, prompts, and tool payloads. Only enable it if you intend to share trace links outside your Langfuse project.
`LANGFUSE_TRACES_PUBLIC` is the equivalent switch for the Langfuse project configured by environment variables. It has no effect on organizations that have connected their own Langfuse project — those follow the setting above.
For self-hosted deployments, you can also configure Langfuse via [environment variables](/developer/environment-variables#tracing-langfuse) (`LANGFUSE_SECRET_KEY`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_HOST`, `LANGFUSE_PROJECT_ID`) as a default for all organizations. Tracing activates automatically once credentials are available — no separate enable flag is required. Per-organization credentials set in the UI take precedence over environment variables.
## Quick Reference
| Trace Type | What It Shows |
| - | - |
| STT | User speech transcribed to text |
| LLM | Prompt + conversation + tools + response |
| Tool Call | Tool invocation and response |
## Tips for Using Traces
* **Debug unexpected responses** — Check the LLM trace to see what prompt and context the model received
* **Verify tool calls** — Confirm tools are being triggered with correct parameters
* **Refine prompts** — Use traces to see how your prompt instructions affect LLM behavior
* **Check transcription accuracy** — Review STT entries if the agent misunderstands users
# Transcriber
Source: https://docs.dograh.com/configurations/transcriber
Voice Agents use STT (Speech to Text), to transcribe what the user speaks. This transcribed speech as text goes into an LLM to generate the response that gets played out to the user.
Dograh platform supports Deepgram, OpenAI, Google, Azure Speech, AssemblyAI, Speechmatics, Cartesia, Gladia, Soniox, Sarvam, Smallest AI, Hugging Face, ElevenLabs, and Dograh transcribers. You can take a look at the providers documentation of which language to select for your language requirements.
Transcriber providers receive the data needed to transcribe speech, such as call audio, language settings, keyterms, and request metadata. Review the provider's data processing, retention, model training, and regional hosting policies before using sensitive data.
For locally deployed or self-hosted STT models, Dograh also supports Speaches, an OpenAI API-compatible server for streaming transcription.
Example: Deepgram has their language support documentation at [https://developers.deepgram.com/docs/models-languages-overview#nova-3](https://developers.deepgram.com/docs/models-languages-overview#nova-3)
## Deepgram Flux language hints
With the `flux-general-multi` model, **Language hints** biases transcription toward the languages you expect callers to speak. Pick several when callers switch between known languages, for example Hindi and English. Hints are combined with the **Language** setting, and leaving both on auto-detect lets Flux detect the language on its own.
Hints only help within the languages Flux multilingual supports. They do not add new ones. See [Deepgram's language prompting guide](https://developers.deepgram.com/docs/flux/language-prompting).
# Voice
Source: https://docs.dograh.com/configurations/voice
Voice Agents use TTS (Text to Speech), which generates audio that LLMs generate during the course of a conversation. This is the audio that the end user having the conversation listens to.
Dograh platform supports ElevenLabs, OpenAI, Google, Azure Speech, Deepgram, Cartesia, Smallest AI, MiniMax, Sarvam, Rime, Inworld, Camb.ai, xAI, LMNT, Speechify, and Dograh TTS engines. There are some voices from the providers that we ship by default. You can refer to the providers API documentation to select a voice ID that's most relevant for your language requirement.
Voice providers receive the data needed to synthesize speech, such as generated text, selected voice, model settings, and request metadata. Review the provider's data processing, retention, model training, and regional hosting policies before using sensitive data.
For locally deployed or self-hosted TTS models, Dograh also supports Speaches, an OpenAI API-compatible server for speech generation.
If you don't find your favourite voice, you can always add the voice ID manually.
# Setup Reference
Source: https://docs.dograh.com/contribution/reference
Host-managed setup, devcontainer internals, the Dev Container CLI, and fork maintenance.
Everything on this page is optional reading — [Setup](/contribution/setup) covers the normal path.
## Host-managed setup
Run everything directly on your host instead of inside the devcontainer. You need Git, Node.js 24, Python 3.13, and Docker (for Postgres, Redis, and MinIO).
1. Run the contributor bootstrap. It configures `origin` as your fork and `upstream` as `dograh-hq/dograh`, initializes the pipecat submodule, creates the Python venv, and copies the `.env` templates:
```bash macOS/Linux theme={null}
bash scripts/setup_fork.sh
```
```powershell Windows theme={null}
.\scripts\setup_fork.ps1
```
2. Activate the virtual environment:
```bash macOS/Linux theme={null}
source venv/bin/activate
```
```powershell Windows theme={null}
.\venv\Scripts\Activate.ps1
```
3. Install Python requirements:
```bash macOS/Linux theme={null}
bash scripts/setup_requirements.sh --dev
```
```powershell Windows theme={null}
.\scripts\setup_requirements.ps1 -Dev
```
4. Install UI dependencies (make sure `node --version` reports 24, e.g. via `nvm use 24`):
```bash theme={null}
cd ui && npm install && cd ..
```
5. Start Postgres, Redis, and MinIO:
```bash theme={null}
docker compose -f docker-compose-local.yaml up -d
```
6. Start the backend:
```bash macOS/Linux theme={null}
bash scripts/start_services_dev.sh
```
```powershell Windows theme={null}
.\scripts\start_services_dev.ps1
```
7. Start the UI, then open `http://localhost:3000`:
```bash theme={null}
cd ui && npm run dev
```
## Devcontainer internals
The checked-in `.devcontainer/` follows the Dev Containers specification, so it should also work in Cursor, JetBrains IDEs, and other compatible editors. Only the VS Code path is actively tested — if something breaks elsewhere, please open a [GitHub issue](https://github.com/dograh-hq/dograh/issues).
### What the bootstrap does
While the container starts, the devcontainer will:
* Initialize the `pipecat` git submodule on the host through `initializeCommand`
* Bring up `postgres`, `redis`, and `minio` through Docker Compose and wait for them to be healthy
* Seed the `venv` named volume from the image-baked Python 3.13 venv
* Reinstall `pipecat` as editable from the bind-mounted submodule so source edits take effect
* Create `api/.env`, `api/.env.test`, and `ui/.env` from their `*.example` templates if they do not already exist
* Run `npm ci` for `ui/` and `api/mcp_server/ts_validator/`
In the API env files, `localhost` for Postgres, Redis, and MinIO is rewritten to the docker network aliases `postgres`, `redis`, and `minio`. `MINIO_PUBLIC_ENDPOINT` deliberately stays on `localhost` because the browser on your host loads from it.
If you already had an `api/.env` or `api/.env.test` from host-managed development with `localhost` hosts for Postgres, Redis, or MinIO, the bootstrap leaves it untouched. Edit those URLs to the docker network aliases before starting the backend inside the devcontainer, or delete the file and let the bootstrap recreate it on the next rebuild.
### When to rebuild the container
The workspace bind mount and the `venv` / `node_modules` named volumes persist across container restarts, so you rarely need to rebuild. Rebuild only when one of these changes:
* `.devcontainer/Dockerfile` or `.devcontainer/devcontainer.json`
* `api/requirements.txt` or `api/requirements.dev.txt`
* The `pipecat/` submodule
Plain source edits, `ui/package.json`, and the `.env.example` templates do **not** require a rebuild. After a `git pull`, a quick check:
```bash theme={null}
git diff HEAD@{1} HEAD -- .devcontainer api/requirements.txt api/requirements.dev.txt pipecat
```
If the diff is empty, you can keep your current container.
### Personal install hook
Anything you install inside the container outside the named volumes, notably under `/home/vscode`, is wiped on rebuild. This includes `npm i -g` packages and tools like the Claude or Codex CLIs.
To reinstall personal tooling automatically on every rebuild, drop an executable script at `.devcontainer/install.local.sh`. It is gitignored and runs at the tail of the post-create bootstrap. Example:
```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
command -v claude >/dev/null 2>&1 || curl -fsSL https://claude.ai/install.sh | bash
command -v codex >/dev/null 2>&1 || npm i -g @openai/codex
```
Keep entries idempotent with `command -v` or a marker file so re-runs are cheap and safe.
## Dev Container CLI
To use the devcontainer without VS Code, install the CLI once on your host (requires Node.js):
```bash theme={null}
npm install -g @devcontainers/cli
```
Start or rebuild the container from the repository root:
```bash theme={null}
devcontainer up --workspace-folder .
```
Open a shell in the running workspace container:
```bash theme={null}
devcontainer exec --workspace-folder . bash
```
Run a one-off command from the host, e.g. starting the backend or the UI:
```bash theme={null}
devcontainer exec --workspace-folder . bash scripts/start_services_dev.sh
devcontainer exec --workspace-folder . bash -lc 'cd ui && npm run dev -- --hostname 0.0.0.0'
```
## Fork and upstream remotes
The contributor bootstrap (`scripts/setup_fork.sh`) configures two remotes:
* `origin`: your fork, where you push
* `upstream`: `dograh-hq/dograh`, where new commits land
If you cloned `dograh-hq/dograh` directly instead of your fork, run it once (inside the devcontainer is fine):
```bash theme={null}
bash scripts/setup_fork.sh
```
To pull in upstream changes:
```bash theme={null}
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
```
Check your remotes any time with `git remote -v`. You should see:
```bash theme={null}
origin https://github.com//dograh.git (fetch/push)
upstream https://github.com/dograh-hq/dograh.git (fetch/push)
```
Always push feature branches to **`origin`** (your fork), then open a pull request against `dograh-hq/dograh:main`. Never push directly to `upstream`.
# Setup
Source: https://docs.dograh.com/contribution/setup
Get a Dograh dev environment running and learn the daily commands.
Dograh is a Next.js UI (`ui/`), a FastAPI backend (`api/`), and a [Pipecat](https://github.com/pipecat-ai/pipecat)-based voice pipeline (`pipecat/`, a git submodule), backed by Postgres, Redis, and MinIO.
**Using Claude Code or Codex?** Install the [Dograh setup skill](https://github.com/dograh-hq/dograh-plugins) (`/plugin marketplace add dograh-hq/dograh-plugins`, then `/plugin install dograh@dograh`) and ask your agent to *"set up Dograh for development"* — it runs the steps below for you and verifies the stack is healthy.
## Set up
You need Git, a local Docker engine (such as Docker Desktop), and VS Code with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
1. Fork [dograh-hq/dograh](https://github.com/dograh-hq/dograh) and clone **your fork**:
```bash theme={null}
git clone https://github.com//dograh
cd dograh
```
2. Open the folder in VS Code and run **Dev Containers: Reopen in Container**. The first build takes several minutes — it starts Postgres, Redis, and MinIO, seeds the Python venv, creates the `.env` files, and installs UI dependencies. Later opens are fast.
3. Start the backend from a terminal inside the container. The script waits for the health check and prints a status summary, so when it exits successfully the backend is up:
```bash theme={null}
bash scripts/start_services_dev.sh
```
4. Start the UI from a second terminal inside the container:
```bash theme={null}
cd ui && npm run dev -- --hostname 0.0.0.0
```
5. Open the app at `http://localhost:3000`.
If these steps do not work for you, please open an issue on [GitHub](https://github.com/dograh-hq/dograh/issues).
Run `bash scripts/setup_fork.sh` once. It prompts for your fork URL, points `origin` at it, and adds `upstream` as `dograh-hq/dograh`. See [Fork and upstream remotes](/contribution/reference#fork-and-upstream-remotes).
The devcontainer also runs headless via the [Dev Container CLI](/contribution/reference#dev-container-cli), or you can run everything [directly on your host](/contribution/reference#host-managed-setup).
## Daily workflow
| What | How |
| - | - |
| Restart the backend | Re-run `bash scripts/start_services_dev.sh` — it stops the old processes first |
| Stop the backend | `bash scripts/stop_services.sh` |
| Backend logs | `tail -f logs/latest/*.log` |
| Code reload | Edits under `api/` auto-reload; `ari_manager`, `campaign_orchestrator`, and `arq` need a backend restart |
| Rebuild the container | Only when `.devcontainer/`, `api/requirements*.txt`, or `pipecat/` change — plain source edits never need it |
| Sync the pipecat submodule | `git submodule update --init --recursive` after pulling a submodule bump |
## Debugging
The repo ships debug configurations in `.vscode/launch.json` for every backend service and for pytest. To run under the debugger instead of the start script:
1. Stop the script-managed backend if it is running, so the ports are free:
```bash theme={null}
bash scripts/stop_services.sh
```
2. In VS Code's **Run and Debug** panel, pick a configuration and press F5:
| Configuration | What it runs |
| - | - |
| **API: Uvicorn (reload)** | The FastAPI backend, with auto-reload (port from `UVICORN_PORT` in `api/.env`, default 8000) |
| **API: Arq worker (watch)** / **API: Campaign orchestrator** / **API: ARI manager** | The other backend services — launch them alongside Uvicorn as needed |
| **Tests: API (pytest, full suite / current file)** | pytest under the debugger, against `api/.env.test` |
| **Tests: Pipecat (pytest, current file)** / **Python: Current file** | Debug a pipecat test or any standalone script |
All configurations load `api/.env` (test configs use `api/.env.test`) and set `justMyCode: false`, so you can step into FastAPI and pipecat code — breakpoints in the `pipecat/` submodule work because it is installed editable. Inside the devcontainer the Python interpreter is preselected; on a host-managed setup, pick `./venv/bin/python` via **Python: Select Interpreter** first.
## Repository layout
| Path | What's there |
| - | - |
| `ui/` | Next.js frontend — the workflow builder, dashboard, and agent editor |
| `api/` | FastAPI backend — REST API, campaign orchestration, telephony, ARQ background workers |
| `pipecat/` | Git submodule for the voice pipeline (STT → LLM → TTS) |
| `docs/` | This documentation site, written in MDX, previewed with `mint dev` |
| `sdk/` | Python/TypeScript SDKs for driving Dograh programmatically |
| `scripts/` | Setup, deployment, and update scripts |
| `deploy/` | nginx and coturn config templates used by remote deployment |
## Contributing a change
1. Create a branch and make your change.
2. Push to your fork (`origin`) and open a pull request against `dograh-hq/dograh:main`.
3. A maintainer reviews and merges.
Bug reports and feature ideas go through [GitHub Issues](https://github.com/dograh-hq/dograh/issues) and [Ideas](https://github.com/orgs/dograh-hq/discussions/categories/ideas). Issues tagged `good first issue` are a good place to start. For questions while you're working, use the [Dograh Community Slack](https://join.slack.com/t/dograh-community/shared_invite/zt-4anp45mlu-ukR3jTqwdDFMEpqeBhKn6g).
Deploying your own build instead of contributing upstream? See [Deployments](/deployment/introduction) instead.
# Calls & Runs
Source: https://docs.dograh.com/core-concepts/calls-and-runs
The lifecycle of a call and how runs capture its results
Every time a workflow executes, Dograh creates a **run**. The run is the record of that execution: what was said, what data was collected, how long it took, and what it cost.
## Calls vs runs
A **call** is the audio connection — whether over the phone via a telephony provider, or through the browser via Web Call.
A **run** is the Dograh record of the workflow execution. Every call — outbound, inbound, web, or campaign — creates a run with the same contents: transcript, recording, gathered context, usage, and cost. Campaign runs are additionally linked to their parent campaign.
## Call lifecycle
The call lifecycle tracks the telephony connection — from the moment a number is dialed to when the line drops.
```mermaid theme={null}
stateDiagram-v2
[*] --> Ringing: Number dialed
Ringing --> Connected: Caller answers
Ringing --> Ended: No answer / voicemail
Connected --> Ended: Conversation completes
Connected --> Ended: Error or disconnect
Ended --> [*]
```
For inbound calls, the flow starts when the caller dials in and the telephony provider routes the call to Dograh.
## Inbound vs outbound
**Outbound calls** are initiated by Dograh — you trigger them via the API or dashboard with a phone number and agent. Used for proactive outreach, reminders, and campaigns.
**Inbound calls** are initiated by the caller — your telephony provider routes incoming calls to a Dograh agent via a webhook URL. Used for support lines, hotlines, and IVR replacement.
## Web Calls
Web Call lets you talk to your agent directly from the browser — no phone number or telephony setup required. It runs the full pipeline: STT, LLM, TTS, recording, and transcript, exactly the same as a phone call.
Use it to try out your agent before going live. The run view shows the live transcript as the conversation progresses, node transitions as the workflow moves through the graph, and any tool or function calls the agent makes in real time.
## What a run contains
After a call completes, the run record includes:
| Field | Description |
| - | - |
| `status` | Final run state |
| `recording_url` | Download URL for the call audio |
| `transcript_url` | Download URL for the conversation transcript |
| `gathered_context` | Structured data extracted by the agent during the call |
| `initial_context` | The data passed in when the call was triggered |
| `usage_info` | Duration in seconds, token count |
| `cost_info` | Cost in USD |
# Campaigns
Source: https://docs.dograh.com/core-concepts/campaigns
Running a voice agent against a list of contacts at scale
A campaign is how you run a workflow against many contacts automatically. Instead of triggering calls one by one via the API, you upload a list of phone numbers and Dograh dials them for you — respecting scheduling windows, concurrency limits, and retry rules.
## How a campaign works
```mermaid theme={null}
flowchart LR
CSV[Contacts CSV] --> Upload[Upload to Dograh]
Upload --> Campaign[Create Campaign]
Campaign --> Schedule[Scheduling & Concurrency]
Schedule --> Calls[Outbound Calls]
Calls --> Runs[Run Records]
Runs --> Results[Progress & Reports]
```
1. **Upload a contacts CSV** — must have a `phone_number` column; any extra columns become `initial_context` for each call
2. **Create the campaign** — link it to a workflow, set concurrency, time slots, and retry behaviour
3. **Start it** — Dograh begins dialing contacts up to your concurrency limit
4. **Monitor progress** — track processed, completed, failed, and pending counts in real time
5. **Pause and resume** — stop and restart at any point without losing progress
## The contacts CSV
The CSV drives the campaign. Each row is one contact.
```csv theme={null}
phone_number,customer_name,account_id,plan,greeting_override_type,greeting_override_text
+14155550100,Jane Smith,acc_001,premium,text,Hello {{customer_name}}
+14155550101,Bob Jones,acc_002,basic,text,Welcome back {{customer_name}}
```
Columns beyond `phone_number` are automatically passed as `initial_context` to each call, making them available as template variables in your agent's prompt — so each call can feel personalised at scale.
To override the Start node greeting for a row, set `greeting_override_type` to `text` and provide `greeting_override_text`. The text can use other columns as template variables. For a recorded greeting, set the type to `audio` and provide its public ID in `greeting_override_recording_id`. Leave the override columns empty to use the greeting configured in the Start node.
## Scheduling and concurrency
**Concurrency** controls how many calls run simultaneously. It's capped by your telephony plan. Set it conservatively to start.
**Time slots** restrict when Dograh is allowed to dial — useful for respecting business hours or regulations:
```json theme={null}
{
"timezone": "America/New_York",
"time_slots": [
{ "day": "monday", "start": "09:00", "end": "17:00" },
{ "day": "tuesday", "start": "09:00", "end": "17:00" }
]
}
```
If no time slots are set, Dograh dials continuously once the campaign is started.
## Retry behaviour
Dograh can automatically retry contacts who didn't answer, were busy, or went to voicemail:
```json theme={null}
{
"retry_config": {
"max_attempts": 3,
"retry_interval_minutes": 60
}
}
```
## Circuit breaker
The circuit breaker automatically pauses a campaign when the call failure rate gets too high — protecting against wasted spend and telephony reputation issues caused by a misconfigured agent or a bad contact list.
When enabled, Dograh monitors the failure rate within a rolling time window. If it exceeds the threshold, the campaign is paused automatically and must be manually resumed after the issue is investigated.
| Setting | Default | Description |
| - | - | - |
| Failure Threshold (%) | `50` | Pause when the failure rate within the window exceeds this percentage |
| Window (seconds) | `120` | Rolling time window over which the failure rate is calculated |
| Min Calls in Window | `5` | Minimum number of calls required before the circuit breaker can trip — prevents false positives on small samples |
A campaign paused by the circuit breaker behaves the same as a manually paused campaign — in-flight calls complete normally, and it can be resumed once the underlying issue is resolved.
## Campaign lifecycle
| Status | Meaning |
| - | - |
| `draft` | Created but not started |
| `running` | Actively dialing |
| `paused` | Stopped; resumes from where it left off |
| `completed` | All contacts processed |
| `failed` | Encountered a fatal error |
You can pause and resume a campaign at any time. In-flight calls complete normally before a pause takes effect.
## Results
Each contact's call creates a run record with the full transcript, recording, and gathered context — same as a manually triggered call. Use the [Get Campaign Runs](/api-reference/campaigns/runs) endpoint to retrieve them all.
See the [Campaigns API reference](/api-reference/campaigns) to get started.
# Context & Variables
Source: https://docs.dograh.com/core-concepts/context-and-variables
How data flows into, through, and out of a conversation
Dograh has a simple data model for passing information through a call. Understanding it is key to building agents that feel personalised and to extracting useful results after a call.
## The three context objects
```
initial_context ──► Agent ──► gathered_context
│
[template variables](/voice-agent/template-variables)
(used in prompts)
```
### initial\_context
Data available to the agent before the call starts — the contact's name, account details, appointment information, anything the agent should know upfront. It can be set from several places:
* **[API trigger](/voice-agent/api-trigger)** — pass it in the request body when calling `POST /public/agent/{uuid}` or `POST /telephony/initiate-call`
* **[Campaign CSV](/core-concepts/campaigns)** — columns beyond `phone_number` automatically become `initial_context` fields for each contact's call
* **[Pre-call data fetch](/voice-agent/pre-call-data-fetch)** — enrich the context with data from your CRM or ERP via an HTTP call as the call starts, before the agent speaks
* **[Agent Settings](/voice-agent/template-variables#using-template-variables-for-testing)** — set template context variables on the agent for testing; they're included in test calls from the workflow editor and ignored on production calls
### Template variables
Values from `initial_context` are available in your agent's prompt using `{{double_brace}}` syntax.
```
You are calling {{customer_name}} about their {{plan}} plan,
which renews on {{renewal_date}}. Be friendly and confirm
whether they'd like to continue.
```
When the call starts, Dograh substitutes the values before sending the prompt to the LLM — so the agent speaks naturally as if it already knows the contact.
### Fallback values
If a variable might be missing or empty, use a pipe (`|`) to provide a default value:
```
Hello {{customer_name | there}}, we're calling about your {{plan | current}} plan.
```
When `customer_name` is not set, the agent will say "Hello there" instead of leaving a blank. The syntax is:
```
{{variable_name | fallback_value}}
```
If the variable is present and non-empty, the fallback is ignored and the actual value is used.
### Default variables
Built-in variables for current time and weekday, available in any prompt without setting up `initial_context`.
| Variable | Description | Example output |
| - | - | - |
| `{{current_time}}` | Current time in UTC (or inferred timezone) | `2026-04-02 14:30:45 UTC` |
| `{{current_time_}}` | Current time in the specified timezone | `2026-04-02 20:00:45 IST` |
| `{{current_weekday}}` | Current weekday name in UTC (or inferred timezone) | `Thursday` |
| `{{current_weekday_}}` | Current weekday name in the specified timezone | `Thursday` |
Replace `` with an [IANA timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) such as `Asia/Kolkata`, `America/New_York`, or `Europe/London`.
```
Today is {{current_weekday}} and the current time is {{current_time_America/New_York}}.
```
When you use a timezone suffix on **either** `current_time` or `current_weekday`, the other variable without a suffix will automatically use the same timezone instead of UTC. For example, if your prompt contains both `{{current_time_Asia/Kolkata}}` and `{{current_weekday}}`, the weekday will also be resolved in `Asia/Kolkata`.
### Telephony variables
For telephony calls (inbound and outbound), Dograh automatically adds these variables to `initial_context`:
| Variable | Description | Example |
| - | - | - |
| `{{caller_number}}` | The phone number that initiated the call | `+14155550100` |
| `{{called_number}}` | The phone number that received the call | `+18005550199` |
For **inbound** calls, `caller_number` is the customer's number and `called_number` is your Dograh number. For **outbound** calls, it's the reverse — `caller_number` is your Dograh number and `called_number` is the customer's number.
```
You are speaking with the caller at {{caller_number}}.
```
### gathered\_context
Data the agent extracts *during* the call — the opposite direction of `initial_context`. Use it to turn a conversation into structured data: what the customer wants, whether they confirmed something, a value they gave you out loud.
#### How it gets populated
Turn on **extraction** on an [Agent](/voice-agent/agent) or [End Call](/voice-agent/end-call) node and define one or more variables to extract. Each variable has:
| Field | Description |
| - | - |
| `name` | The key it will appear under in `gathered_context` |
| `type` | `string`, `number`, or `boolean` |
| `prompt` | A natural-language description of what to look for, e.g. *"Did the customer confirm the appointment?"* |
When the conversation reaches that node, the LLM reads the transcript so far and fills in each variable based on its `prompt`. If a value can't be determined from the conversation, the variable is left empty rather than guessed — leave the `prompt` specific enough that the LLM knows exactly what counts as a match.
You can add extraction to more than one node. Each node's extracted variables are merged into the same `gathered_context` object as the call progresses, keyed by `name` — reuse a `name` at a later node if you want to overwrite an earlier value.
#### How to reference it downstream
`gathered_context` is **not available in Agent prompts** — a prompt can only reference `initial_context` fields, because extraction typically happens after the conversation that would use it. To act on extracted data, send it out via a node instead:
| Where | Syntax | Notes |
| - | - | - |
| [Webhook node](/voice-agent/webhook) payload | `{{gathered_context.field_name}}` | Prefixed, since the payload template can also reference `initial_context` |
| [Run record](/developer/webhooks#payload-context-variables) (API / dashboard) | `gathered_context` object | Returned after the run completes, alongside `recording_url` and `transcript_url` |
```json theme={null}
{
"customer": "{{initial_context.customer_name}}",
"resolution": "{{gathered_context.resolution}}",
"callback_requested": "{{gathered_context.wants_callback}}"
}
```
See [Webhook Payloads](/developer/webhooks) for the full list of variables available alongside `gathered_context` in a payload template.
## Data flow example
```mermaid theme={null}
sequenceDiagram
participant App as Your System
participant Dog as Dograh
participant LLM as LLM
App->>Dog: initial_context: {customer_name: "Jane", plan: "premium"}
Dog->>LLM: Prompt with {{customer_name}} and {{plan}} substituted
LLM-->>Dog: Conversation response
Note over Dog,LLM: Call progresses...
Dog->>LLM: Extract: did the customer confirm renewal?
LLM-->>Dog: gathered_context: {renewal_confirmed: true}
Dog-->>App: Run record with gathered_context
```
## Where variables are available
| Location | Variables available |
| - | - |
| Agent node prompts | `initial_context` fields via `{{variable_name}}` |
| Edge conditions | Evaluated against the live conversation — no explicit variable syntax needed |
| Webhook payload templates | All context objects via `{{initial_context.field}}`, `{{gathered_context.field}}` etc. |
| Campaign CSV columns | CSV columns beyond `phone_number` become `initial_context` fields automatically |
# How Dograh Works
Source: https://docs.dograh.com/core-concepts/how-dograh-works
The big picture — from API call to phone conversation to transcript
Dograh is a platform for building and running voice AI agents. You define a conversation flow, connect a phone number, and Dograh handles the rest — transcribing the caller's speech (STT), generating intelligent responses (LLM), speaking them back in a natural voice (TTS), and returning structured results when the call ends.
## The core loop
```mermaid theme={null}
sequenceDiagram
participant U as Dashboard / Your App
participant API as Dograh API
participant STT as Transcriber (STT)
participant LLM as LLM Provider
participant TTS as Voice Synthesizer (TTS)
participant Tel as Telephony Provider
participant Cal as Caller / Contact
U->>API: Trigger call (dashboard or API)
API->>Tel: Initiate outbound call
Tel->>Cal: Phone rings
Cal-->>Tel: Answers
Tel-->>API: Raw audio stream
loop Conversation
API->>STT: Caller audio
STT-->>API: Transcribed text
API->>LLM: Transcript + agent prompt + context
LLM-->>API: Agent response text
API->>TTS: Response text
TTS-->>API: Synthesized audio
API->>Tel: Audio stream
Tel->>Cal: Agent speaks
end
API->>API: Extract context, run webhooks
API-->>U: Run record (transcript, recording, gathered data)
```
## Key components
**[Workflows (Agents)](/core-concepts/workflows-and-agents)**
The conversation logic. A workflow is a graph of nodes (conversation steps) connected by edges (conditional transitions). You define what the agent says, when it moves on, and what data it collects.
**[Runs](/core-concepts/calls-and-runs)**
Every execution of a workflow creates a run. The run record holds the transcript, recording, extracted data, and cost information.
**[Telephony](/integrations/telephony/overview)**
The phone infrastructure. Dograh connects to your telephony provider (Twilio, Vonage, etc.) to place and receive calls. The audio streams between the caller and Dograh in real time.
**[Transcriber (STT)](/configurations/transcriber)**
Converts the caller's speech to text in real time. Dograh sends the audio stream to your configured speech-to-text provider and uses the transcript to drive both the LLM and the final run record.
**[LLM Provider](/configurations/llm)**
Processes the transcript and the active node's prompt to generate the agent's next response. It also evaluates edge conditions to decide when to move the conversation forward.
**[Voice Synthesizer (TTS)](/configurations/voice)**
Converts the LLM's text response to audio and streams it back to the caller. The choice of TTS provider and voice is configurable per agent.
## How it fits together
When you trigger a call:
1. Dograh instructs your telephony provider to dial the number
2. When the caller answers, a real-time audio pipeline opens
3. The caller's speech is transcribed by the STT provider
4. The transcript is sent to the LLM with the active node's prompt and conversation history
5. The LLM responds — the response is synthesized to audio by the TTS provider and streamed to the caller
6. When an edge condition is met, Dograh transitions to the next node
7. When an end node is reached, the call ends
8. Post-call: context is extracted, webhooks fire, the run record is saved
## Next steps
How the conversation graph works
The lifecycle of a call
How data flows through a conversation
Running agents at scale
# Workflows & Agents
Source: https://docs.dograh.com/core-concepts/workflows-and-agents
How conversation flows are defined in Dograh
In Dograh, what you see as an **agent** in the dashboard is called a **workflow** in the API. They are the same thing — a workflow is the underlying definition, agent is the product name for it.
Anywhere the API says `workflow`, think "agent". Anywhere the API says `workflow_definition`, think "the conversation logic inside your agent".
## The graph model
A workflow is a **directed graph** — a set of nodes connected by edges.
```mermaid theme={null}
graph LR
A[Start Call] -->|Caller greets| B[Qualify Intent]
B -->|Wants support| C[Support Agent]
B -->|Wants sales| D[Sales Agent]
C -->|Issue resolved| E[End Call]
D -->|Demo booked| E
```
**Nodes** are the steps in the conversation. Each node has a prompt that tells the LLM what to say and do at that point.
**Edges** are the transitions between nodes. Each edge has a condition — a natural language description of when to move on. The LLM evaluates whether the condition has been met based on the conversation so far.
## Node types
| Type | What it does |
| - | - |
| `startCall` | Entry point for telephony calls. The first thing the agent says when a call connects |
| `agentNode` | An LLM-powered conversation step. The core building block |
| `globalNode` | Defines instructions that apply across all agent nodes (e.g. tone, language, fallback behaviour) |
| `endCall` | Terminates the call |
| `trigger` | Entry point for API-triggered runs (non-telephony) |
| `webhook` | Fires an HTTP request when reached — use for CRM updates, notifications, etc. |
| `qa` | Runs automated quality analysis on the completed call |
## Edges and transitions
An edge connects two nodes and fires when its condition is satisfied:
`transition_speech` is optional — if set, the agent speaks it before moving to the next node.
## Versioning
Every time you update a workflow's `workflow_definition`, Dograh saves a new version while keeping the history. The current version is always what runs. Old versions are retained for auditing.
## Creating workflows
There are two ways to create a workflow via the API:
* **From a definition** — provide the full node/edge graph yourself. Best for programmatic generation.
* **From a template** — describe the use case in natural language and Dograh generates the initial graph using an LLM. Best for getting started quickly.
See the [Workflow Definition Schema](/developer/workflow-schema) for the full field reference.
# Authentication
Source: https://docs.dograh.com/deployment/authentication
Configure how self-hosted Dograh authenticates users — the default local provider, or Stack Auth for social login
Self-hosted Dograh ships with a built-in **local** authentication provider (email + password, backed by a signed JWT). This is the default and needs no external service.
To offer social logins (Google, GitHub, and others), you can delegate sign-in to **[Stack Auth](https://stack-auth.com)**. Enabling it is a **runtime** configuration change — set a few environment variables and restart. The prebuilt `dograhai/dograh-api` and `dograhai/dograh-ui` images work as-is; you do **not** need to rebuild or build from source.
The active provider is controlled by the backend `AUTH_PROVIDER` variable (`local` by default). The frontend discovers the provider — and, for Stack, its public client config — at runtime from the backend's `/api/v1/health` response, so the browser bundle never needs Stack values baked in at build time.
## How it works
1. The backend reads `AUTH_PROVIDER` and the Stack settings from its environment.
2. When `AUTH_PROVIDER=stack`, `/api/v1/health` returns the **public** Stack client config (project id + publishable client key).
3. The UI fetches that at runtime and initializes the Stack SDK in the browser.
4. The **secret server key** is used only server-side (by the backend and the UI's server runtime) and is never sent to the browser.
## Prerequisites
A Stack Auth project. Create one in the [Stack Auth dashboard](https://app.stack-auth.com) and configure the social login providers you want to offer.
## Step 1 — Collect your Stack credentials
From your project in the [Stack Auth dashboard](https://app.stack-auth.com), gather:
| Value | Sensitivity |
| - | - |
| **Project ID** | Public |
| **Publishable client key** | Public (safe to expose in the browser) |
| **Secret server key** | Secret — keep server-side only |
| **API base URL** | Public. For Stack's hosted service this is `https://api.stack-auth.com` |
## Step 2 — Configure the backend (`api`)
Set these on the `api` service. Add them to the `environment:` block of the `api` service in your `docker-compose.yaml`:
```yaml docker-compose.yaml theme={null}
services:
api:
environment:
AUTH_PROVIDER: "stack"
STACK_AUTH_PROJECT_ID: ""
STACK_PUBLISHABLE_CLIENT_KEY: ""
STACK_SECRET_SERVER_KEY: ""
STACK_AUTH_API_URL: "https://api.stack-auth.com"
```
## Step 3 — Configure the UI (`ui`)
The UI runs server-side code (SSR pages and the `/handler/*` auth routes) that calls Stack with the secret server key, so the `ui` service needs that one value too:
```yaml docker-compose.yaml theme={null}
services:
ui:
environment:
STACK_SECRET_SERVER_KEY: ""
```
The `ui` service does **not** need the project id or publishable client key — it receives those from the backend at runtime via `/api/v1/health`. Only the secret server key (used server-side) is set here.
## Step 4 — Restart and verify
Recreate the containers so they pick up the new environment:
```bash theme={null}
docker compose up -d
```
Confirm the backend reports the active provider and the public client config:
```bash theme={null}
curl -s http://localhost:8000/api/v1/health
# expect: "auth_provider":"stack", plus "stack_project_id" and "stack_publishable_client_key"
```
Then open the UI. The sign-in page should now present your configured Stack Auth social login options instead of the local email/password form.
## Environment variable reference
| Variable | Service | Secret | Notes |
| - | - | - | - |
| `AUTH_PROVIDER` | `api` | — | Set to `stack` (default `local`) |
| `STACK_AUTH_PROJECT_ID` | `api` | No | Stack project ID; served to the UI at runtime |
| `STACK_PUBLISHABLE_CLIENT_KEY` | `api` | No | Publishable key; served to the UI at runtime |
| `STACK_SECRET_SERVER_KEY` | `api` + `ui` | **Yes** | Server-side only — never exposed to the browser |
| `STACK_AUTH_API_URL` | `api` | No | Stack REST API base URL |
`STACK_SECRET_SERVER_KEY` is the only secret here. Keep it out of any client-visible config and never bake it into an image. The project ID and publishable client key are public by design — the backend deliberately serves them to the browser so Stack can initialize at runtime.
## Reverting to local auth
Remove the variables above (or set `AUTH_PROVIDER=local`) and restart. The UI detects `local` from the backend at runtime and falls back to the built-in email/password flow — no rebuild required.
# Custom Domain
Source: https://docs.dograh.com/deployment/custom-domain
Deploy Dograh AI with custom domain names and SSL certificates
Deploy Dograh AI with your own custom domain name for a professional production setup. By now, you should be able to create and test a voice agent by following the previous guide to setup the platform on a remote server using [Docker](docker#option-2%3A-remote-server-deployment)
**You don't need to own a domain just to remove the browser warning.** If your server has a public IP, the [remote setup](docker#option-2%3A-remote-server-deployment) already issues a free, auto-renewing Let's Encrypt certificate via [sslip.io](https://sslip.io) (e.g. `https://203-0-113-10.sslip.io`) — no DNS configuration required. Follow this Custom Domain guide only when you want your **own** domain name (e.g. `voice.yourcompany.com`).
## What is Custom Domain Deployment?
Custom domain deployment allows you to run Dograh AI with a personalized domain name (like `voice.yourcompany.com`) instead of using IP addresses. This setup includes:
* **Custom Domain**: Access your application via a memorable domain name
* **Automatic SSL**: Proper SSL certificates from Let's Encrypt or similar providers
* **Professional Setup**: Production-ready configuration for business use
* **Easy Sharing**: Share a clean URL with your team and customers
## Prerequisites
Before starting, ensure you have:
* A domain name you own (e.g., `yourcompany.com`)
* Access to your domain's DNS settings (usually through your domain registrar)
* Dograh AI already running on your server via the [remote deployment](docker#option-2%3A-remote-server-deployment) guide
* Your server's public IP address
## Step 1: Configure DNS Records
You need to create a DNS record that points your domain to your server's IP address.
### Add an A Record
Log in to your domain registrar or DNS provider and add an A record:
| Setting | Value |
| - | - |
| Type | A |
| Name/Host | `voice` (or `@` for root domain) |
| Value/Points to | Your server's IP address (e.g., `203.0.113.50`) |
| TTL | 300 (or default) |
DNS changes can take anywhere from a few minutes to 48 hours to propagate, though most changes take effect within 5-30 minutes. You can check if your DNS has propagated using tools like [dnschecker.org](https://dnschecker.org).
### Verify DNS Propagation
Before proceeding, verify that your domain points to your server:
```bash theme={null}
nslookup voice.yourcompany.com
```
You should see your server's IP address in the response.
## Step 2: Quick Setup (Recommended)
Once your DNS is configured, run the automated setup script that handles the rest.
You must be at the same place where you had run `setup_remote.sh` from. The directory should contain `dograh/` with the artifacts that got created when `setup_remote.sh` was run.
You must not move the `dograh/` directory to a different location after running `setup_custom_domain.sh`, since we set up auto certificate renewal script as certbot renewal hook pointing to the `dograh/` directory.
```bash theme={null}
curl -o setup_custom_domain.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_custom_domain.sh && chmod +x setup_custom_domain.sh && sudo ./setup_custom_domain.sh
```
The script will prompt you for:
* Your domain name
* An email address for Let's Encrypt notifications
It will automatically:
* Verify DNS configuration
* Install Certbot
* Generate Let's Encrypt SSL certificates
* Update the canonical public host/base URL settings in `.env`
* Validate the runtime config that `dograh-init` will render from `.env`
* Configure automatic certificate renewal
* Restart Dograh services through the validated startup wrapper
Once complete, your application will be available at `https://voice.yourcompany.com`.
If you prefer to set things up manually, continue with the steps below.
***
## Manual Setup
If you prefer to configure everything manually, follow these steps instead of using the automated script.
### Install Certbot
Certbot is the official Let's Encrypt client that automates SSL certificate generation.
**Ubuntu/Debian:**
```bash theme={null}
sudo apt update
sudo apt install certbot -y
```
**Amazon Linux/RHEL:**
```bash theme={null}
sudo yum install certbot -y
```
### Point `.env` at your domain
Update `.env` so the canonical remote settings use your domain — `dograh-init` reads these to render nginx's `server_name`. Replace `voice.yourcompany.com` with your actual domain throughout:
```bash theme={null}
cd dograh
sed -i "s/^PUBLIC_HOST=.*/PUBLIC_HOST=voice.yourcompany.com/" .env
sed -i "s|^PUBLIC_BASE_URL=.*|PUBLIC_BASE_URL=https://voice.yourcompany.com|" .env
```
### Start services so nginx can answer the ACME challenge
Bring the stack up (or recreate it) through the validated wrapper. nginx serves the Let's Encrypt HTTP-01 challenge from `certs/.well-known/acme-challenge/` on port 80, so the stack must be **running** during issuance — there's no need to stop it:
```bash theme={null}
./remote_up.sh
```
### Generate the SSL certificate (webroot)
Issue the certificate using the webroot challenge served by the running nginx:
```bash theme={null}
sudo certbot certonly --webroot -w "$(pwd)/certs" -d voice.yourcompany.com
```
Certbot will:
1. Write a challenge file under `certs/.well-known/acme-challenge/`
2. Have Let's Encrypt fetch it over HTTP (port 80) to verify you control the domain
3. Store the certificate in `/etc/letsencrypt/live/voice.yourcompany.com/`
You'll be prompted for an email address (for renewal notices) and to agree to the terms of service. Because nginx keeps serving traffic throughout, issuance and renewal happen with **no downtime** — unlike the older `--standalone` flow, which had to stop the stack to free port 80.
### Copy the certificate and load it
Copy the issued certificate into the `certs/` directory nginx reads, then restart nginx to load it:
```bash theme={null}
sudo cp /etc/letsencrypt/live/voice.yourcompany.com/fullchain.pem certs/local.crt
sudo cp /etc/letsencrypt/live/voice.yourcompany.com/privkey.pem certs/local.key
sudo chmod 644 certs/local.crt certs/local.key
sudo docker compose --profile remote restart nginx
```
### Access Your Application
Your application is now available at:
```
https://voice.yourcompany.com
```
You should see a valid SSL certificate (green padlock) in your browser.
### Set Up Certificate Renewal
Let's Encrypt certificates expire after 90 days. Set up automatic renewal.
Create a renewal hook script that copies the new certificates:
```bash theme={null}
sudo nano /etc/letsencrypt/renewal-hooks/deploy/dograh-reload.sh
```
Add the following content (replace paths as needed):
```bash theme={null}
#!/bin/bash
# Copy renewed certificates to dograh certs directory
cp /etc/letsencrypt/live/voice.yourcompany.com/fullchain.pem /home/ubuntu/dograh/certs/local.crt
cp /etc/letsencrypt/live/voice.yourcompany.com/privkey.pem /home/ubuntu/dograh/certs/local.key
chmod 644 /home/ubuntu/dograh/certs/local.crt /home/ubuntu/dograh/certs/local.key
# Restart nginx to load new certificates
cd /home/ubuntu/dograh
docker compose --profile remote restart nginx
```
Make the script executable:
```bash theme={null}
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/dograh-reload.sh
```
Test that renewal works:
```bash theme={null}
sudo certbot renew --dry-run
```
## Troubleshooting
### Certificate Generation Fails
If Certbot fails to generate certificates:
1. **Port 80 blocked**: Ensure port 80 is open in your firewall and reachable from the internet. With the webroot flow nginx must be **running** and serving the challenge on port 80 (don't stop the stack)
2. **DNS not propagated**: Wait for DNS changes to propagate and verify with `nslookup`
3. **Rate limits**: Let's Encrypt has rate limits. If you've exceeded them, wait before retrying
### SSL Certificate Errors in Browser
If you see SSL errors after setup:
1. Verify the certificates were copied correctly: `ls -la dograh/certs/`
2. Run `./remote_up.sh --preflight-only` in `dograh/` to verify the `dograh-init` runtime render matches `.env`
3. Restart the nginx container: `sudo docker compose --profile remote restart nginx`
### WebRTC Connection Issues
If voice calls don't connect after domain setup:
1. Ensure TCP/UDP ports 3478, 5349, and UDP 49152-49200 are still open
2. Check that `PUBLIC_HOST` / `PUBLIC_BASE_URL` in `.env` match your domain, then re-run `./remote_up.sh`
# Docker
Source: https://docs.dograh.com/deployment/docker
Deploy Dograh AI using Docker for local development and remote server environments
Dograh AI can be deployed using Docker in two main configurations. Choose the option that best fits your needs:
* **Option 1**: For local development and testing on your own machine
* **Option 2**: For remote server deployment with HTTPS (using IP address). If you also have a custom domain, you can first deploy Dograh stack on your server using steps in this document and then proceed to the [Custom Domain](deployment/custom-domain) section.
**Using Claude Code or Codex?** Install the official Dograh setup skill and let your agent drive either deployment below — it orients to your OS, picks local vs remote, runs Dograh's own setup scripts, and verifies the result with a built-in health check.
```text Claude Code theme={null}
/plugin marketplace add dograh-hq/dograh-plugins
/plugin install dograh@dograh
```
```text Codex theme={null}
codex plugin marketplace add dograh-hq/dograh-plugins
codex plugin add dograh@dograh
```
Start a new session, then ask it to *"set up Dograh"* (or run `/dograh-setup` in Claude Code). More at [dograh-hq/dograh-plugins](https://github.com/dograh-hq/dograh-plugins).
## Option 1: Local Docker Deployment
Watch the video tutorial below for a step-by-step walkthrough of setting up Dograh AI locally with Docker.
For local development and testing, you can run Dograh AI directly on your machine using Docker with a small startup script.
### Quick Start
Download the compose file and starter script, then confirm the prompt to start Dograh AI:
```bash macOS/Linux theme={null}
curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml && curl -o start_docker.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.sh && chmod +x start_docker.sh && ./start_docker.sh
```
```powershell Windows theme={null}
Invoke-WebRequest -OutFile docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml; Invoke-WebRequest -OutFile start_docker.ps1 https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.ps1; .\start_docker.ps1
```
This setup:
* Downloads the latest docker-compose.yaml
* Creates `OSS_JWT_SECRET`, `REDIS_PASSWORD`, and MinIO credentials in `.env` if they do not already exist
* Creates `POSTGRES_PASSWORD` for brand-new `.env` files and syncs retained local Postgres volumes to that value before startup
* Prompts before running Docker Compose
* Starts all required services including PostgreSQL, Redis, MinIO, API, and UI
* Pulls the latest images automatically
### Access the Application
Once running (first startup takes 2-3 minutes), open:
```
http://localhost:3010
```
You can disable telemetry by setting `ENABLE_TELEMETRY=false` before running `./start_docker.sh` or `.\start_docker.ps1`.
### Troubleshooting WebRTC Connectivity
The Quick Start above relies on direct peer-to-peer WebRTC between your browser and the API container. On most single-machine setups this works without any extra configuration. If you hit any of the following, you likely need a TURN server in the path:
* The call connects but no audio flows in either direction
* The browser console reports `iceConnectionState: failed`
* You are testing from a browser on your smartphone or another device on your LAN against the laptop running Docker
* A VPN, corporate firewall, or strict NAT sits between the browser and Docker
For these cases, use the alternate local setup script which configures a coturn TURN server alongside the rest of the stack:
```bash macOS/Linux theme={null}
curl -o setup_local.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_local.sh && chmod +x setup_local.sh && ./setup_local.sh
```
```powershell Windows theme={null}
Invoke-WebRequest -OutFile setup_local.ps1 https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_local.ps1; .\setup_local.ps1
```
The script will prompt you for:
* Whether to enable coturn (answer `y`)
* The host browsers should use to reach TURN (press Enter for `127.0.0.1`; use your LAN IP if testing from another device on the same network)
* A shared secret for the TURN server (press Enter to generate a random one)
It creates `docker-compose.yaml`, a `.env` file with JWT and TURN credentials, and the small helper bundle that `dograh-init` uses to render coturn config at startup. Start the stack with the `local-turn` profile so coturn comes up alongside the other services:
```bash theme={null}
docker compose --profile local-turn --profile tunnel up --pull always
```
The application is still available at `http://localhost:3010`.
To verify that media is actually traversing TURN (and not silently falling back to a direct path), set `FORCE_TURN_RELAY=true` in `.env` and restart the API. The browser will then only use relay ICE candidates — if TURN is misconfigured or unreachable, the call fails cleanly with `ICE failed` in the console instead of appearing to work. Set it back to `false` once you have verified connectivity.
## Option 2: Remote Server Deployment
Watch the video tutorial below for a step-by-step walkthrough of deploying Dograh AI to a remote server.
Deploy Dograh AI on a remote server to make it accessible from anywhere. If your server has a **public IP**, setup obtains a free, trusted HTTPS certificate automatically via [sslip.io](https://sslip.io) and Let's Encrypt — no domain name or DNS configuration required, and no browser warning. On a private/reserved IP it falls back to a self-signed certificate. **We need to serve the application over HTTPS, since modern browsers only allow microphone permissions for websites being served over HTTPS**.
We highly recommend you set up the platform on a fresh server, so that there are less chances of confliciting dependencies, and ports from other applications.
### Prerequisites
* A server with Docker and Docker Compose installed. It should have minimum of 8 GB RAM and 4 vCPUs.
* Root (`sudo`) access on the server — the setup script must be run as root and exits early otherwise.
* Public IP address for your server. You can also access the server using a local IP address in your VPC as long as its reachable from your browser.
* TCP Ports 80, 443, 3478, 5349 and UDP Ports 3478, 5349 and 49152:49200 reachable from Internet (Port 80 and 443 to access the UI and rest of the ports for WebRTC Signaling)
IMPORTANT: Please double check the ports on your firewall. Please refer to your server hosting provider's documentaion on how you can open these ports in the firewall of the server.
### Quick Setup
Run the automated setup script that will configure everything for you:
```bash theme={null}
curl -o setup_remote.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_remote.sh && chmod +x setup_remote.sh && sudo ./setup_remote.sh
```
Run with `sudo` — the script must run as root (it provisions Docker, binds ports 80/443, and installs a Let's Encrypt certificate with a system renewal hook) and exits early if it isn't. On a public IP it obtains and auto-renews a trusted certificate; on a private IP it falls back to a self-signed one, since Let's Encrypt can't validate a private address. Optional environment-variable overrides: `CERT_MODE=self-signed` forces a self-signed cert, `ACME_DOMAIN_SUFFIX=nip.io` switches to the nip.io pool if sslip.io is rate-limited, and `LETSENCRYPT_EMAIL=you@example.com` sets the address for expiry notices.
The script will prompt you for:
* Your server's public IP address
* A password for the TURN server (optional, press Enter for default)
* Deployment mode — press Enter for **prebuilt** (pulls official images, the recommended default) or pick **build** to compile images from source. Use **build** when you maintain a fork or want to deploy local customizations — see [Building from source](#building-from-source) below.
* Number of FastAPI workers (uvicorn processes nginx will load-balance) — press Enter for the default of `4`, or pick a value that suits your server's CPU count. See [Scaling](/deployment/scaling) for sizing guidance and how to change this on a running stack.
It will automatically:
* Get the source — `docker-compose.yaml` only (prebuilt mode), or clone the full repo (build mode)
* Download the validated remote deployment helper bundle
* Obtain a free trusted Let's Encrypt certificate via sslip.io and configure auto-renewal (public IP), or generate a self-signed certificate (private IP / no root)
* For a public IP, also start the stack and print your ready-to-use `https://…sslip.io` URL
* Create an environment file with TURN server configuration
* Validate the runtime config that `dograh-init` will render from `.env`
* Write a `docker-compose.override.yaml` with build directives (build mode only)
### Start the Application
Please ensure that Docker Compose is installed on your machine before proceeding further. You can check whether its installed by running `docker compose version` command. If its not installed, please install it by following your server provider documentation.
After the setup script completes, start Dograh. For a **public-IP install the trusted-certificate flow already started the stack** and printed your `https://…sslip.io` URL — you can skip to [Access Your Application](#access-your-application). For a self-signed install, start it yourself (the script prints the exact command at the end — it differs slightly between modes):
```bash Prebuilt mode theme={null}
cd dograh
./remote_up.sh
```
```bash Build mode theme={null}
cd dograh
./remote_up.sh --build
```
First boot in build mode takes several minutes — Docker has to build both the API and UI images before the stack comes up.
### Access Your Application
Your application will be available at the URL the setup script printed at the end:
* **Public IP (trusted certificate):** `https://.sslip.io` — for example `https://203-0-113-10.sslip.io`. No browser warning.
* **Self-signed (private IP):** `https://YOUR_SERVER_IP`
With a self-signed certificate your browser shows a security warning you can safely accept. With the sslip.io Let's Encrypt certificate there is no warning.
You should be able to create and test a voice agent now.
### Configuration Notes
* The remote deployment includes an nginx reverse proxy for HTTPS termination
* File downloads (transcripts, recordings) are automatically routed through nginx
* WebSocket connections for real-time features are properly proxied
* Public-IP installs get a trusted Let's Encrypt certificate (via sslip.io) that renews automatically; private-IP installs use a self-signed certificate (browser warning)
* The TURN server (coturn) is configured for WebRTC NAT traversal
* For production deployments with proper SSL and domain names, see the [Custom Domain](custom-domain) documentation
### Files Created
The setup script creates the following files in the `dograh/` directory:
| File | Purpose |
| - | - |
| `docker-compose.yaml` | Main Docker Compose configuration |
| `docker-compose.override.yaml` | Build directives for `api` and `ui` (**build mode only**) |
| `remote_up.sh` | Validated startup wrapper for the remote stack |
| `scripts/run_dograh_init.sh` | One-shot init renderer/validator used by Docker Compose |
| `scripts/lib/setup_common.sh` | Shared deployment helper library |
| `deploy/templates/` | nginx and coturn runtime config templates |
| `generate_certificate.sh` | Script to regenerate SSL certificates |
| `certs/local.crt` | SSL certificate (Let's Encrypt via sslip.io, or self-signed) |
| `certs/local.key` | SSL private key |
| `.env` | Single source of truth for deployment settings (TURN secret, JWT secret, FastAPI worker count, public host/base URL) |
### Building from source
If you maintain a fork or want to ship local code changes without waiting for an official image release, pick **build** when the setup script prompts for deployment mode.
In this mode the script:
1. Clones the repo into `dograh/` (default: `dograh-hq/dograh@main`; the script will ask for an `owner/name` and `branch` so a fork can point at its own).
2. If you run the script from inside an existing dograh checkout, offers to use that checkout instead of cloning.
3. Generates a `docker-compose.override.yaml` next to `docker-compose.yaml` that swaps the `image:` directives for local `build:` directives. Docker Compose auto-loads the override file — you don't need to pass any `-f` flags.
#### Updating after a code change
Whenever the codebase changes — a `git pull`, a local edit, or a submodule bump — rebuild the `api` and `ui` images and recreate the running containers so they pick up the new code.
**TL;DR (rebuilds both services and restarts in one shot):**
```bash theme={null}
cd dograh
sudo docker compose --profile remote up -d --build
```
`--build` rebuilds any service whose `build:` context has changed (both `api` and `ui` are wired through the `docker-compose.override.yaml` written by `setup_remote.sh`), and `up -d` recreates the affected containers so they run off the new image.
**Step-by-step** — use this when you're pulling fresh code from a remote, or want to be explicit:
```bash theme={null}
cd dograh
# 1. Pull the latest source.
git pull
git submodule update --init --recursive # picks up pipecat submodule bumps
# 2. Rebuild both images.
sudo docker compose --profile remote build api ui
# 3. Recreate the running containers so they use the new images.
sudo docker compose --profile remote up -d
```
If you update `pipecat` submodule, you must do `git submodule update --init --recursive`, or the Docker build step will not pick up `pipecat` changes
**Rebuild a single service** — faster when you only changed one side:
```bash theme={null}
sudo docker compose --profile remote build api # or: build ui
sudo docker compose --profile remote up -d api # or: up -d ui
```
**Force a clean rebuild** — needed when a base image changed, a `pip install` was added, or you suspect a stale layer:
```bash theme={null}
sudo docker compose --profile remote build --no-cache api ui
sudo docker compose --profile remote up -d
```
**Verify the containers were actually recreated** — check the `CREATED` column shows a recent timestamp, not minutes/hours ago:
```bash theme={null}
sudo docker compose --profile remote ps
```
To revert to pulling official images, delete `docker-compose.override.yaml` and start the stack with `--pull always` as in the [prebuilt flow](#start-the-application).
Build mode is for **fork maintainers and self-hosters who want to deploy customized images** — not for active development on the code itself. For day-to-day contributor work (fast reload, IDE/LSP integration), use the [contributor setup](/contribution/setup) instead.
### Next Steps
Now that you are able to create and test a voice agent, you can setup a custom domain and setup SSL using letsencrypt. Checkout [Custom Domain](custom-domain) for instructions on how to do that.
# Heroku (Coming Soon)
Source: https://docs.dograh.com/deployment/heroku
One-click deployment of Dograh AI to Heroku cloud platform
Deploy Dograh AI to Heroku with a single click for instant cloud hosting.
## What is Heroku Deployment?
Heroku deployment provides a one-click solution to deploy Dograh AI to the cloud without managing servers or infrastructure. Perfect for:
* **Quick Testing**: Get a cloud instance running in minutes
* **Small Teams**: Cost-effective solution for lightweight usage
* **Proof of Concept**: Demonstrate Dograh AI to stakeholders quickly
* **No DevOps**: Deploy without server management knowledge
## Benefits
* **One-Click Setup**: Deploy instantly with minimal configuration
* **Automatic Scaling**: Heroku handles infrastructure scaling
* **Built-in Monitoring**: Access logs and performance metrics
* **Easy Sharing**: Get a public URL immediately
* **Cost-Effective**: Pay-as-you-use pricing model
## What You'll Get
Once available, the Heroku deployment will include:
* Pre-configured Dograh AI application
* PostgreSQL database setup
* Redis cache configuration
* Public HTTPS URL
* Environment variable management
## Coming Soon
One-click Heroku deployment is in development. This will include:
* **Deploy to Heroku** button in the README
* Automatic provisioning of required add-ons
* Environment variable configuration guide
* Scaling and monitoring instructions
For updates on Heroku deployment availability, please [join our Slack community](https://join.slack.com/t/dograh-community/shared_invite/zt-4anp45mlu-ukR3jTqwdDFMEpqeBhKn6g) or watch our GitHub repository for announcements.
# Deployments
Source: https://docs.dograh.com/deployment/introduction
Options for self-hosting Dograh — from a local Docker install to a production remote server with a custom domain.
Dograh is self-hosted with Docker. Everything ships as two images (`dograh-api` and `dograh-ui`) plus Postgres, Redis, and MinIO, orchestrated with Docker Compose. Pick the deployment path that matches what you're trying to do:
| Path | Use it when | Guide |
| - | - | - |
| **Local Docker** | You want to try Dograh on your own machine. No public URL, no telephony. | [Docker → Option 1](/deployment/docker#option-1-local-docker-deployment) |
| **Remote server** | You want a real server reachable from the internet, with HTTPS out of the box (via a free `sslip.io` certificate — no domain required). | [Docker → Option 2](/deployment/docker#option-2-remote-server-deployment) |
| **Custom domain** | You already have a remote deployment and want your own domain (e.g. `voice.yourcompany.com`) instead of the `sslip.io` URL. | [Custom Domain](/deployment/custom-domain) |
| **Heroku** | One-click PaaS deployment. | [Heroku](/deployment/heroku) — coming soon |
Browsers treat microphone access as a secure-context feature, so the app must be served over HTTPS — or accessed at localhost/127.0.0.1 over HTTP, which is also a secure context. Local Docker uses `http://localhost:3010` and therefore does not need a certificate.
## After you deploy
Once the platform is running, these guides cover keeping it running:
* [**Authentication**](/deployment/authentication) — switch from the built-in local email/password provider to Stack Auth for social login.
* [**Scaling**](/deployment/scaling) — run multiple FastAPI workers behind nginx for higher call throughput.
* [**Update**](/deployment/update) — move an existing deployment to a newer Dograh version.
## Where to start
1. **Kicking the tires?** Start with [local Docker](/deployment/docker#option-1-local-docker-deployment) — running in a few minutes, no server needed.
2. **Putting it in front of real callers?** Use [remote server deployment](/deployment/docker#option-2-remote-server-deployment), then layer on a [custom domain](/deployment/custom-domain) if you want one.
3. **Already running and need to upgrade?** Go straight to [Update](/deployment/update).
4. **Expecting high call volume?** Read [Scaling](/deployment/scaling) once your base deployment is live.
## Related
* [Contribution: Setup](/contribution/setup) — for developing on Dograh itself rather than deploying it
* [Environment Variables](/developer/environment-variables)
# Scaling
Source: https://docs.dograh.com/deployment/scaling
Run multiple FastAPI worker processes behind nginx for higher throughput
By default, the Dograh API container runs a single uvicorn worker. For production traffic — especially with many concurrent voice calls (long-lived WebSockets) — you'll want multiple workers. Dograh ships with built-in support for this: nginx load-balances across N independent uvicorn processes using a `least_conn` strategy.
This page covers how the multi-worker setup works, how to choose a worker count at install time, and how to change it on a running stack.
Multi-worker support requires **Dograh v1.29.0 or newer**. Earlier releases used `uvicorn --workers` and a different remote deployment layout. If your stack is older, [update first](/deployment/update) and then come back to this page.
## How it works
The API container starts `FASTAPI_WORKERS` separate uvicorn processes, each bound to its own port (`8000`, `8001`, `8002`, …). nginx exposes a single upstream `dograh_api` that includes all worker ports and routes new requests to whichever worker currently has the **fewest active connections**.
```
┌───────────────────────────────────┐
│ api container │
│ uvicorn worker 0 → :8000 │
browser ──► nginx ──► │ uvicorn worker 1 → :8001 │
(443) (least_conn) uvicorn worker 2 → :8002 │
│ uvicorn worker 3 → :8003 │
└───────────────────────────────────┘
```
This is intentionally **not** `uvicorn --workers N` (the built-in pre-fork mode). With pre-fork, the Linux kernel distributes new TCP connections across workers via `accept()` — fine for short HTTP requests, but long-lived WebSockets stick to whichever worker first accepted them. A handful of unlucky workers end up handling most of the streaming traffic while the others idle. Routing at the nginx layer with `least_conn` knows the actual per-worker connection count and distributes WebSockets evenly.
The `ari_manager` and `campaign_orchestrator` processes inside the API container stay as **singletons** regardless of `FASTAPI_WORKERS` — they coordinate global state (Asterisk channels, campaign scheduling) and should not be duplicated. ARQ background workers are controlled separately via `ARQ_WORKERS`.
## Choosing a worker count
A safe starting point is **one worker per available vCPU**, capped at 8 unless you've profiled your workload. The [Remote Server Deployment prerequisites](/deployment/docker#prerequisites) ask for a minimum of 4 vCPUs, so:
| vCPUs | Suggested `FASTAPI_WORKERS` |
| - | - |
| 4 | 4 |
| 8 | 6–8 |
| 16+ | profile first |
Each worker holds its own Python process and memory — budget roughly **300–500 MB RAM per worker** in addition to the postgres/redis/minio overhead. If you're near the 8 GB RAM minimum and see OOMs, drop the worker count before adding more.
## Setting the worker count at install time
`setup_remote.sh` prompts for the worker count alongside the other configuration:
```
Number of FastAPI workers (uvicorn processes nginx will load-balance):
[4]:
```
Press Enter for the default (`4`) or enter a different positive integer. Non-interactive callers (cloud-init, CI, Terraform) can set the value via environment variable instead:
```bash theme={null}
sudo SERVER_IP=... TURN_SECRET=... FASTAPI_WORKERS=8 ./setup_remote.sh
```
The script stores the value in **`.env`** (`FASTAPI_WORKERS=N`). The supported startup path (`./remote_up.sh`) preflights the `dograh-init` render from that value before every remote start, so nginx and the API worker count stay aligned.
## Changing the worker count on a running stack
Once Dograh is running, increasing or decreasing the worker count is a one-file edit plus a restart. Change `.env`, then start through `./remote_up.sh` so `dograh-init` regenerates nginx runtime config before Docker starts the stack.
### Steps
All commands run from your `dograh/` directory (the one with `docker-compose.yaml`).
**1. Edit `.env`** and change the `FASTAPI_WORKERS` line:
```bash theme={null}
# Before
FASTAPI_WORKERS=4
# After
FASTAPI_WORKERS=8
```
**2. Recreate the stack through the validated wrapper.** The simplest path — brief downtime, no surprises:
```bash theme={null}
./remote_up.sh
```
If you want to avoid downtime and your stack is healthy, you can recreate only the `api` and `nginx` containers:
```bash theme={null}
./remote_up.sh -- api nginx
```
`remote_up.sh` validates `.env`, runs the same `dograh-init` render that Compose will use at startup, runs `docker compose config -q`, and then starts the requested services.
**3. Verify.** Confirm the right number of uvicorn processes are running. The API image is slim and doesn't include `ps`, so use Docker's host-side view instead:
```bash theme={null}
sudo docker compose --profile remote top api | grep uvicorn
```
You should see one line per worker. To confirm the bound ports, check the startup logs — each worker logs an `Uvicorn running on http://0.0.0.0:800X` line on boot:
```bash theme={null}
sudo docker compose --profile remote logs api | grep "Uvicorn running"
```
Then hit the API through nginx to confirm requests still flow:
```bash theme={null}
curl -k https://YOUR_SERVER_IP/api/v1/health
```
### Why not just re-run `setup_remote.sh`?
`setup_remote.sh` refuses to overwrite an existing install by design — re-running it would regenerate `OSS_JWT_SECRET` (logging everyone out), reset the TURN shared secret (breaking WebRTC auth on connected clients), and regenerate SSL certificates. The two-file edit above is the supported way to change worker count after install.
If you genuinely want a clean reinstall, see the `DOGRAH_FORCE_OVERWRITE=1` escape hatch documented in the script.
## What this does not scale
Multi-worker mode scales the HTTP/WebSocket API surface. It does **not** scale:
* **ARQ background workers** — controlled by `ARQ_WORKERS` (defaults to 1). Increase this in the API container's environment if your background job queue backs up.
* **`ari_manager` / `campaign_orchestrator`** — singletons by design; they don't benefit from extra processes.
* **Postgres, Redis, MinIO** — each runs as a single container in the stack. For production-scale Postgres you'd run a managed service and point `DATABASE_URL` at it; the same applies to Redis and S3-compatible storage.
For multi-machine horizontal scaling (separate API containers across hosts), see the [Custom Domain](/deployment/custom-domain) guide for the load-balancer-in-front-of-multiple-hosts pattern — it's the same idea as the in-container `least_conn` upstream, just one layer higher.
# Update
Source: https://docs.dograh.com/deployment/update
Update your self-hosted Dograh stack to a newer image version
This guide covers updating a Dograh stack you've already deployed with [Docker](/deployment/docker) or a [custom domain](/deployment/custom-domain). You run commands from the same directory that contains your `docker-compose.yaml` (this is the `dograh/` directory if you used `setup_remote.sh`).
There are three update flows depending on how you deployed:
* **Remote, prebuilt mode** (most users) — use [`update_remote.sh`](#remote-prebuilt-mode-recommended) below.
* **Local Docker** — pull images and restart; see [Local deployment](#local-deployment).
* **Remote, build mode** (you have a `docker-compose.override.yaml`) — update via git; see [Updating a source build](#updating-a-source-build).
## Find an image version
Dograh publishes two images — `dograh-api` and `dograh-ui` — to both container registries:
* **GitHub Container Registry** — [github.com/orgs/dograh-hq/packages](https://github.com/orgs/dograh-hq/packages)
* **Docker Hub** — [hub.docker.com/u/dograhai](https://hub.docker.com/u/dograhai)
Each release is published under release tags. Note the formats differ between GitHub releases and Docker image tags — `update_remote.sh` understands both and normalizes for you.
| Where | Tag format | Example | When to use |
| - | - | - | - |
| GitHub release tag | `dograh-vX.Y.Z` | `dograh-v1.28.0` | Use when copying the version from a GitHub release |
| Docker image tag (semver) | `X.Y.Z` | `1.28.0` | Stable, recommended for production |
| Docker image tag (`latest`) | `latest` | `latest` | Tracks the most recent release tag |
Always update **`dograh-api`** and **`dograh-ui`** to the **same tag**. The two images are built from the same commit and the UI expects API responses in a matching shape — mixing versions will break the app. `update_remote.sh` handles this for you automatically.
## Remote, prebuilt mode (recommended)
`update_remote.sh` is the supported path for updating a stack created with `setup_remote.sh`. In one shot it:
* Asks for a target version (defaults to the latest release tag on GitHub).
* Pulls `docker-compose.yaml` at that version and pins both `api` and `ui` images to it.
* Refreshes the remote helper bundle (`remote_up.sh` plus shared templates/helpers).
* Synchronizes the canonical remote keys in `.env` and validates the runtime config that `dograh-init` will render from it.
* Backs up every file it changes with a `.bak.` suffix.
From your install directory:
```bash theme={null}
cd dograh
curl -o update_remote.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/update_remote.sh
bash update_remote.sh
```
You'll be prompted for the target version, defaulting to the most recent release. Accepted forms: bare semver (`1.28.0`), v-prefixed (`v1.28.0`), the full GitHub tag (`dograh-v1.28.0`), or `main` to refresh deployment files from the default branch — the script normalizes them. Non-interactive callers can set it via environment variable and skip the confirmation prompt:
```bash theme={null}
TARGET_VERSION=1.28.0 DOGRAH_UPDATE_YES=1 bash update_remote.sh
```
After the script finishes, apply the update through the validated startup wrapper:
```bash theme={null}
./remote_up.sh
```
The script overwrites `docker-compose.yaml` and the remote helper bundle (`remote_up.sh`, `scripts/run_dograh_init.sh`, `scripts/lib/setup_common.sh`, and `deploy/templates/*`) from the shared upstream deployment bundle. If you've made local edits to any of these, check the `.bak.` files after the update and re-apply your edits.
## Local deployment
For local Docker installs (the [Quick Start](/deployment/docker#quick-start) flow or `setup_local.sh` / `setup_local.ps1`), refresh `docker-compose.yaml` and the startup script, stop the stack, then run the startup script. The script preserves existing `.env` secrets, creates `REDIS_PASSWORD` and MinIO credentials if they are missing, and only creates `POSTGRES_PASSWORD` for brand-new `.env` files. If a retained local Postgres volume already exists, it syncs the database user's password to `.env` before starting the full stack:
```bash macOS/Linux theme={null}
curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml
curl -o start_docker.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.sh && chmod +x start_docker.sh
docker compose down
./start_docker.sh
```
```powershell Windows theme={null}
Invoke-WebRequest -OutFile docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml; Invoke-WebRequest -OutFile start_docker.ps1 https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.ps1; docker compose down; .\start_docker.ps1
```
To pin a specific version instead of `latest`, edit `docker-compose.yaml` and change both `image:` lines for `api` and `ui` to the same tag (e.g. `:1.28.0` — Docker image tags use bare semver, no `v` prefix), then run the commands above.
## Verify the update
Check the running image tags:
```bash theme={null}
docker compose ps --format "table {{.Service}}\t{{.Image}}"
```
You should see the API and UI both running the tag you pinned.
Hit the health endpoint to confirm the API is responding:
```bash theme={null}
curl -k https://YOUR_SERVER_IP/api/v1/health # remote
curl http://localhost:8000/api/v1/health # local
```
## Roll back
`update_remote.sh` saves backups of every file it touched. To roll back, restore them and recreate the stack — the exact commands (including the timestamp it used) are printed at the end of the script's output. The generic form:
```bash theme={null}
cd dograh
for f in docker-compose.yaml nginx.conf turnserver.conf .env remote_up.sh scripts/run_dograh_init.sh scripts/lib/setup_common.sh deploy/templates/nginx.remote.conf.template deploy/templates/turnserver.remote.conf.template; do
[[ -f "$f.bak." ]] && cp "$f.bak." "$f"
done
./remote_up.sh
```
Your Postgres data volume persists across `down`/`up` cycles, so agents and call history are preserved.
Rolling back across a database migration is not always safe — if the newer release ran a schema migration, downgrading may leave the DB in a state the older API doesn't understand. If in doubt, [open an issue](https://github.com/dograh-hq/dograh/issues) before rolling back.
## Updating a source build
If you deployed in **build mode** (you'll have a `docker-compose.override.yaml` in your install directory), `update_remote.sh` deliberately refuses to run — you already have the full repo locally and update via git:
```bash theme={null}
cd dograh
git fetch
# Track latest main:
git pull
# Or pin to a specific release (git tag format is dograh-vX.Y.Z):
git checkout dograh-v1.28.0
# Pick up pipecat and other submodule bumps
git submodule update --init --recursive
# Rebuild and restart
sudo docker compose --profile remote build
sudo docker compose --profile remote up -d
```
If you update the `pipecat` submodule, you **must** run `git submodule update --init --recursive` before rebuilding, or the Docker build will not pick up `pipecat` changes.
If you maintain a fork with local customizations on top of upstream, merging conflicts in `docker-compose.yaml`, `remote_up.sh`, `scripts/run_dograh_init.sh`, `deploy/templates/*`, or `setup_remote.sh` is up to you — resolve them as you would any other git merge. Leave `OSS_JWT_SECRET`, `TURN_SECRET`, `POSTGRES_PASSWORD`, `REDIS_PASSWORD`, `MINIO_ROOT_USER`, and `MINIO_ROOT_PASSWORD` in `.env` unchanged across updates to preserve sessions, WebRTC auth, and service credentials.
The same migration warning above applies: rolling back across a schema change can leave the DB in a state the older API can't read.
# Call Dispositions
Source: https://docs.dograh.com/developer/call-dispositions
Configure workflow-specific business outcomes and use them consistently across Dograh
A call disposition is a stable code that describes the business outcome of a
completed conversation, such as `qualified`, `appointment_booked`, or
`callback_requested`. It is different from the call status: `user_hangup`
explains how a call ended, while `appointment_booked` explains what the call
achieved.
Call disposition extraction is disabled by default for every workflow. Until
you enable it, Dograh keeps the disposition recorded by the event that ended
the call.
## Before you begin
Before configuring a workflow, decide which outcomes matter to your business
and write an observable criterion for each one. You also need permission to
edit and publish the workflow.
## Configure outcomes for a workflow
1. Open the agent whose workflow you want to configure.
2. Open **Settings**, then select **General**.
3. Turn on **Extract call disposition at the end of the call**.
4. In the **Call disposition extraction** dialog, review the starter outcomes.
5. Edit or remove any outcome that does not fit this workflow.
6. Select **Add custom disposition** to add your own code and description.
7. Select **Enable extraction**.
8. Select **Save General Settings**, then publish the agent for the change to
affect live calls.
The dialog starts with these suggestions. They do not become active until you
enable extraction and save the workflow.
| Code | Selection criteria |
| - | - |
| `qualified` | The call achieved the workflow's primary goal. |
| `not_interested` | The person clearly declined the offer or said they are not interested. |
| `wrong_number` | The call reached the wrong person or an incorrect phone number. |
| `voicemail_detected` | The call reached voicemail or an answering machine instead of a person. |
| `do_not_call` | The person explicitly asked not to be contacted again. |
| `callback_requested` | The person asked to be contacted again at a later time. |
To change an enabled workflow later, select **Configure options**, update the
list, save the general settings, and publish again. Turning extraction off
removes the configured options from the workflow after you save and publish.
### Define effective custom outcomes
The code is the value Dograh stores for the run. Keep it stable and
machine-readable. The description tells the classifier when it should select
that code.
For example:
| Code | Description |
| - | - |
| `appointment_booked` | Use when the customer agrees to a specific appointment date and time. |
| `follow_up_required` | Use when the customer is interested but no appointment or next step is confirmed. |
Use mutually exclusive descriptions based on evidence in the conversation.
Avoid vague descriptions such as "the call went well," because two outcomes
may appear equally valid.
Each workflow can have up to 50 outcomes. A code must start with a letter, may
contain letters, numbers, underscores, and hyphens, and can be at most 64
characters. Codes are unique regardless of letter case. Each description can
be at most 1,000 characters, with a combined limit of 4,000 characters across
the workflow.
## How extraction works
At the end of an eligible call, Dograh analyzes the complete conversation and
selects exactly one of the configured codes. It cannot invent a code that is
not in the workflow configuration.
If the conversation does not support any configured outcome, contains no user
speech, or classification fails, Dograh keeps the disposition already recorded
for the call. A disposition explicitly set by an end-call or transfer tool is
also preserved rather than overwritten by final classification.
This classification is separate from variable extraction on individual agent
nodes. Node variables describe information gathered at one point in the
workflow; the call disposition describes the final outcome of the conversation
as a whole.
## Understand the stored fields
Dograh stores the termination mechanism, raw business outcome, and optional
organization-specific code separately:
| Field | Meaning | Example |
| - | - | - |
| `gathered_context.call_status` | Observed reason the call ended; never inferred | `user_hangup` |
| `gathered_context.call_disposition` | Raw outcome recorded by the workflow before organization mapping | `callback_requested` |
| `gathered_context.mapped_call_disposition` | Organization-specific code after disposition mapping | `follow_up` |
When no custom outcome is recorded, `call_disposition` falls back to the call's
termination reason. When disposition mapping is disabled or no mapping matches,
`mapped_call_disposition` keeps the raw value.
## Use dispositions in a webhook
Reference the raw, mapped, and status values independently in a webhook payload
template:
```json theme={null}
{
"call_status": "{{gathered_context.call_status}}",
"call_disposition": "{{gathered_context.call_disposition}}",
"mapped_call_disposition": "{{gathered_context.mapped_call_disposition}}"
}
```
Dograh automatically adds a top-level `call_disposition` containing the raw
value when your webhook template does not define that key. To send your
organization-specific code instead, reference
`{{gathered_context.mapped_call_disposition}}` explicitly. See
[Webhook Payloads](./webhooks) for the complete payload context.
## Map outcomes to your organization's codes
Disposition mapping is optional and applies across your organization. Use it
to normalize raw outcomes from different workflows into a shared vocabulary
for filtering and reporting.
1. Open **Platform Settings**.
2. Under **Preferences**, turn on **Disposition mapping**.
3. Select **Configure mapping**.
4. Map each raw Dograh or workflow disposition to your preferred organization
code.
5. Select **Add disposition** if a custom workflow code is not already listed.
6. Save the mapping.
For example, you can map `callback_requested` to `follow_up`. Dograh preserves
`call_disposition` as `callback_requested` and writes `follow_up` to
`mapped_call_disposition`. Run filters and reports use the mapped value. In a
webhook, you can choose either the raw or mapped field explicitly. Mapping
changes apply to subsequent calls; they do not rewrite completed runs.
## Configure dispositions through the API
The workflow stores its options in
`workflow_configurations.call_dispositions`:
```json theme={null}
{
"call_dispositions": [
{
"code": "appointment_booked",
"description": "Use when the customer agrees to a specific appointment date and time."
},
{
"code": "callback_requested",
"description": "Use when the customer asks to be contacted again later."
}
]
}
```
The example above shows the `workflow_configurations` value, not the complete
request body. When using the [Update Agent API](../api-reference/agents/update),
read the agent's current configuration first and preserve unrelated
`workflow_configurations` fields in the update. Send an empty
`call_dispositions` array to disable final classification.
# Environment Variables
Source: https://docs.dograh.com/developer/environment-variables
Complete reference for all environment variables used by the Dograh backend
Core environment variables are centralized in `api/constants.py`. Variables marked **Required** in the description must be explicitly set — the application will either fail to boot or behave insecurely without them.
## Deployment Modes
Dograh supports two deployment modes, set via `DEPLOYMENT_MODE`:
* **OSS**: The default mode. Designed for self-hosted deployments using [Docker Compose](/deployment/docker) — the fastest way to get Dograh running. Uses local JWT authentication and MinIO for storage.
* **SaaS**: Intended for customised deployments outside of Docker. Authentication and API key management are handled through Dograh Managed Platform Services (MPS), allowing greater flexibility in how the platform is hosted and integrated.
The relevant required variables for each mode are noted in the descriptions below.
***
## Application
| Variable | Default | Description |
| - | - | - |
| `ENVIRONMENT` | `local` | Runtime environment. Affects logging and behaviour. One of `local`, `production`, `test` |
| `DEPLOYMENT_MODE` | `oss` | Deployment mode. Use `oss` for self-hosted |
| `AUTH_PROVIDER` | `local` | Authentication provider. `local` (default) uses the built-in email/password flow. Set to `stack` to delegate to Stack Auth for social login — see [Authentication](/deployment/authentication) for the full setup |
***
## Database
| Variable | Default | Description |
| - | - | - |
| `DATABASE_URL` | N/A | **Required.** PostgreSQL connection string. e.g. `postgresql+asyncpg://user:pass@host:5432/dbname` |
| `REDIS_URL` | N/A | **Required.** Redis connection string. e.g. `redis://localhost:6379` |
***
## Authentication (OSS)
| Variable | Default | Description |
| - | - | - |
| `OSS_JWT_SECRET` | N/A | **Required for OSS deployments.** Secret used to sign JWT tokens. Must be set to a strong random value in production |
| `OSS_JWT_EXPIRY_HOURS` | `720` | JWT token lifetime in hours (default: 30 days) |
| `ENABLE_SIGNUP` | `true` | Set to `false` to disable public signup on invite-only installs — `POST /api/v1/auth/signup` returns 403 and the login page hides the Sign up link |
Never use the placeholder `OSS_JWT_SECRET` in a production deployment. Generate a strong random secret and store it securely.
***
## Authentication (Stack Auth)
Set these when `AUTH_PROVIDER=stack` to delegate sign-in to [Stack Auth](https://stack-auth.com) for social login. The project id and publishable client key are public and are served to the browser at runtime via `/api/v1/health`; the secret server key stays server-side. See [Authentication](/deployment/authentication) for the full walkthrough.
| Variable | Default | Description |
| - | - | - |
| `STACK_AUTH_PROJECT_ID` | `null` | **Required for `stack`.** Stack project ID (public) |
| `STACK_PUBLISHABLE_CLIENT_KEY` | `null` | **Required for `stack`.** Stack publishable client key (public) |
| `STACK_SECRET_SERVER_KEY` | `null` | **Required for `stack`.** Stack secret server key — server-side only, also set on the `ui` service. Keep secret |
| `STACK_AUTH_API_URL` | `null` | **Required for `stack`.** Stack REST API base URL (e.g. `https://api.stack-auth.com`) |
***
## URLs
| Variable | Default | Description |
| - | - | - |
| `PUBLIC_BASE_URL` | `null` | Canonical public origin for the deployment (scheme + host, e.g. `https://203-0-113-10.sslip.io`). For a standard single-host install this is the only endpoint value you set — `BACKEND_API_ENDPOINT` and `MINIO_PUBLIC_ENDPOINT` derive from it |
| `PUBLIC_HOST` | `null` | Public host without scheme (e.g. `203-0-113-10.sslip.io`); `TURN_HOST` derives from it |
| `BACKEND_API_ENDPOINT` | `PUBLIC_BASE_URL`, else `http://localhost:8000` | Public URL the backend builds webhook / callback / embed links from. Set explicitly only to override the value derived from `PUBLIC_BASE_URL` |
| `UI_APP_URL` | `http://localhost:3010` | URL of the frontend application |
| `MPS_API_URL` | `https://services.dograh.com` | Dograh Managed Platform Services URL |
| `DOGRAH_MPS_SECRET_KEY` | `null` | **Required for non-OSS deployments.** Secret key for authenticating with MPS |
| `CORS_ALLOWED_ORIGINS` | `null` | **Required for non-OSS deployments.** Comma-separated list of origins allowed to make credentialed cross-origin requests (e.g. `https://app.example.com,https://admin.example.com`). Ignored in OSS mode, which serves a permissive same-origin policy without credentials |
***
## Storage
Dograh uses **MinIO by default**, which is bundled with the self-hosted deployment and requires no external setup. Set `ENABLE_AWS_S3=true` to switch to AWS S3 — typically used for cloud or managed deployments where S3 is already part of the infrastructure.
### MinIO (OSS default)
| Variable | Default | Description |
| - | - | - |
| `MINIO_ENDPOINT` | `localhost:9000` | MinIO server host and port |
| `MINIO_PUBLIC_ENDPOINT` | `PUBLIC_BASE_URL`, else `http://localhost:9000` | Publicly accessible MinIO URL for download links. Derives from `PUBLIC_BASE_URL`; set explicitly only for a separate object-storage origin |
| `MINIO_ACCESS_KEY` | N/A | **Required for OSS deployments.** MinIO access key. Must be set to a secure value in production |
| `MINIO_SECRET_KEY` | N/A | **Required for OSS deployments.** MinIO secret key. Must be set to a secure value in production |
| `MINIO_BUCKET` | `voice-audio` | Bucket name for audio files |
| `MINIO_SECURE` | `false` | Use HTTPS for MinIO connections |
### AWS S3 (alternative)
| Variable | Default | Description |
| - | - | - |
| `ENABLE_AWS_S3` | `false` | Set to `true` to use AWS S3 instead of MinIO |
| `S3_BUCKET` | `null` | S3 bucket name |
| `S3_REGION` | `us-east-1` | AWS region |
| `S3_ENDPOINT_URL` | `null` | Custom S3 endpoint for S3-compatible servers (e.g. `https://s3.example.com`). Leave unset for AWS. |
| `S3_SIGNATURE_VERSION` | `null` | Signing version. Unset uses botocore's default; set `s3v4` for servers that require SigV4. |
| `S3_ADDRESSING_STYLE` | `null` | `auto` (default), `path`, or `virtual`. Many S3-compatible servers and TLS setups require `path`. |
Credentials come from the standard `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` environment variables.
#### S3-compatible servers (MinIO, rustfs, Ceph, ...)
The S3 backend can target any S3-compatible server, not just AWS. Prefer it over the MinIO backend when you need **presigned URLs against a private bucket**: the MinIO backend returns plain unsigned object URLs and relies on the bucket being anonymously public-readable, whereas the S3 backend issues real presigned URLs so the bucket can stay private.
To use it, set `ENABLE_AWS_S3=true` and point it at your server with the `S3_*` overrides above. For example, against [rustfs](https://github.com/rustfs/rustfs):
```bash theme={null}
ENABLE_AWS_S3=true
S3_BUCKET=voice-audio
S3_REGION=us-east-1
S3_ENDPOINT_URL=https://s3.example.com
S3_SIGNATURE_VERSION=s3v4 # rustfs rejects SigV2 with SignatureDoesNotMatch
S3_ADDRESSING_STYLE=path # rustfs and most non-AWS TLS certs require path-style
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
```
Presigned URLs point at `S3_ENDPOINT_URL`, so that host must be reachable from the browser. Because browsers fetch transcripts cross-origin, the bucket also needs a CORS rule allowing your app's origin for `GET`/`HEAD` — configure this on the storage server (e.g. via `PutBucketCors`), not in Dograh.
***
## WebRTC
| Variable | Default | Description |
| - | - | - |
| `ENABLE_COTURN` | `false` | Whether this deployment runs a TURN server. Reported to browsers as `turn_enabled` on `/api/v1/health` and in the embed widget config; when `false` clients skip the TURN credential request and connect over STUN, and the server returns 503 from the credential endpoints. Set to `true` wherever coturn is running — the setup scripts and Helm chart do this for you |
| `TURN_HOST` | `PUBLIC_HOST`, else `localhost` | TURN server hostname for WebRTC NAT traversal. Derives from `PUBLIC_HOST`; set explicitly only when TURN runs on a separate host |
| `TURN_PORT` | `3478` | TURN server port |
| `TURN_TLS_PORT` | `5349` | TURN server TLS port |
| `TURN_SECRET` | `null` | **Required for WebRTC.** Shared secret for TURN credential generation |
| `TURN_CREDENTIAL_TTL` | `86400` | TURN credential validity in seconds (default: 24h) |
| `FORCE_TURN_RELAY` | `false` | Diagnostic flag. When `true`, restricts ICE to relay-only candidates on both server (SDP filter) and browser (`iceTransportPolicy: 'relay'`). Use to verify TURN connectivity end-to-end — calls fail cleanly if TURN is misconfigured instead of silently falling back to a direct path. |
***
## Tracing (Langfuse)
| Variable | Default | Description |
| - | - | - |
| `LANGFUSE_HOST` | `null` | Langfuse server URL |
| `LANGFUSE_PUBLIC_KEY` | `null` | Langfuse public key |
| `LANGFUSE_SECRET_KEY` | `null` | Langfuse secret key |
| `LANGFUSE_PROJECT_ID` | `null` | Langfuse project ID, required with environment credentials to generate trace URLs |
| `LANGFUSE_TRACES_PUBLIC` | `false` | When `true`, traces exported to the project configured *here* are marked public — anyone holding a trace URL can read it without logging in. Organizations that connected their own Langfuse project in the UI are unaffected; their visibility follows the per-organization toggle in **Platform Settings**, which is off by default. |
Tracing activates automatically as soon as credentials are available — either via these environment variables (applied to all organizations) or per-organization in the UI under **Platform Settings**. Environment credentials require `LANGFUSE_HOST`, `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, and `LANGFUSE_PROJECT_ID`. If neither is set, spans are dropped silently. See the [Tracing guide](/configurations/tracing) for setup instructions.
***
## Telephony
Carriers dial the media WebSocket back at `/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}`. Those ids travel in a caller-visible URL, so on their own they are a guessable capability. Setting a secret makes Dograh sign that URL with an HMAC and verify the signature when the socket opens.
The signature travels as a trailing path segment — `/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}/{token}` — because carriers do not reliably forward query strings. Twilio [documents that its `` url "does not support query string parameters"](https://www.twilio.com/docs/voice/twiml/stream) and drops them outright. Asterisk ARI is the exception: it connects to `/api/v1/telephony/ws/ari` and passes `token` as a query parameter alongside its other routing values, which works because Asterisk builds that URL itself.
| Variable | Default | Description |
| - | - | - |
| `TELEPHONY_WS_TOKEN_SECRET` | `null` | Secret used to sign the media WebSocket URL **and** the Telnyx call-events webhook URL. Unset leaves both URLs unchanged and both checks disabled |
| `TELEPHONY_WS_TOKEN_ENFORCE` | `false` | Reject connections whose token is missing or invalid (WebSocket close `4401`) |
Roll it out in two steps so no call is dropped:
1. Set `TELEPHONY_WS_TOKEN_SECRET` on **every** process that places or receives calls — the `api` service and, if you run Asterisk, the `ari-manager`. They must share the same value: one mints the token, the other verifies it. Calls keep connecting either way; anything unverifiable is logged as `UNVERIFIED media socket`.
2. Once those warnings stop, set `TELEPHONY_WS_TOKEN_ENFORCE=true` to start rejecting them.
Setting `TELEPHONY_WS_TOKEN_ENFORCE` without a secret does nothing at all — the check is skipped entirely rather than rejecting every connection.
The same secret also signs the Telnyx call-events webhook URL (`/api/v1/telephony/telnyx/events/{workflow_run_id}?token=…`), which the events route verifies before any database lookup. Unlike the media socket, that token rides in a query string: Telnyx preserves query strings on webhook POSTs. It is a bearer credential with no expiry and appears in full in access logs — the same caveat below applies.
The signed URL is a bearer credential, and it is not hidden: the token appears in full in the `api` and nginx access logs, and in carrier-side request logs (Twilio's debugger, Telnyx's request inspector). Anyone who can read those logs can open the media socket for a run that is still in `initialized`. Treat log access as socket access.
***
## Monitoring
| Variable | Default | Description |
| - | - | - |
| `SENTRY_DSN` | `null` | Sentry DSN for error tracking |
| `ENABLE_TELEMETRY` | `false` | Enable anonymous telemetry collection |
***
## Logging
| Variable | Default | Description |
| - | - | - |
| `LOG_LEVEL` | `DEBUG` | Log level: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
| `LOG_FILE_PATH` | `null` | Write logs to this file path (in addition to stdout) |
| `LOG_ROTATION_SIZE` | `100 MB` | Rotate log file when it reaches this size |
| `LOG_RETENTION` | `7 days` | How long to keep rotated log files |
| `LOG_COMPRESSION` | `gz` | Compression format for rotated logs |
| `SERIALIZE_LOG_OUTPUT` | `false` | Output logs as JSON (useful for log aggregation) |
***
## Campaigns
Controls concurrency for [Campaigns](/core-concepts/campaigns), Dograh's bulk outbound calling feature.
| Variable | Default | Description |
| - | - | - |
| `DEFAULT_ORG_CONCURRENCY_LIMIT` | `10` | Maximum concurrent active calls per organization (values below 1 are clamped to 1) |
***
## Further Customisation
The variables documented here cover the standard configuration surface. For advanced customisation — such as integrating additional services or tuning internal behaviour — consult the relevant module alongside `api/constants.py` to understand how each variable is consumed.
# Webhook Payloads
Source: https://docs.dograh.com/developer/webhooks
Context variables available in webhook nodes and the data Dograh sends after a call
Dograh executes **webhook nodes** asynchronously after a workflow run completes. You configure the target URL, HTTP method, headers, and payload template directly in the workflow definition.
***
## How webhooks work
1. A call completes (or a run finishes)
2. Dograh executes any `webhook` nodes in the workflow asynchronously
3. The payload template is rendered with the run's context and sent as a JSON `POST` (or your configured method) to your endpoint
4. Non-200 responses are logged but do not block or retry by default (configure `retry_config` to change this)
***
## Payload context variables
The following variables are available in your `payload_template` using double-brace syntax (e.g. `{{workflow_run_id}}`):
| Variable | Type | Description |
| - | - | - |
| `workflow_run_id` | integer | ID of the completed run |
| `workflow_run_name` | string | Name of the run |
| `workflow_id` | integer | ID of the workflow |
| `workflow_name` | string | Name of the workflow |
| `campaign_id` | integer \| null | ID of the campaign this run belongs to (null for ad-hoc runs) |
| `call_time` | string | ISO-8601 UTC timestamp of when the run was created |
| `initial_context` | object | Context passed when the call was initiated |
| `gathered_context` | object | Data extracted during the call by agent nodes |
| `gathered_context.call_status` | string | Observed reason the call ended |
| `gathered_context.call_disposition` | string | Raw final outcome before organization mapping |
| `gathered_context.mapped_call_disposition` | string | Final outcome after organization mapping |
| `cost_info` | object | Call cost breakdown |
| `cost_info.call_duration_seconds` | number | Call duration in seconds |
| `annotations` | object | QA analysis results (if a `qa` node is configured) |
| `recording_url` | string \| null | Public download URL for the call recording |
| `transcript_url` | string \| null | Public download URL for the call transcript |
### Call disposition
Use `{{gathered_context.call_disposition}}` for the raw workflow outcome or
`{{gathered_context.mapped_call_disposition}}` for your organization's mapped
code. `{{gathered_context.call_status}}` remains the observed reason the call
ended. See [Call Dispositions](./call-dispositions) to configure custom business
outcomes for each workflow and understand the fallback behavior.
Dograh always includes a top-level `call_disposition` field in the delivered
payload. If your template doesn't set one, it is added automatically from the
raw `gathered_context.call_disposition`; if your template sets it explicitly,
your value is kept.
### Example payload template
```json theme={null}
{
"run_id": "{{workflow_run_id}}",
"customer": "{{initial_context.customer_name}}",
"outcome": "{{gathered_context.resolution}}",
"call_status": "{{gathered_context.call_status}}",
"disposition": "{{gathered_context.call_disposition}}",
"mapped_disposition": "{{gathered_context.mapped_call_disposition}}",
"duration": "{{cost_info.call_duration_seconds}}",
"recording": "{{recording_url}}"
}
```
***
## Authentication
Webhook requests support the following authentication methods, configured via a stored credential:
| Type | Description |
| - | - |
| `NONE` | No authentication |
| `API_KEY` | Sends the key in a custom header (e.g. `X-API-Key`) |
| `BEARER_TOKEN` | Sends `Authorization: Bearer ` |
| `BASIC_AUTH` | HTTP Basic authentication (username + password) |
| `CUSTOM_HEADER` | Any custom header key-value pair |
***
## Receiving webhooks
Your endpoint should:
* Accept `POST` requests with `Content-Type: application/json`
* Respond with a `2xx` status code promptly (within 30 seconds)
* Handle duplicate deliveries idempotently (retries may deliver the same payload more than once)
### Minimal example receiver (Python)
```python theme={null}
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhook/dograh")
async def handle_dograh_webhook(request: Request):
payload = await request.json()
run_id = payload.get("run_id")
outcome = payload.get("outcome")
# process the call result...
return {"status": "ok"}
```
***
## Webhook node in a workflow definition
See [Workflow Definition Schema](/developer/workflow-schema#webhook-node) for the full configuration reference.
```json theme={null}
{
"id": "webhook-1",
"type": "webhook",
"position": { "x": 600, "y": 0 },
"data": {
"name": "Notify CRM",
"enabled": true,
"http_method": "POST",
"endpoint_url": "https://your-service.com/webhook/dograh",
"payload_template": {
"run_id": "{{workflow_run_id}}",
"customer": "{{initial_context.customer_name}}",
"outcome": "{{gathered_context.resolution}}"
}
}
}
```
# Workflow Definition Schema
Source: https://docs.dograh.com/developer/workflow-schema
Schema reference for the workflow_definition object used in the Agents API
The `workflow_definition` object passed to [Create from Definition](/api-reference/agents/create-from-definition) and [Update Agent](/api-reference/agents/update) defines the full conversation graph. It is the same structure the dashboard's visual workflow builder reads and writes — building an agent in the UI produces a `workflow_definition` under the hood, and anything you can configure visually can equally be expressed here as JSON.
```json theme={null}
{
"nodes": [...],
"edges": [...]
}
```
***
## Nodes
Each node represents a step in the conversation.
```json theme={null}
{
"id": "uuid-string",
"type": "agentNode",
"position": { "x": 100, "y": 200 },
"data": { ... }
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique node ID (UUID recommended) |
| `type` | string | One of the node types below |
| `position` | object | Visual coordinates in the workflow builder |
| `data` | object | Node configuration — fields vary by type |
### Node types
| Type | Description |
| - | - |
| `startCall` | Entry point for telephony calls |
| `endCall` | Terminates the call |
| `agentNode` | LLM-powered conversation step |
| `globalNode` | Global configuration applied across all agent nodes |
| `trigger` | Entry point for API-triggered (non-telephony) runs |
| `webhook` | Sends an HTTP request when reached |
| `qa` | Runs quality analysis on the completed call |
***
## Node data fields
### Common fields (all node types)
| Field | Type | Default | Description |
| - | - | - | - |
| `name` | string | required | Display name for the node |
| `prompt` | string | required\* | LLM system prompt. \*Not required for `trigger`, `webhook`, `qa` nodes |
| `allow_interrupt` | boolean | `false` | Allow the caller to interrupt the agent mid-speech |
| `wait_for_user_response` | boolean | `false` | Pause and wait for caller input before continuing |
| `wait_for_user_response_timeout` | number | `null` | Seconds to wait for input before timing out |
| `detect_voicemail` | boolean | `false` | Detect and handle voicemail on outbound calls |
| `delayed_start` | boolean | `false` | Delay execution of this node |
| `delayed_start_duration` | number | `null` | Delay in seconds |
| `add_global_prompt` | boolean | `true` | Merge the `globalNode` prompt into this node's prompt |
### agentNode — data extraction
| Field | Type | Default | Description |
| - | - | - | - |
| `extraction_enabled` | boolean | `false` | Extract structured data from the conversation |
| `extraction_prompt` | string | `null` | Custom prompt to guide extraction |
| `extraction_variables` | array | `[]` | Variables to extract (see below) |
**Extraction variable schema:**
```json theme={null}
{
"name": "customer_intent",
"type": "string",
"prompt": "What did the customer want to achieve?"
}
```
`type` is one of `string`, `number`, or `boolean`.
### agentNode — tools
| Field | Type | Description |
| - | - | - |
| `tool_uuids` | string\[] | IDs of tools (HTTP API, call transfer, etc.) to attach to this node |
| `document_uuids` | string\[] | IDs of knowledge base documents available to this node |
### trigger node
| Field | Type | Description |
| - | - | - |
| `trigger_path` | string | Unique UUID that becomes the API trigger endpoint path |
### webhook node
| Field | Type | Default | Description |
| - | - | - | - |
| `enabled` | boolean | `true` | Whether this webhook fires when reached |
| `http_method` | string | — | `GET`, `POST`, `PUT`, `PATCH`, or `DELETE` |
| `endpoint_url` | string | — | Target URL |
| `credential_uuid` | string | `null` | UUID of a stored auth credential |
| `custom_headers` | array | `[]` | Additional request headers `[{"key": "...", "value": "..."}]` |
| `payload_template` | object | `null` | Request body template (supports context variables) |
### qa node
| Field | Type | Default | Description |
| - | - | - | - |
| `qa_enabled` | boolean | `true` | Enable QA analysis |
| `qa_system_prompt` | string | `null` | Custom evaluation prompt |
| `qa_model` | string | `null` | LLM model to use for evaluation |
| `qa_min_call_duration` | integer | `15` | Minimum call duration in seconds to run QA |
| `qa_voicemail_calls` | boolean | `false` | Include voicemail calls in QA |
| `qa_sample_rate` | integer | `100` | Percentage of calls to analyse (1–100) |
***
## Edges
Each edge connects two nodes and defines when the transition fires.
```json theme={null}
{
"id": "edge-uuid",
"source": "node-uuid-a",
"target": "node-uuid-b",
"data": {
"label": "Customer confirms",
"condition": "The customer has confirmed their appointment",
"transition_speech": "Great, I've got that noted."
}
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique edge ID |
| `source` | string | ID of the originating node |
| `target` | string | ID of the destination node |
| `data.label` | string | Short label shown in the workflow builder |
| `data.condition` | string | Natural language condition the LLM evaluates to trigger this edge |
| `data.transition_speech` | string | Optional speech the agent says before transitioning |
***
## Validation rules
* All `source` and `target` IDs in edges must reference existing node IDs
* All nodes except `trigger`, `webhook`, and `qa` must have a non-empty `prompt`
* Node IDs must be unique within the workflow
* Each workflow must have exactly one `startCall` or `trigger` node as the entry point
***
## Minimal example
```json theme={null}
{
"nodes": [
{
"id": "start-1",
"type": "startCall",
"position": { "x": 0, "y": 0 },
"data": {
"name": "Start",
"prompt": "You are a friendly assistant. Greet the caller and ask how you can help."
}
},
{
"id": "end-1",
"type": "endCall",
"position": { "x": 400, "y": 0 },
"data": {
"name": "End",
"prompt": "Thank the caller and say goodbye."
}
}
],
"edges": [
{
"id": "edge-1",
"source": "start-1",
"target": "end-1",
"data": {
"label": "Done",
"condition": "The caller's question has been answered and they want to end the call"
}
}
]
}
```
# Use External Tools, Knowledge
Source: https://docs.dograh.com/getting-started/add-tools-and-knowledge-base
Make your agent do something during the call, not just talk. Connect it to live data and your company documents.
You've made the agent reachable through real phone calls. Now let's make it useful during the call.
Your agent can hold a conversation. But can it check an order, or answer a policy question correctly? That's what this page is about.
There are two ways to connect real information to your agent. A **tool** is for anything live, data that changes, or an action that needs to happen, like checking an order or looking up availability. A **Knowledge Base** is for anything written down, like your return policy or an FAQ, that doesn't change from call to call.
Here's the setup used in the video: an order support agent named Maya, for a store called QuickMart. A customer can ask what's in their cart, and Maya calls an API to check it. A customer can also ask a policy question, and Maya answers from a document you uploaded.
## Step 1: Create the tool
Open the node where most of the conversation happens, in this case Maya's main support node. Scroll down and click **Create a Tool**, then choose **External HTTP API**. Name it `get_cart_details`.
The description matters more than anything else here. It's what the agent reads to decide when to call the tool. Write it plainly: *"Use this tool when the caller asks about cart details. It returns the cart's products, quantities, total, and discounted total."*
For the endpoint, this demo uses [dummyjson.com](https://dummyjson.com), a public sample API. Copy this exact URL and test it yourself, no backend required:
```
GET https://dummyjson.com/carts/1
```
No auth, no parameters. It's a GET request because you're fetching information, not changing anything. In your own setup, this would point at your CRM or order system instead.
Save the tool.
## Step 2: Attach the tool to the node
Creating the tool isn't enough by itself. Go back to Maya's node and select `get_cart_details` under Tools, then save.
A tool only becomes available once it's attached to the node that needs it. If you skip this, the tool exists but the agent will never call it.
## Step 3: Upload a Knowledge Base document
Click **Upload Document**, or go straight to **Knowledge Base Files**. Pick the document you want the agent to read from, a return policy, a shipping FAQ, whatever your customers actually ask about.
Dograh asks how it should retrieve from this document:
* **Full Document** works best for small files, where the agent can see the whole thing at once. It does not require an embeddings model.
* **Chunked Search** is for large documents, where the agent should only pull the relevant section. Configure an embeddings model before selecting this mode; otherwise document processing or retrieval will fail.
A short policy document is a Full Document case. Upload it, then wait for the status to say **Completed**.
## Step 4: Attach the document to the node
Same rule as the tool. Go back to Maya's node, scroll to **Knowledge Base Documents**, and attach the file.
Uploading isn't enough on its own. The document has to be attached to the node where the agent should use it.
## Step 5: Tell the agent when to use each
In the node's prompt, spell out which mechanism handles which question. Don't leave it to guesswork:
```
If the caller asks about cart contents, order items, quantities, total, or discounts,
use the get_cart_details tool before answering.
For questions about returns, refunds, or cancellations,
use the attached Knowledge Base document.
```
Save the node.
## Step 6: Test it
Start a Web Call and try both:
* *"Hi Maya, what's inside my cart?"* Watch the transcript. Maya decides the tool is relevant, calls the API, and answers from the response instead of guessing.
* *"What's the return policy?"* Maya retrieves the answer from the document and responds from that context.
That's the difference that matters: for cart details, Maya uses the tool because the data changes call to call. For the return policy, she uses the Knowledge Base because that answer lives in a document and doesn't change. Tools connect your agent to systems. Knowledge Base connects it to documents. Together, your agent starts to feel connected to your actual business, not just reciting a script.
## Next Steps
You've now got the full loop: calls trigger automatically, data flows in and out, your number is connected, and the agent can act on live data and your own documents.
* **Full tool reference**: parameter types, authentication, and best practices in [HTTP API Tools](/voice-agent/tools/http-api).
* **Full Knowledge Base reference**: chunking behavior and embedding setup in [Knowledge Base](/voice-agent/knowledge-base).
# Connect with Telephony
Source: https://docs.dograh.com/getting-started/connect-telephony
Hook up Twilio so Dograh can make and receive real calls, not just browser test calls.
You've covered API Trigger and Webhooks. Both of those work on real calls, which means you need a real phone number connected first. This page walks through Twilio specifically, since it's the fastest way to get a number and test with free trial credits.
## Step 1: Create a Twilio account and buy a number
Sign up at Twilio. You'll land on their dashboard with free trial credits you can use for testing. Go to **Phone Numbers** and click **Buy a number**, pick any number you like.
## Step 2: Copy your Account SID
Back on the Twilio dashboard, copy your **Account SID**.
## Step 3: Add the configuration in Dograh
Open **Telephony** in Dograh, click **Add Configuration**, and select **Twilio** as the provider. Name it something you'll recognize later, your workflow name works well. Paste the Account SID.
Go back to Twilio, copy your **Auth Token**, and paste it in. Keep that token private.
If you want this configuration to be the default for test calls and campaigns, enable that here. For a basic setup, leave answering machine detection off, then click **Create**.
Enable **Set as default for outbound calls** on your telephony configuration. If no default is set, API Trigger will return a `400: Telephony provider not configured for this organization` error even after a successful setup.
## Step 4: Add your phone number
Once the configuration exists, go to manage phone numbers and add the number you bought. Copy it from Twilio and paste it in as-is, country **US**. The label is optional, leave it blank if you want. Select your inbound workflow, in this demo, a debt collection agent. Keep it active, and enable default caller ID if you want test calls to always show that number.
## Step 5: Verify your number
Before testing from a Twilio trial account, verify the destination number you plan to call, such as your personal phone number. This is separate from the Twilio number you purchased for use as the caller ID. In Twilio, go to the [Verified Caller IDs page](https://www.twilio.com/console/phone-numbers/verified) and add the destination number.
## Step 6: Test it
Once verified, trigger a call the same way you did in [Trigger Calls using API](/getting-started/trigger-calls-from-your-backend), from Postman or your backend. Dograh places the outbound call through Twilio. The phone rings, the agent answers, and the connection is live.
On a trial account, Twilio plays a default message before connecting to your agent. Press any key to skip it and go straight to the agent.
Inbound calls work the same way in reverse, routed through Twilio's webhook into your Dograh workflow.
## Next Steps
* **Make your agent do more on the call**: [Use External Tools, Knowledge](/getting-started/add-tools-and-knowledge-base) covers tools and a Knowledge Base.
* **Other providers**: Vonage, Plivo, Cloudonix, and SIP-based setups are covered in [Telephony Integration](/integrations/telephony/overview).
# Your First Agent in 5 Minutes
Source: https://docs.dograh.com/getting-started/first-agent
Build and talk to a working voice agent in five minutes using Web Calls — no telephony setup required.
This is the fastest path to a working voice agent. You'll create an agent, get a generated conversation flow, and talk to it in your browser — no phone number, no telephony provider, no configuration.
Using the hosted platform, go to [app.dograh.com](https://app.dograh.com). Self-hosting locally? Use `http://localhost:3010` instead, and follow [Getting Started](/getting-started) first to get the platform running.
## Step 1: Create an agent
Go to [app.dograh.com/workflow](https://app.dograh.com/workflow) and create a new agent.
You'll be asked for:
* **Direction** — Inbound (agent receives calls) or Outbound (agent makes calls). Pick either; it only affects the starting prompt.
* **Use case** — a description of what the agent should do, e.g. *"Book a haircut appointment, ask for preferred date and time, confirm before ending the call."*
This description is sent to an LLM to generate a starting workflow. The more specific you are, the better the generated prompts and pathways will be.
## Step 2: Land in the workflow editor
After generation, you're dropped into the [Voice Agent Builder](/voice-agent/introduction) with a graph already built for you — typically a **Start Call** node, one or more **Agent** nodes with prompts for your use case, and an **End Call** node, connected by pathways.
You don't need to understand the full [graph model](/voice-agent/introduction#the-graph-model) yet — the generated agent works out of the box. You can come back and customize prompts once you've heard it talk.
## Step 3: Talk to it with a Web Call
In the agent editor, start a **Web Call**. This runs the full pipeline — speech-to-text, LLM, text-to-speech — straight from your browser microphone, exactly like a real phone call would, just without a phone number.
While the call is active, you can watch:
* The **live transcript** as the conversation happens
* **Node transitions** as the agent moves through the graph
* Any **tool calls** the agent makes
Talk to it like a real caller would. Try to get it off-script — say something unexpected — and see how it responds.
## Step 4: Iterate
Didn't go the way you wanted? Open the node whose behavior you want to change and edit its prompt directly. Save, then start a new Web Call to hear the change immediately. No redeploy, no restart.
Common first tweaks:
* Adjust tone or wording in an **Agent** node's prompt
* Add shared instructions (tone, objection handling) to the [**Global**](/voice-agent/global) node
* Add a [**Webhook**](/voice-agent/webhook) node to send call results somewhere once you're happy with the flow
## Next Steps
You have a working agent that runs entirely in the browser. From here:
* **Take real calls** — connect a [telephony provider](/integrations/telephony/overview) and trigger calls via [API Trigger](/voice-agent/api-trigger) or inbound routing.
* **Learn the graph model in depth** — see [Voice Agent Builder](/voice-agent/introduction) for all node types and how pathways work.
* **Give it tools** — let the agent call external APIs or transfer calls with [Tools](/voice-agent/tools/introduction).
* **Embed it on a website** — skip telephony entirely and let website visitors talk to the agent via [Add to Website](/voice-agent/add-to-website).
# Introduction
Source: https://docs.dograh.com/getting-started/index
## About Dograh
**Dograh is an open-source alternative to platforms like Vapi and Retell** - Dograh helps you build voice AI agents with an easy drag-and-drop workflow builder. Unlike proprietary solutions like Vapi, Dograh gives you:
* **100% open source** - no vendor lock-in, full transparency
* **Self-hostable** - deploy anywhere, own your infrastructure
* **Complete control** - every line of code is open and customizable
* **2-minute setup** - from zero to working voice bot in under 2 minutes
## Overview
Watch the following video to learn about Dograh’s capabilities.
## Setting up
Get the platform up and running using Docker with a small startup script on your local computer. If you are looking to deploy the platform on a server different than your local machine, please check the [Deployment](deployment/introduction) section.
We collect anonymous usage data to improve the product. You can opt out by setting `ENABLE_TELEMETRY=false` before running the startup script.
```bash macOS/Linux theme={null}
curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml && curl -o start_docker.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.sh && chmod +x start_docker.sh && ./start_docker.sh
```
```powershell Windows theme={null}
Invoke-WebRequest -OutFile docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml; Invoke-WebRequest -OutFile start_docker.ps1 https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/start_docker.ps1; .\start_docker.ps1
```
**Using an AI coding agent?** If you work in **Claude Code** or **Codex**, install the official Dograh setup skill and let your agent handle installation, configuration, and troubleshooting for you. It orients to your OS, picks the right deploy path, runs Dograh's own setup scripts, and verifies the result. Install it once:
```text Claude Code theme={null}
/plugin marketplace add dograh-hq/dograh-plugins
/plugin install dograh@dograh
```
```text Codex theme={null}
codex plugin marketplace add dograh-hq/dograh-plugins
codex plugin add dograh@dograh
```
Then start a new session and ask it to *"set up Dograh"* (in Claude Code you can also run `/dograh-setup`). See [dograh-hq/dograh-plugins](https://github.com/dograh-hq/dograh-plugins) for details.
Please check [Prerequisites](getting-started/prerequisites) for the system requirements and [Troubleshooting](getting-started/troubleshooting) for common issues.
## Next Steps
Once the platform is running, build your first agent in [Your First Agent in 5 Minutes](/getting-started/first-agent) — no telephony setup required. Or configure an [inference provider](/configurations/inference-providers) first if you're bringing your own LLM keys.
# Prerequisites
Source: https://docs.dograh.com/getting-started/prerequisites
System requirements and setup needed to run Dograh AI locally
## System Requirements (Local Setup)
### Minimum Requirements
* **RAM**: 8 GB (4 GB available for Docker)
* **Storage**: 10 GB free disk space
* **CPU**: 2 cores (x86\_64 or ARM64)
* **OS**:
* macOS 10.15+ (Catalina or newer)
* Windows 10/11 with WSL2
* Linux with kernel 3.10+
## Software Requirements
To run Dograh AI locally, make sure you have the following installed:
* [Docker](https://docs.docker.com/get-docker/) (version 20.10 or later)
* [curl](https://curl.se/download.html) - usually preinstalled on macOS/Linux
Docker Compose is included with Docker Desktop. Make sure Docker is running before you begin.
### Docker Resource Allocation
For Docker Desktop users, ensure Docker has adequate resources:
1. Open Docker Desktop settings
2. Navigate to Resources
3. Allocate at least:
* Memory: 4 GB (8 GB recommended)
* CPUs: 2 (4 recommended)
* Disk: 10 GB
## Required Ports
Ensure these ports are available:
* `3010` - Web UI
* `8000` - API Server
* `5432` - PostgreSQL
* `6379` - Redis
* `9000` - MinIO (S3-compatible storage)
* `9001` - MinIO Console
## Checking Port Availability
Check if a port is in use (replace 3010 with the port number):
```bash macOS/Linux theme={null}
lsof -i :3010
```
```bash Linux (alternative) theme={null}
netstat -tulpn | grep 3010
```
```bash Windows (PowerShell) theme={null}
netstat -ano | findstr :3010
```
## Docker Registry Options
Dograh images are available from two registries:
* **GitHub Container Registry (Default)**: `ghcr.io/dograh-hq` - Recommended for most users
* **Docker Hub**: `dograhai` - Alternative registry
Both registries also host the bundled MinIO image (`minio`), so `REGISTRY` applies to it as well.
To use a specific registry, set the `REGISTRY` environment variable when running the startup script:
```bash theme={null}
# Using GitHub Container Registry (recommended)
REGISTRY=ghcr.io/dograh-hq ./start_docker.sh
# Using Docker Hub
REGISTRY=dograhai ./start_docker.sh
```
```powershell Windows theme={null}
# Using GitHub Container Registry (recommended)
$env:REGISTRY = 'ghcr.io/dograh-hq'; .\start_docker.ps1
# Using Docker Hub
$env:REGISTRY = 'dograhai'; .\start_docker.ps1
```
# Sync Data using Webhook
Source: https://docs.dograh.com/getting-started/send-call-data-with-webhooks
Get everything Dograh learned during the call back into your system the moment it ends.
The last page covered sending data into a call with API Trigger and `initial_context`. Now the opposite side: how Dograh sends what it learned back out to your system once the call ends.
That extracted data lives in `gathered_context`. So the full picture is: `initial_context` is what your backend sends into the call, `gathered_context` is what Dograh extracts from it. A **Webhook** node is how you get `gathered_context` back out.
## Step 1: Add the node
Click **Add node**, scroll down, and you'll find the **Webhook** node. Add it and open it up.
## Step 2: Set the endpoint
First thing you'll see is the endpoint URL. Paste your backend's webhook URL here. Still testing? Use a free URL from [webhook.site](https://webhook.site), copy your unique URL there and paste it into Dograh.
Set the method to **POST**. If your backend needs auth, add a bearer token or API key right here too.
## Step 3: Build the payload template
This is the important part, it decides exactly what JSON Dograh sends. Include both kinds of context: `initial_context` fields like debtor name, client name, and debt amount, and `gathered_context` fields like amount to pay, payment method, and sentiment. You can also include the final business outcome with `{{gathered_context.call_disposition}}`, such as `qualified` or `callback_requested`. If workflow-level extraction is disabled or no custom outcome is recorded, this value falls back to the reason the call ended. See [Call Dispositions](../developer/call-dispositions) to configure the outcomes.
```json theme={null}
{
"debtor_name": "{{initial_context.debtor_name}}",
"client_name": "{{initial_context.client_name}}",
"debt_amount": "{{initial_context.debt_amount}}",
"amount_user_will_pay": "{{gathered_context.amount_user_will_pay}}",
"payment_method": "{{gathered_context.payment_method}}",
"sentiment": "{{gathered_context.sentiment}}",
"call_disposition": "{{gathered_context.call_disposition}}"
}
```
For every variable you can reference here, see the [Webhook Payloads developer reference](/developer/webhooks).
Variable names in your payload have to match your context fields exactly. If a variable cannot be resolved, its field remains in the payload with an empty string value.
## Step 4: Save and publish
Save the webhook node, then publish the agent.
## Step 5: Test it
Run a test call. When it ends, go back to your webhook receiver. You'll see one request with both `initial_context`, the data you sent into the call, and `gathered_context`, what Dograh extracted from the conversation, sitting in the same JSON payload.
That's the complete loop: your backend sends `initial_context` into Dograh, Dograh runs the call and extracts `gathered_context`, and the webhook node sends the final structured data back to your backend. For production, swap the webhook.site URL for your real backend URL.
## Next Steps
* **Connect a real phone number**: [Connect with Telephony](/getting-started/connect-telephony) covers hooking up Twilio so these calls go out for real.
* **Full variable list and authentication options**: see the [Webhook reference](/voice-agent/webhook) and the [Webhook Payloads developer reference](/developer/webhooks).
# Trigger Calls using API
Source: https://docs.dograh.com/getting-started/trigger-calls-from-your-backend
Kick off a call the moment a lead lands in your CRM or spreadsheet, no manual dialing.
If your team is manually dialing leads one by one, or your CRM needs to kick off a call the moment a lead comes in, this is how you automate that with Dograh.
Say you've got leads or accounts sitting in a CRM or a Google Sheet. Each row has details like customer name, company name, amount due. The **API Trigger** node lets you take that row of data and turn it into a personalized call, automatically.
## Step 1: Add the node
Click **Add node** and add an **API Trigger** node, the topmost option in the list. This node starts the call and passes your data into the agent as `initial_context`.
That's the part that matters: instead of the agent asking basic setup questions on the call, like "what's your name" or "how much do you owe", it already knows. In this demo, the opening node greets the caller with `{{debtor_name}}`, and the next node references `{{client_name}}` and `{{debt_amount}}`. None of that is hardcoded into the agent. It all comes from the API call.
Every node has a docs link in the top right corner. If you get stuck or want to go deeper on any node type, open the docs straight from there.
## Step 2: Check the prerequisites
Before you trigger anything:
1. [Connect with Telephony](/getting-started/connect-telephony) for outbound calls.
2. Go to the developer portal and generate an API key. You'll need it in the request header.
## Step 3: Find your trigger URL
Open the API Trigger node. You'll see two URLs:
* **Test URL** runs your latest draft.
* **Production URL** runs your published agent.
If you change the agent and want the production URL to reflect it, publish first, otherwise production keeps running the older version. While you're still testing, use the test URL.
## Step 4: Send the request
Trigger the call from Postman, or wherever your backend lives. In the body, send `phone_number` and `initial_context`:
```bash theme={null}
curl -X POST https://api.dograh.com/api/v1/public/agent/test/{uuid} \
-H "Content-Type: application/json" \
-H "X-API-Key: dg_your_api_key" \
-d '{
"phone_number": "+14155550100",
"initial_context": {
"debtor_name": "Rushil",
"client_name": "Quick Credit",
"debt_amount": "3500"
}
}'
```
The body can come from your CRM, a Google Sheet, or your own backend, wherever that row of data lives.
## What you get
Send the request, and the call goes out immediately. Because the agent already had the context before the call started, the conversation is short and specific from the first line: *"Hi, this is Alex from Apex Recovery Solutions. Am I speaking with Rushil? I'm calling about your Quick Credit account with an outstanding balance of \$3,500."* No setup questions, straight to the point.
## Next Steps
* **Get that data back automatically**: the next step is [Sync Data using Webhook](/getting-started/send-call-data-with-webhooks), which takes whatever the agent learns on the call and sends it back to your system the moment it ends.
* **No phone number connected yet?** See [Connect with Telephony](/getting-started/connect-telephony) first, outbound calls need a telephony provider attached.
* **Full endpoint reference**: error codes, response shape, and choosing a specific telephony configuration are in [API Trigger](/voice-agent/api-trigger).
# Troubleshooting
Source: https://docs.dograh.com/getting-started/troubleshooting
Common issues and solutions for running Dograh AI
# Troubleshooting
## Freeing Up Ports
### When a port is already in use:
##### Check what's using the port first and then kill the process (may require sudo on Linux)
```bash macOS/Linux theme={null}
lsof -i :3010
kill -9 $(lsof -t -i :3010)
```
```powershell Windows theme={null}
netstat -ano | findstr :3010
# Find the PID in the last column, then:
Stop-Process -Id -Force
```
### When Docker containers are using the ports (with auto-restart enabled):
**Step 1:** Stop all running containers
```bash theme={null}
docker stop $(docker ps -q)
```
**Step 2:** Disable restart policy for all containers
This prevents containers from automatically restarting:
```bash theme={null}
docker update --restart=no $(docker ps -a -q)
```
**Step 3:** Verify
Check that no containers are running:
```bash theme={null}
docker ps
```
Check restart policies (should show 'no' for each container):
```bash theme={null}
docker inspect -f '{{.Name}} - {{.HostConfig.RestartPolicy.Name}}' $(docker ps -a -q)
```
## Stopping Dograh Services
##### Stop services or Stop and remove all data (full cleanup)
```bash theme={null}
docker compose down
docker compose down -v
```
# MCP Server
Source: https://docs.dograh.com/integrations/mcp
Connect Codex, Claude, and other AI assistants to your Dograh workspace via the Model Context Protocol
## Overview
Dograh exposes an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that lets AI assistants like Codex, Claude Code, Claude Desktop, and Cursor access your workspace and documentation. Once connected, an assistant can list your agents, fetch agent definitions, and search Dograh docs on your behalf.
## Prerequisites
* A Dograh API key. Generate one at [`/api-keys`](https://app.dograh.com/api-keys) (or `http://localhost:3010/api-keys` for self-hosted). See [API Keys](/configurations/api-keys).
* Your Dograh MCP endpoint:
* Hosted: `https://app.dograh.com/api/v1/mcp/`
* Self-hosted: `/api/v1/mcp/`
The endpoint is also shown in **Platform Settings → MCP Server** inside the Dograh UI.
If you deployed Dograh to a remote server using [`setup_remote.sh`](/deployment/docker#option-2%3A-remote-server-deployment), your endpoint is served with a self-signed SSL certificate. Claude Code will refuse MCP connections to it unless you start the CLI with TLS verification disabled:
```bash theme={null}
NODE_TLS_REJECT_UNAUTHORIZED=0 claude
```
This is only required for self-signed certificates. If you have set up a [custom domain](/deployment/custom-domain) with Let's Encrypt certificates via Certbot, no extra flag is needed.
## Claude Code
Register Dograh as an MCP server with the Claude Code CLI:
```bash theme={null}
claude mcp add --transport http dograh https://app.dograh.com/api/v1/mcp/ \
--header "X-API-Key: YOUR_API_KEY"
```
Replace `YOUR_API_KEY` with the key you generated. For self-hosted deployments, swap the URL for your backend.
Verify the server is connected:
```bash theme={null}
claude mcp list
```
## Codex
Open Codex's config file (`~/.codex/config.toml`) and add a `dograh` MCP server:
```toml theme={null}
[mcp_servers.dograh]
url = "https://app.dograh.com/api/v1/mcp/"
http_headers = { "X-API-Key" = "YOUR_API_KEY" }
```
Replace `YOUR_API_KEY` with the key you generated. For self-hosted deployments, replace the URL with your backend MCP endpoint.
If you prefer to keep the API key out of `config.toml`, store it in an environment variable instead:
```toml theme={null}
[mcp_servers.dograh]
url = "https://app.dograh.com/api/v1/mcp/"
env_http_headers = { "X-API-Key" = "DOGRAH_API_KEY" }
```
Then set the API key before starting Codex:
```bash theme={null}
export DOGRAH_API_KEY="YOUR_API_KEY"
codex
```
Verify the server is registered:
```bash theme={null}
codex mcp list
codex mcp get dograh
```
## Claude Desktop
Open Claude Desktop's config file (`claude_desktop_config.json`) and add the `dograh` entry under `mcpServers`:
```json theme={null}
{
"mcpServers": {
"dograh": {
"url": "https://app.dograh.com/api/v1/mcp/",
"headers": {
"X-API-Key": "YOUR_API_KEY"
}
}
}
}
```
Restart Claude Desktop after saving. The Dograh tools should appear in the tool picker.
## Cursor and other MCP clients
Any MCP client that supports Streamable HTTP transport can connect with the same URL and header. Paste the configuration above into your client's MCP settings file and replace `YOUR_API_KEY`.
## Example prompts
Once the MCP server is connected, you can drive Dograh from your coding agent in plain English. A few prompts to try:
**Explore your workspace**
* "List my agents in Dograh."
* "Show me the definition of the agent called *Lead Qualifier*."
* "Which credentials and tools are configured in my Dograh workspace?"
* "List the recordings from my most recent agent."
**Edit an agent**
* "In my *Lead Qualifier* agent, add a new agent node after the greeting that asks the caller for their budget, then routes to the existing qualification node."
* "Add an end-call node to *Support Bot* that triggers when the user says they are done, with a polite goodbye prompt."
* "Rename the *intro* node in *Lead Qualifier* to *greeting* and update any edges that reference it."
* "Change the LLM model on all agent nodes in *Support Bot* to `gpt-4o-mini`."
**Learn the platform**
* "Search the Dograh docs for how to configure a TURN server."
* "What node types does Dograh support, and what fields does a `knowledge_base` node take?"
* "How do I deploy Dograh on a custom domain with HTTPS?"
Agent edits are saved as a new **draft** version — your published agent keeps serving calls until you explicitly publish the draft from the Dograh UI.
The API key controls which workspace the assistant sees. Treat it like any other credential — do not commit it to source control or paste it into shared chats.
# Noveum Integration
Source: https://docs.dograh.com/integrations/noveum
Connect Dograh to Noveum — full-call tracing, audio capture, and AI-powered evaluation for your voice agents
## Overview
The Noveum integration node automatically exports every completed call to [Noveum](https://noveum.ai) as a full observability trace. Each call becomes a structured trace with per-turn spans for STT, LLM, and TTS — transcripts, prompts and completions, token usage, latency breakdowns, interruptions, and (optionally) the call audio itself, ready for playback and audio-level evaluation on the Noveum platform. No custom code required.
The export is **post-call and bring-your-own-key**: while the call is live, Dograh only records data locally — nothing is sent to Noveum and no latency is added to the conversation. After the call finishes, the trace and audio are uploaded in the background using the Noveum API key you configure on the node.
## Prerequisites
* A [Noveum account](https://noveum.ai) with an API key
* A Dograh voice agent workflow
## Setup
### 1. Get your Noveum credentials
Log in to [Noveum](https://noveum.ai) and gather two values:
| Credential | Where to find it |
| - | - |
| **API Key** | Dashboard → Settings → **API Keys** → Create New API Key |
| **Project** | The project name your traces should land in — pick any slug (e.g. `my-voice-agent`); it is created on first export |
Treat the API key like a password — it authorizes writes into your Noveum project.
### 2. Add the Noveum node to your workflow
In your Dograh workflow editor, click **Add node** and scroll to the **Integrations** section. Select **Noveum**.
Scroll to **Integrations** and select **Noveum**:
The node appears on your canvas with a **Not configured** badge. Like other integration nodes, it is standalone — it does not connect to any other node in the conversation graph, and a workflow can contain at most one Noveum node.
### 3. Configure the node
Click on the Noveum node and fill in the following fields:
* **Noveum API Key** — Your API key from step 1
* **Noveum Project** — The Noveum project the call traces are exported into
* **Noveum Environment** — Environment label stamped on exported traces (defaults to `production`; use `staging`, `development`, etc. to separate environments)
* **Record audio** — Capture per-segment STT/TTS audio plus the full-conversation recording for audio evaluation on Noveum (on by default)
* **Enabled** — Toggle on to activate the export
Click **Save**, then **Publish** your workflow.
### 4. Verify the connection
Make a test call through your agent. Once the call completes, open your project in the Noveum dashboard — the call appears as a new trace within a few moments. Open it to see the turn-by-turn timeline with STT, LLM, and TTS spans, and play back the captured audio.
## What gets exported
Each call produces one conversation trace containing:
| Data | Details |
| - | - |
| **Turn spans** | One span per user→bot exchange, with duration, user→bot latency, and interruption (barge-in) flags |
| **STT spans** | Final transcript, interim results, confidence, language, and speech-to-final latency |
| **LLM spans** | Full message history, system prompt, completion, token usage, time-to-first-token, and tool/function calls |
| **TTS spans** | Synthesized text, voice, characters, and time-to-first-byte |
| **Latency breakdown** | Per-service TTFB (STT / LLM / TTS) and end-to-end user→bot latency per turn |
| **Call metadata** | Workflow run ID, call disposition, agent version, run mode, and the STT/LLM/TTS model names configured on the workflow |
| **Audio** (when **Record audio** is on) | Per-turn user and bot audio segments plus a full-conversation stereo recording (left channel = user, right channel = bot), attached to their spans for playback and audio evaluation |
Once the traces are in Noveum you can inspect and replay calls, build evaluation datasets from production traffic, and run automated scorers over transcripts and audio to track agent quality over time.
## Disabling the integration
To temporarily stop exporting calls to Noveum, open the Noveum node configuration and toggle **Enabled** off. Your credentials are preserved — toggle it back on anytime to resume.
## Troubleshooting
| Issue | Solution |
| - | - |
| Traces not appearing in Noveum | Verify the API key and project are correct with no extra whitespace |
| Node shows "Not configured" | Open the node and fill in the Noveum API Key and Project — both are required when the node is enabled |
| Workflow not sending data | Make sure the workflow is published, not just saved as a draft |
| Trace arrives but has no audio | Confirm **Record audio** is toggled on in the node configuration |
| Calls that never connected show no trace | Expected — runs that never start a pipeline (e.g. telephony failures) produce no trace to export |
## Learn more
* [Noveum](https://noveum.ai) — AI observability and evaluation platform
* [Noveum documentation](https://noveum.ai/docs) — Complete platform docs, including the [Dograh integration guide](https://noveum.ai/docs/integration-examples/dograh/overview)
# Paygent Integration
Source: https://docs.dograh.com/integrations/paygent
Connect Dograh to Paygent — real-time cost tracking, multimodal usage monitoring, and billing for voice agents
## Overview
**Paygent** is a specialized, usage-based billing platform designed exclusively for AI voice agents.
If you are building voice agents for clients or offering them as a SaaS product, calculating margins across different AI providers (LLMs, TTS, STT, and Speech-to-Speech models) can be incredibly complex. Paygent solves this by serving as your centralized billing engine.
### How you charge your customers
Instead of building custom tracking infrastructure, you simply connect your Dograh workflow to Paygent. As your agents handle calls, Dograh passively calculates the exact multimodal token usage and audio duration.
This data is securely exported to Paygent after every call, where your custom pricing margins (rate cards) are applied. This seamless flow allows you to automatically invoice your end-users for the exact infrastructure they consume — turning your AI agents into a scalable, profitable business with zero engineering overhead.
## Prerequisites
* A [Paygent account](https://withpaygent.com)
* A Dograh voice agent workflow
## Setup
### 1. Create an agent and configure pricing in Paygent
Before connecting Dograh, you must register your agent in Paygent and define how you want to charge your customers.
Log in to [Paygent](https://withpaygent.com) and click **Create Agent**. You will be prompted to define your agent's core details, including the **Agent Name** and **Agent ID**:
Next, set the **Indicator Name**. This is the billing event identifier you will pass from Dograh (e.g. `per-minute-call`) to tell Paygent which rate to apply for this agent's calls:
Finally, you will configure your **Pricing Strategy**. Paygent allows you to set custom markup rates (rate cards) for every modality. You can define exact margins for Speech-to-Text (STT) seconds, LLM tokens, Text-to-Speech (TTS) characters, and total call minutes:
Once your pricing is configured, confirm your setup. You will use the **Agent ID** and **Indicator** from this process to connect your Dograh workflow.
### 2. Gather your Paygent credentials
You'll need three core values from your Paygent dashboard to link your Dograh agent:
| Credential | Where to find it / Description |
| - | - |
| **Paygent API Key** | Your workspace API key used to authenticate requests (`pg_live_...`) |
| **Agent ID** | The unique identifier configured for your agent in Step 1 |
| **Customer ID** | Your Paygent organisation or customer ID |
### 3. Add the Paygent node to your workflow
In your Dograh workflow editor, click **Add node** and scroll to the **Integrations** section. Select **Paygent**. The node will appear on your canvas with a **Not configured** badge.
### 4. Configure the node
Click on the Paygent node and fill in the following fields:
* **Paygent API Key** — Your `pg_live_...` secret key
* **Agent ID** — The unique agent identifier from Paygent
* **Customer ID** — Your Paygent organisation ID
* **Indicator** — The billing event name (defaults to `per-minute-call`)
* **Enabled** — Toggle on to activate the export
Click **Save**, then **Publish** your workflow.
### 5. Verify the connection
Make a test call through your agent. Once the call completes, check your Paygent dashboard. The billing event and detailed multimodal usage breakdown should appear under your configured Agent ID within a few moments, with your configured pricing margins automatically applied.
## Disabling the integration
To temporarily stop exporting usage data to Paygent, open the Paygent node configuration and toggle **Enabled** off. Your credentials are preserved — toggle it back on anytime to resume tracking.
## Troubleshooting
| Issue | Solution |
| - | - |
| Usage data not appearing in Paygent | Verify all credentials are correct with no extra whitespace |
| Node shows "Not configured" | Open the node and fill in API Key, Agent ID, Customer ID, and Indicator |
| Workflow not sending data | Make sure the workflow is published, not just saved as a draft |
| Missing realtime STS tokens | For OpenAI Realtime or Google Live models, verify the pipeline is running in realtime mode (`is_realtime=True`) |
## Learn more
* [Paygent](https://withpaygent.com) — Usage-based billing platform for AI agents
# Agent Stream
Source: https://docs.dograh.com/integrations/telephony/agent-stream
Stream audio to a Dograh agent over a WebSocket
## Overview
Agent Stream is a WebSocket endpoint that lets a telephony provider point its media stream at a single URL and drive a Dograh agent run. The agent UUID in the URL selects the agent, and the provider path segment selects the streaming protocol. Provider-specific identifiers come from the stream protocol itself. For Cloudonix, Dograh reads the domain from the `start.accountSid` field, then uses the matching stored telephony configuration to validate the session and issue provider API calls (hangup, transfer) during the call.
This is useful when:
* You're integrating Dograh into a SIP gateway or in-house dialer that already speaks a supported provider's streaming protocol
* You want one stable endpoint per agent rather than wiring up an inbound webhook per phone number
Agent Stream currently supports the **Cloudonix** provider only. Other providers
return `NotImplementedError` until a per-provider implementation lands. If you
need Twilio, Plivo, Telnyx, Vonage, ARI, or another provider, please open a
request on [GitHub Discussions](https://github.com/dograh-hq/dograh/discussions)
with your use case.
## Endpoint
```
wss://api.dograh.com/api/v1/agent-stream/{provider}/{agent_uuid}
```
`{provider}` is the registered provider name, currently `cloudonix`. `{agent_uuid}` is the agent's stable UUID (see [Get the Agent UUID](#get-the-agent-uuid) below). On self-hosted deployments, replace `api.dograh.com` with your backend host.
## Prerequisites
* A Dograh agent (workflow) — published or in draft is fine
* A Cloudonix telephony configuration in your Dograh organization whose `domain_id` matches the `accountSid` Cloudonix sends in the stream `start` message. Dograh uses the bearer token from this configuration to validate the call session and to issue provider API calls (hangup, transfer).
## Get the Agent UUID
The Agent UUID is the workflow's stable identifier — it doesn't change when versions are published.
To find and copy it in the UI, see [Agent UUID](/configurations/agent-uuid).
## Connect to the WebSocket
### Path parameters
| Param | Required | Description |
| - | - | - |
| `provider` | Yes | Provider name. Currently only `cloudonix` is supported. |
| `agent_uuid` | Yes | Stable UUID of the Dograh agent to run. |
### Cloudonix example
```
wss://api.dograh.com/api/v1/agent-stream/cloudonix/{agent_uuid}
```
Use this URL inside the CXML `` your Cloudonix Voice Application returns when the call needs to be bridged to the Dograh agent:
```xml theme={null}
```
The first two messages on the socket should be Cloudonix's standard `connected` and `start` events (Twilio-compatible framing). Dograh extracts `streamSid`, `callSid`, `session`, `accountSid`, `from`, `to`, `context`, `tracks`, and `mediaFormat` from the `start` event payload. It validates the session against Cloudonix using the bearer token from the stored telephony configuration matched by `accountSid`, then begins streaming audio.
## Workflow run lifecycle
When the WebSocket is accepted, Dograh:
1. Looks up the workflow by `agent_uuid`
2. Runs a quota check against the workflow's owning user
3. Creates a new `WorkflowRun` (`call_type=inbound`, `mode=cloudonix`, name `WR-AGS-XXXXXXXX`)
4. Transitions the run to `running`
5. Reads and validates the Cloudonix `start` message, then stamps the `from`/`to` numbers on `initial_context`, stores `session` as `call_id` on `gathered_context`, and records `accountSid` under the run's `inbound_webhook` log
6. Starts the agent pipeline
The run is visible under the agent's **Runs** tab as soon as it's minted, just like an inbound or outbound call.
## Close codes
| Code | Reason |
| - | - |
| `1008` | Routing failure — unknown provider, workflow not found, or quota exceeded |
| `1011` | Server-side failure or unsupported provider for Agent Stream |
| `4400` | Provider-level handshake error — for cloudonix, missing `start.accountSid`, no matching telephony configuration, missing bearer token on the configuration, malformed `connected`/`start` events, or session validation failed against Cloudonix |
## Security notes
* Treat the URL as a secret — the agent UUID itself authorizes the connection. Store and transmit it only over TLS, and avoid logging the raw URL in places where access is broader than your operations team.
* No bearer tokens or provider secrets are passed in the URL. Provider credentials live in the stored telephony configuration (matched by `start.accountSid` for Cloudonix) and are used server-side by Dograh to validate the session and issue provider API calls.
# Asterisk ARI Integration
Source: https://docs.dograh.com/integrations/telephony/asterisk-ari
Connect Dograh AI to your Asterisk PBX using the Asterisk REST Interface (ARI)
## Overview
Asterisk ARI (Asterisk REST Interface) allows you to connect Dograh AI voice agents to your existing Asterisk PBX. ARI provides a WebSocket-based event model for controlling calls via Stasis applications, giving Dograh full control over call flow and audio streaming.
This guide focuses on the Dograh-specific configuration. For general Asterisk installation and administration, refer to the [official Asterisk documentation](https://docs.asterisk.org/).
## Prerequisites
Before setting up the ARI integration, ensure you have:
* A running Asterisk instance with `chan_websocket` and `res_websocket_client` modules available. Known-working setups: (a) Asterisk 22+, (b) Asterisk 20 LTS with these modules included
* ARI module enabled in Asterisk
* `chan_websocket` (WebSocket channel driver) and `res_websocket_client` (loads `websocket_client.conf`) enabled in your Asterisk build. Verify with `asterisk -rx "module show like chan_websocket"` and `asterisk -rx "module show like res_websocket_client"` — both should report **Running**.
* Network connectivity between your Dograh instance and Asterisk
* Dograh AI instance running and accessible
If you compiled Asterisk from source, ensure both `chan_websocket` and `res_websocket_client` are included during the build. These modules are required for external media streaming between Asterisk and Dograh. Refer to the [Asterisk build system documentation](https://docs.asterisk.org/) for details on enabling modules.
## How setup fits together
Setup crosses between the two systems, so the order matters:
| | Where | What |
| - | - | - |
| **1** | Asterisk | Create the ARI user, enable the HTTP server, and add the external-media WebSocket client — the values Dograh asks for |
| **2** | Dograh | Enter those values and save. Dograh generates a **Stasis App Name** unique to this configuration |
| **3** | Asterisk | Put that generated name in your dialplan as `Stasis()` and reload |
| **4** | Dograh | Register the extensions that should reach an agent, then place a test call |
The dialplan comes last because the Stasis application name does not exist until the configuration is saved. Everything else can be set up in advance.
## Part 1: Asterisk connection settings
These are minimal examples focused on the Dograh integration -- refer to the [Asterisk documentation](https://docs.asterisk.org/) for full configuration details.
### Enable ARI (`ari.conf`)
Create an ARI user that Dograh will use to authenticate:
```ini theme={null}
[general]
enabled = yes
[dograh]
type = user
read_only = no
password = your_secure_password
```
The username (section name, e.g., `dograh`) and password here are the **ARI Username** and **App Password** you enter in Dograh. This name authenticates to Asterisk — it is *not* the Stasis application name, which Dograh generates for you in [Part 2](#part-2-create-the-configuration-in-dograh).
### Enable the HTTP Server (`http.conf`)
ARI requires the Asterisk HTTP server to be enabled:
```ini theme={null}
[general]
enabled = yes
bindaddr = 0.0.0.0
bindport = 8088
```
### Configure External Media Streaming (`websocket_client.conf`)
Dograh uses Asterisk's external media streaming to send and receive audio over WebSocket. Configure a WebSocket client connection that points to your Dograh instance:
```ini theme={null}
[dograh]
type = websocket_client
uri = wss://api.dograh.com/api/v1/telephony/ws/ari
protocols = media
tls_enabled = yes
ca_list_file = /etc/ssl/certs/ca-certificates.crt
```
`tls_enabled = yes` is required even though the URI scheme is `wss://` — without it Asterisk will not negotiate TLS and the connection will fail. The ARI credentials (**ARI Username** and **App Password**) must match what you configure in the Dograh dashboard under Telephony Settings.
```ini theme={null}
[dograh]
type = websocket_client
uri = ws://your-dograh-host:port/api/v1/telephony/ws/ari
protocols = media
```
Self-hosted deployments on an internal network may use an unencrypted WebSocket (`ws://`). If your Dograh instance is exposed over HTTPS, use `wss://` and the corresponding hostname instead.
The section name (e.g., `dograh`) is the **WebSocket Client Name** you'll enter in the Dograh telephony configuration. This name tells Asterisk which WebSocket connection to use for external media streaming during calls.
Configure the `uri` as a base URL only, without a query string. During each call, Dograh asks Asterisk to create an `externalMedia` channel and Asterisk appends `workflow_id`, `organization_id`, and `workflow_run_id` through the `v()` transport data for that call. Opening `/api/v1/telephony/ws/ari` directly in a browser or with `wscat` can return HTTP 403 because those routing parameters are missing; that is expected and does not indicate a `websocket_client.conf` misconfiguration.
Dograh's external media channel uses **G.711 μ-law (`ulaw`)**. Make sure any PJSIP endpoint or SIP trunk that places or receives calls through Dograh allows `ulaw` (e.g. `allow=ulaw` in the endpoint config).
Refer to the [Asterisk WebSocket documentation](https://docs.asterisk.org/) for additional `websocket_client.conf` options and TLS configuration.
### Apply the configuration changes
Reload the affected Asterisk modules from the Asterisk CLI (`asterisk -rvvv`):
```bash theme={null}
module reload res_ari.so # picks up ari.conf changes
module reload res_websocket_client.so # picks up websocket_client.conf changes
```
Changes to `http.conf` require a full Asterisk reload (`core reload`) or a service restart. `core reload` also covers both commands above if you would rather reload everything at once.
## Part 2: Create the configuration in Dograh
### Step 1: Navigate to Telephony Settings
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Asterisk ARI** as your provider
### Step 2: Enter Your ARI Credentials
Configure the following fields:
| Field | Description | Example |
| - | - | - |
| **ARI Endpoint URL** | HTTP base URL of your Asterisk ARI server | `http://asterisk.example.com:8088` |
| **ARI Username** | The ARI username configured in `ari.conf` | `dograh` |
| **App Password** | The ARI password configured in `ari.conf` | `your_secure_password` |
| **WebSocket Client Name** | The connection name from `websocket_client.conf` | `dograh` |
| **From Extensions** | Optional SIP extensions or trunk numbers for outbound calls | `PJSIP/6001` or `6001` |
| **Dial String Template** | How a dialed number reaches your trunk. See [Outbound Calling](#outbound-calling) | `PJSIP/{number}@my-trunk` |
**Stasis App Name** is not something you enter — Dograh generates it when you save and displays it on the configuration for you to copy.
### Step 3: Save and copy the Stasis App Name
Click **Save Configuration**. Dograh assigns a **Stasis App Name** to this configuration — something like `dograh_a1b2c3d4e5f6` — and shows it on the configuration page. Copy it; you need it for [Part 3](#part-3-route-calls-into-the-stasis-application).
The Stasis App Name is generated, not chosen. Asterisk gives a Stasis application to whichever ARI connection registered for it most recently and stops delivering events to the previous one, with no error on either side. If two configurations named the same application on one Asterisk, one would silently stop receiving calls while the other received calls that were not its own. Generating the name is what prevents that.
## Part 3: Route calls into the Stasis application
### Configure the Stasis Dialplan (`extensions.conf`)
Route incoming calls into the Stasis application Dograh generated for your configuration:
```ini theme={null}
[from-external]
exten => _X.,1,NoOp(Incoming call to ${EXTEN})
same => n,Stasis(dograh_a1b2c3d4e5f6)
same => n,Hangup()
```
Replace `dograh_a1b2c3d4e5f6` with the **Stasis App Name** from your configuration, then reload the dialplan:
```bash theme={null}
dialplan reload
```
Until the dialplan names the generated application, calls reach Asterisk but never arrive at Dograh. This is the most common reason a correctly configured ARI integration receives no calls.
## Part 4: Add extensions and test
1. Add each SIP extension that should be reachable as a **phone number** (e.g. `8000`). For inbound, you'll assign a workflow to each extension separately — see [Inbound Calling](#inbound-calling) below.
2. Create a test workflow and initiate a test call to verify the connection.
## Outbound Calling
Asterisk dials a channel technology and a route, not a number. Dograh builds that dial string from the **Dial String Template** on your configuration, replacing `{number}` with the number being called.
The default is `PJSIP/{number}`, which dials a PJSIP endpoint *named after the number*. That is right when your endpoints are the extensions you dial, and wrong on an install whose endpoints are trunks — there, originating `PJSIP/+966500000000` fails with `Allocation failed`, because no endpoint by that name exists.
Pick the template that matches how your dialplan places outbound calls:
| Your Asterisk | Template | Dials |
| - | - | - |
| Endpoints named after extensions | `PJSIP/{number}` | `PJSIP/1001` |
| One outbound trunk | `PJSIP/{number}@my-trunk` | `PJSIP/+966500000000@my-trunk` |
| FreePBX, or any dialplan with outbound routes | `Local/{number}@from-internal` | `Local/+966500000000@from-internal` |
Routing through a `Local` channel is usually the right choice on FreePBX: the call enters your dialplan, and your own outbound routes handle number translation and trunk selection exactly as they do for a deskphone.
A destination that already names a channel technology — `PJSIP/1001@my-trunk`, `Local/1001@from-internal`, `SIP/1001` — is dialed exactly as written, whatever the template says. That is what lets one campaign row or one test call address a specific trunk without changing the configuration.
### Campaign contacts
A campaign's `phone_number` column is a destination for this Asterisk, not necessarily a phone number. Extensions (`1001`), SIP URIs (`sip:1001@pbx.local`) and dial strings (`PJSIP/1001@my-trunk`) are all accepted alongside E.164 numbers, and each row goes through the same template above.
Two rules still apply: a destination cannot contain spaces, commas or ampersands, because each of those means something else inside a dial string; and destinations must be unique within a campaign.
Other providers are carriers and reject anything that is not a routable number, so their campaign uploads still require E.164 (`+` and country code). The rule follows the configuration the campaign dials with.
## Inbound Calling
Unlike other telephony providers that use HTTP webhooks for inbound calls, ARI delivers inbound calls as **StasisStart events on the ARI WebSocket**. Dograh automatically detects these events and activates the workflow assigned to the called extension.
### How It Works
1. An external call arrives at Asterisk and the dialplan routes it into your configuration's Stasis application
2. Asterisk fires a StasisStart event over the ARI WebSocket with the channel in `Ring` state and the dialed extension in the dialplan context
3. Dograh looks up the called extension in your telephony configuration's phone numbers, finds the assigned workflow, validates quota, and creates a workflow run
4. The call is answered, bridged to an external media channel, and your voice agent workflow begins
Workflow assignment is **per extension**, so different extensions on the same Asterisk can route to different agents.
### Setting Up Inbound Calls
**Step 1: Configure the Asterisk dialplan**
Ensure your dialplan routes the extensions you care about into the Stasis application you configured in [Part 3](#part-3-route-calls-into-the-stasis-application). Either route a specific extension:
```ini theme={null}
[from-external]
exten => 8000,1,NoOp(Incoming call to 8000)
same => n,Stasis(dograh_a1b2c3d4e5f6)
same => n,Hangup()
```
…or use a pattern that catches every extension you'll register in Dograh:
```ini theme={null}
[from-external]
exten => _X.,1,NoOp(Incoming call to ${EXTEN})
same => n,Stasis(dograh_a1b2c3d4e5f6)
same => n,Hangup()
```
Replace `dograh_a1b2c3d4e5f6` with the **Stasis App Name** shown on your Dograh configuration.
**Step 2: Add the extension as a phone number in Dograh**
1. Go to **/telephony-configurations** and open your Asterisk ARI configuration
2. In the **Phone numbers** section, add a phone number whose address is the SIP extension (e.g. `8000`)
3. Set its **Inbound workflow** to the agent that should answer
4. Save
Adding the extension in Dograh doesn't change Asterisk's dialplan — that's
what Step 1 is for. The Dograh entry tells the StasisStart handler which
workflow to run when a call to that extension reaches the Stasis app.
Repeat Step 2 for each extension that should reach a voice agent.
**Step 3: Test an inbound call**
Place a call to one of the extensions you configured. You should see the assigned workflow activate and the voice agent respond.
### Inbound Call Context
When an inbound call activates a workflow, the following context is available to your workflow:
| Field | Description |
| - | - |
| `caller_number` | The caller's phone number or extension |
| `called_number` | The dialed number or extension |
| `direction` | Always `inbound` |
| `call_id` | The Asterisk channel ID |
| `provider` | Always `ari` |
## Troubleshooting
* Verify the ARI endpoint URL is correct and reachable from your Dograh instance
* Check that the Asterisk HTTP server is running (`http.conf` has `enabled = yes`)
* Ensure firewall rules allow traffic on the ARI port (default: 8088)
* Confirm the ARI module is loaded: run `module show like res_ari` in the Asterisk CLI
* Verify the ARI Username matches the ARI user section name in `ari.conf`
* Check the App Password matches the password in `ari.conf`
* Ensure there are no extra spaces in the credentials
* Verify `chan_websocket` is loaded: run `module show like chan_websocket` in the Asterisk CLI
* Check that `websocket_client.conf` is correctly configured with the right Dograh URI
* Ensure the WebSocket Client Name in Dograh matches the section name in `websocket_client.conf`
* Verify network connectivity and firewall rules allow WebSocket traffic between Asterisk and Dograh
* Ensure the dialplan routes calls to `Stasis(...)` using the **Stasis App Name** from your Dograh configuration, not the ARI username
* Confirm you ran `dialplan reload` after editing `extensions.conf`
* If two Dograh configurations point at this Asterisk, check that they do not name the same Stasis application — the one that connected most recently takes the application over and the other stops receiving calls entirely
* Check Asterisk CLI for errors: `asterisk -rvvv`
* Confirm the ARI WebSocket connection is active
* Verify the called extension is added as a phone number under your ARI
configuration in /telephony-configurations and has an **Inbound workflow**
assigned
* Confirm the workflow exists and belongs to the same organization as the
ARI config
* Check that your organization has available quota
* Review Dograh logs for warnings like "no matching phone number registered
for config" or "has no inbound\_workflow\_id assigned"
* Check the URI in `websocket_client.conf` points to the correct Dograh host and port
* Verify the Dograh instance is running and accepting WebSocket connections
* If using TLS, ensure certificates are correctly configured on both sides
## Best Practices
* Keep your Asterisk instance on the same network or a low-latency connection to Dograh for optimal audio quality
* Use strong passwords for ARI authentication
* Restrict ARI access to known IP addresses using firewall rules
* Monitor Asterisk logs alongside Dograh logs when debugging call issues
* Keep Asterisk updated to the latest stable version for security and compatibility
## Further Reading
* [Asterisk Documentation](https://docs.asterisk.org/) -- official reference for all Asterisk configuration
* [ARI Documentation](https://docs.asterisk.org/Configuration/Interfaces/Asterisk-REST-Interface-ARI/) -- detailed ARI configuration and API reference
# Cloudonix Integration
Source: https://docs.dograh.com/integrations/telephony/cloudonix
Configure Cloudonix for voice communication in Dograh AI
## Overview
Cloudonix is a cloud communications platform that enables Dograh voice agents to connect to SIP communications
platform, either on-premise or in the cloud. In addition, Cloudonix exposes a full set of voice APIs, that provide
a full suite of call-control capabilities and more.
**Already have a configuration named "Dograh Cloudonix SIP"?** That one is
provisioned for you — Dograh owns the Cloudonix domain, so it needs no
Cloudonix account or credentials of your own. It does need you to connect
your SIP carrier and add your phone numbers. Follow
[Dograh Managed SIP](/integrations/telephony/dograh-sip) instead of this page.
This page covers bringing **your own** Cloudonix account as a separate
configuration.
## Prerequisites
Before setting up Cloudonix integration, you'll need:
* A [Cloudonix account](https://cockpit.cloudonix.io/onboarding?affiliate=DOGRAH)
* A Cloudonix domain UUID (or the domain name)
* A Cloudonix domain API Key (Bearer Token)
* A Cloudonix **Voice Application** on that domain (optional — leave the field blank in Dograh and we'll auto-create one for you on save, with the application `url` pre-set)
* A Cloudonix outbound voice trunk service provider connection
* Dograh AI instance running and accessible
## Video Tutorial
Watch this step-by-step guide to set up Cloudonix with Dograh AI:
## Configuration
### Step 1: Get Cloudonix Credentials
1. Log in to your [Cloudonix Console](https://cockpit.cloudonix.io/onboarding?affiliate=DOGRAH)
2. Find your **Domain ID** (UUID or domain name) and **Bearer Token** (Domain API Key) on the dashboard
3. (Optional) Navigate to your domain's **Applications** and create (or open) the application you'll use with Dograh, then copy its **Application Name**. Skip this if you want Dograh to auto-create the Voice Application for you on save.
### Step 2: Configure in Dograh AI
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Watch the Cloudonix setup video tutorial above for detailed guidance
3. Select **Cloudonix** as your provider
4. Enter your credentials:
* Bearer Token
* Domain ID
* Application Name — *optional*. Leave blank and Dograh will auto-create a Voice Application on save (with the application `url` and CXML runtime already configured) and store its name on this configuration.
5. Click **Save Configuration**
6. Open the configuration you just created and add at least one **phone number** (with country code, in [E.164](https://en.wikipedia.org/wiki/E.164) format — `+` then country code then number, no spaces or dashes, e.g. `+1234567890`). The default caller ID is used for outbound calls.
If Dograh auto-created the Voice Application for you, you still need to
bind your DNIDs to that application in the Cloudonix cockpit (see
[Step 2 of Inbound Calling Setup](#step-2-create-the-voice-application-and-link-dnids)).
The auto-created application is named `dograh-` — its name is
shown on the saved configuration.
### Step 3: Test Your Configuration
1. Create a test workflow
2. Click "Call" to verify connection
3. Check call logs for successful connection
## Inbound Calling Setup
Cloudonix routes inbound calls per **Voice Application** — the webhook URL is set once on the application, and applies to every DNID bound to it. **When you save an inbound workflow on a phone number, Dograh automatically pushes the webhook URL to your Voice Application's `url`** (provided the credentials are correct), so you don't need to set the webhook by hand. If Dograh auto-created the application during configuration save, the `url` is already set and this push is a no-op — you only need to bind your DNIDs to the auto-created application.
### Step 1: Set Up the Inbound Trunk
1. Log in to your [Cloudonix Console](https://cockpit.cloudonix.io)
2. Navigate to **Trunks** → **Create Inbound Trunk** and configure your inbound trunk with your voice service provider
### Step 2: Create the Voice Application and Link DNIDs
1. In the Cloudonix cockpit, select your domain and navigate to **Applications**
2. Create a new application (or open the existing one whose name you configured in Dograh) with these settings:
* **Application Resource Type**: `Remote Application Resource`
* **Application Runtime**: `Cloudonix (CXML)`
* **Application URL**: `https://api.dograh.com/api/v1/telephony/inbound/run`
* **HTTP Method**: `POST`
3. Under **DNID Numbers**, add each phone number (DNID) you want to route through this application
4. Save
The Application URL is what Dograh's auto-push updates in Step 3 — you
can leave it blank during creation and let the auto-push fill it in,
or paste the URL above so the application is usable immediately.
Either works. Self-hosted Dograh deployments use their backend domain
in place of `api.dograh.com`.
### Step 3: Assign an Inbound Workflow to the Phone Number in Dograh
1. Go to **/telephony-configurations** and open your Cloudonix configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
### Step 4: Verify the URL on the Voice Application
1. Open your Cloudonix cockpit and navigate to your domain's **Applications**
2. Open the application whose name you configured in Dograh
3. Confirm:
* **URL** is set to: `https://api.dograh.com/api/v1/telephony/inbound/run`
* **Method** is `POST`
Dograh pushed this URL automatically when you saved the inbound workflow
in Step 3. The same URL is shared across every DNID bound to that
application — Dograh routes each inbound call to the right agent based
on the called number's inbound workflow assignment. If the field is
empty, shows a different URL, or Dograh surfaced a sync warning on
save, the auto-push failed — most often because the Bearer Token,
Domain ID, or Application Name in Dograh is incorrect. Paste the URL
into the field yourself, set the method to `POST`, and save. On
self-hosted Dograh, replace `api.dograh.com` with your backend domain.
### Step 5: Verify Setup
* Ensure your Dograh AI instance is publicly accessible
* Verify any firewalls allow Cloudonix's IP ranges
* Confirm your inbound trunk is active and receiving calls
### Test Inbound Calling
1. Call your configured phone number from another phone
2. Verify your Dograh AI voice agent answers and responds
3. Check call logs in both Dograh AI dashboard and Cloudonix Console
4. Confirm clear two-way audio communication
## Troubleshooting
Ensure phone numbers include country code in E.164 format: `+1234567890`
* Verify Domain ID, Bearer Token, and Application Name are correct
* Check for extra spaces in credentials
* Ensure credentials haven't been disabled or deleted in Cloudonix Console
* Verify WebSocket connection is established
* Check firewall rules for WebSocket traffic
* Ensure audio pipeline is configured correctly
* Verify the DNID is bound to the same Voice Application whose name you
configured in Dograh - Confirm the called number exists in your Dograh
telephony configuration and has an **Inbound workflow** assigned -
After assigning the inbound workflow, confirm Dograh successfully updated
the application's `url` (no warning shown on save) - Verify your inbound
trunk is active and receiving calls
* Confirm the phone number has an **Inbound workflow** assigned in
/telephony-configurations - Verify the Bearer Token in Dograh matches the
one in your Cloudonix Console - Verify WebSocket connection establishes
successfully - Review call logs for error messages
## Best Practices
* Test your configuration with a single call before running campaigns
* Monitor Cloudonix Sessions for usage
* Use a dedicated Voice Application for Dograh so the shared `url` doesn't conflict with other systems
# Custom Telephony Provider
Source: https://docs.dograh.com/integrations/telephony/custom
Build your own telephony provider integration for Dograh AI
## Prerequisites
This guide assumes familiarity with two libraries the provider interface is built on:
* **[Pipecat](https://docs.pipecat.ai)** — the open-source real-time voice/multimodal pipeline framework Dograh's call pipeline is built on. Its [`FastAPIWebsocketTransport`](https://docs.pipecat.ai/server/services/transport/fastapi-websocket) is what streams audio between the telephony provider and Dograh's pipeline — you'll be constructing one of these in your transport factory. If you haven't built a Pipecat transport or frame serializer before, skim their [transports guide](https://docs.pipecat.ai/server/base-classes/transport) first.
* **[Pydantic](https://docs.pydantic.dev)** — used for the request/response schemas that define a provider's stored credentials (`config.py` below). If you're new to Pydantic, its [models documentation](https://docs.pydantic.dev/latest/concepts/models/) covers everything used here (`BaseModel`, `Field`, `Literal` discriminators).
You don't need deep expertise in either — enough to read a `BaseModel` and a Pipecat transport class is sufficient to follow this guide.
## Overview
A telephony provider is implemented as a **self-registering package** under `api/services/telephony/providers//`. The package contributes everything Dograh needs to wire the provider in — the provider class, transport factory, audio config, request/response schemas, optional HTTP routes, and the form metadata used to render its configuration UI — through a single `ProviderSpec` registered at import time.
Adding a new provider should not require touching the factory, the audio config, the API routes module, the run-pipeline module, or the frontend. The only edits outside the provider folder are:
1. One import line in `api/services/telephony/providers/__init__.py`
2. One import line in `api/schemas/telephony_config.py` to add the request/response classes to the `TelephonyConfigRequest` discriminated union
## Provider Package Layout
```
api/services/telephony/providers/your_provider/
├── __init__.py # Builds and registers ProviderSpec
├── config.py # Pydantic Request/Response schemas
├── provider.py # TelephonyProvider subclass
├── transport.py # Pipecat WebSocket transport factory
├── serializers.py # Frame serializer (usually re-exports from pipecat)
├── routes.py # (optional) HTTP webhook/callback handlers
└── strategies.py # (optional) Transfer/hangup strategies
```
Three files are required (`__init__.py`, `config.py`, `provider.py`, `transport.py`). The rest are optional and are discovered automatically when present:
* **`routes.py`** — if the module exists and exports `router: APIRouter`, the routes module is imported lazily and mounted under `/api/v1/telephony` by `api.routes.telephony` via `importlib`. Providers that only stream over WebSocket (e.g. ARI) can omit it.
* **`strategies.py`** — used by transports that need provider-specific call transfer/hangup logic in the frame serializer (e.g. Twilio Conference transfers).
* **`serializers.py`** — typically a re-export from pipecat. Keep the file even when it's a one-line re-export so transport code imports from `.serializers`, giving you an obvious place to drop a custom subclass later.
## The `TelephonyProvider` Interface
Subclass `TelephonyProvider` in `provider.py`:
```python theme={null}
from api.services.telephony.base import (
CallInitiationResult,
NormalizedInboundData,
ProviderSyncResult,
TelephonyProvider,
)
class YourProvider(TelephonyProvider):
PROVIDER_NAME = "your_provider"
WEBHOOK_ENDPOINT = "your-provider-xml" # path under /api/v1/telephony
def __init__(self, config: dict):
self.api_key = config.get("api_key")
self.from_numbers = config.get("from_numbers", [])
# ---------- outbound ----------
async def initiate_call(self, to_number, webhook_url, workflow_run_id=None,
from_number=None, **kwargs) -> CallInitiationResult: ...
async def get_call_status(self, call_id) -> dict: ...
async def get_call_cost(self, call_id) -> dict: ...
async def get_available_phone_numbers(self) -> list[str]: ...
def validate_config(self) -> bool: ...
# ---------- webhooks ----------
async def verify_webhook_signature(self, url, params, signature) -> bool: ...
async def get_webhook_response(self, workflow_id, organization_id, workflow_run_id) -> str: ...
def parse_status_callback(self, data: dict) -> dict: ...
# ---------- websocket ----------
async def handle_websocket(self, websocket, workflow_id, organization_id, workflow_run_id): ...
# ---------- inbound ----------
@classmethod
def can_handle_webhook(cls, webhook_data, headers) -> bool: ...
@staticmethod
def parse_inbound_webhook(webhook_data) -> NormalizedInboundData: ...
@staticmethod
def validate_account_id(config_data, webhook_account_id) -> bool: ...
def normalize_phone_number(self, phone_number: str) -> str: ...
async def verify_inbound_signature(self, url, webhook_data, headers, body="") -> bool: ...
async def start_inbound_stream(self, *, websocket_url, workflow_run_id,
normalized_data, backend_endpoint): ...
@staticmethod
def generate_error_response(error_type, message) -> tuple: ...
# ---------- transfers ----------
async def transfer_call(self, destination, transfer_id, conference_name,
timeout=30, **kwargs) -> dict: ...
def supports_transfers(self) -> bool: ...
# ---------- optional ----------
async def configure_inbound(self, address, webhook_url) -> ProviderSyncResult:
# Default returns ok=True — implement only if your provider supports
# programmatic webhook configuration (e.g. binding a number to a URL
# via API). Used to point inbound numbers at /api/v1/telephony/inbound/run.
return ProviderSyncResult(ok=True)
```
See `api/services/telephony/base.py` for the full docstrings on each method.
`get_webhook_response` and `handle_websocket` receive an `organization_id`, not a
user id. It is the tenant that every workflow and workflow-run lookup must be
scoped by — pass it straight through to `run_pipeline_telephony`. The workflow
owner is deliberately not handed to providers; read it off the workflow row if you
need it for attribution.
## Implementation Guide
### 1. Configuration schemas
Define Pydantic models for the credential payload. The `provider` `Literal` discriminator is what makes the schemas dispatch correctly through the registry's discriminated union.
```python theme={null}
# providers/your_provider/config.py
from typing import List, Literal
from pydantic import BaseModel, Field
class YourProviderConfigurationRequest(BaseModel):
provider: Literal["your_provider"] = Field(default="your_provider")
api_key: str = Field(..., description="Your Provider API key")
api_secret: str = Field(..., description="Your Provider API secret")
from_numbers: List[str] = Field(default_factory=list)
class YourProviderConfigurationResponse(BaseModel):
provider: Literal["your_provider"] = Field(default="your_provider")
api_key: str # masked when returned
api_secret: str # masked when returned
from_numbers: List[str]
```
### 2. Transport factory
Build the Pipecat `FastAPIWebsocketTransport` for accepted WebSockets. Always load credentials through `load_credentials_for_transport` so the right config row is picked when the workflow run carries a `telephony_configuration_id` (multi-config orgs).
```python theme={null}
# providers/your_provider/transport.py
from fastapi import WebSocket
from api.services.pipecat.audio_config import AudioConfig
from api.services.pipecat.audio_mixer import build_audio_out_mixer
from api.services.telephony.factory import load_credentials_for_transport
from pipecat.transports.websocket.fastapi import (
FastAPIWebsocketParams,
FastAPIWebsocketTransport,
)
from .serializers import YourProviderFrameSerializer
async def create_transport(
websocket: WebSocket,
workflow_run_id: int,
audio_config: AudioConfig,
organization_id: int,
*,
vad_config: dict | None = None,
ambient_noise_config: dict | None = None,
telephony_configuration_id: int | None = None,
# provider-specific kwargs (forwarded by run_pipeline_telephony as **transport_kwargs)
stream_id: str,
call_id: str,
):
config = await load_credentials_for_transport(
organization_id, telephony_configuration_id,
expected_provider="your_provider",
)
serializer = YourProviderFrameSerializer(
stream_id=stream_id,
call_id=call_id,
api_key=config["api_key"],
)
mixer = await build_audio_out_mixer(
audio_config.transport_out_sample_rate, ambient_noise_config
)
return FastAPIWebsocketTransport(
websocket=websocket,
params=FastAPIWebsocketParams(
audio_in_enabled=True,
audio_out_enabled=True,
audio_in_sample_rate=audio_config.transport_in_sample_rate,
audio_out_sample_rate=audio_config.transport_out_sample_rate,
audio_out_mixer=mixer,
serializer=serializer,
),
)
```
### 3. Routes (optional)
If your provider POSTs webhooks to Dograh (answer URL, status callbacks, hangup callbacks), expose them through a module-level `router`. The routes are auto-mounted under `/api/v1/telephony`.
```python theme={null}
# providers/your_provider/routes.py
from fastapi import APIRouter, Request
from api.services.telephony.status_processor import (
StatusCallbackRequest,
_process_status_update,
)
router = APIRouter()
@router.post("/your-provider/status-callback/{workflow_run_id}")
async def status_callback(workflow_run_id: int, request: Request):
...
```
Routes are loaded lazily via `importlib` from `api.routes.telephony._mount_provider_routers`, so your route module can freely import other backend services without creating import cycles at provider-class load time.
### 4. Register the `ProviderSpec`
The package's `__init__.py` is where everything comes together:
```python theme={null}
# providers/your_provider/__init__.py
from typing import Any, Dict
from api.services.pipecat.audio_config import AudioConfig
from api.services.telephony.registry import (
ProviderSpec,
ProviderUIField,
ProviderUIMetadata,
register,
)
from .config import YourProviderConfigurationRequest, YourProviderConfigurationResponse
from .provider import YourProvider
from .transport import create_transport
def _config_loader(value: Dict[str, Any]) -> Dict[str, Any]:
"""Normalize the stored credentials dict into the constructor shape."""
return {
"provider": "your_provider",
"api_key": value.get("api_key"),
"api_secret": value.get("api_secret"),
"from_numbers": value.get("from_numbers", []),
}
_AUDIO_CONFIG = AudioConfig(
transport_in_sample_rate=8000,
transport_out_sample_rate=8000,
vad_sample_rate=8000,
pipeline_sample_rate=8000,
buffer_size_seconds=5.0,
)
_UI_METADATA = ProviderUIMetadata(
display_name="Your Provider",
docs_url="https://docs.your-provider.com",
fields=[
ProviderUIField(name="api_key", label="API Key", type="text", sensitive=True),
ProviderUIField(name="api_secret", label="API Secret", type="password", sensitive=True),
ProviderUIField(
name="from_numbers", label="Phone Numbers", type="string-array",
description="E.164-formatted phone numbers used for outbound calls",
),
],
)
SPEC = ProviderSpec(
name="your_provider",
provider_cls=YourProvider,
config_loader=_config_loader,
transport_factory=create_transport,
audio_config=_AUDIO_CONFIG,
config_request_cls=YourProviderConfigurationRequest,
config_response_cls=YourProviderConfigurationResponse,
ui_metadata=_UI_METADATA,
# Credential field that uniquely identifies the provider account.
# Used to disambiguate inbound webhooks across multiple configs of the
# same provider. Empty string for providers without an account-id concept.
account_id_credential_field="api_key",
)
register(SPEC)
```
`ProviderSpec` covers everything downstream code needs:
| Field | Used by |
| - | - |
| `name` | Stored as the discriminator on every `TelephonyConfiguration` row and as the `WorkflowRunMode` value |
| `provider_cls` | `factory.get_default_telephony_provider`, `get_telephony_provider_by_id`, `get_telephony_provider_for_run` |
| `config_loader` | `factory._normalize_with_phone_numbers` (replaces the old if/elif chain) |
| `transport_factory` | `run_pipeline_telephony` |
| `audio_config` | `create_audio_config()` and `run_pipeline_telephony` |
| `config_request_cls` / `config_response_cls` | `TelephonyConfigRequest` discriminated union |
| `ui_metadata` | `GET /api/v1/organizations/telephony-providers/metadata` (drives the form UI) and the `_sensitive_fields` masking helper |
| `account_id_credential_field` | Inbound webhook routing across multiple configs of the same provider |
### 5. Wire the package into the registry import chain
Add one import line to `api/services/telephony/providers/__init__.py`:
```python theme={null}
from api.services.telephony.providers import ( # noqa: F401 -- side effects
ari,
cloudonix,
plivo,
telnyx,
twilio,
vobiz,
vonage,
your_provider, # ← add this
)
```
### 6. Add to the discriminated union
Add one import block to `api/schemas/telephony_config.py` so the request/response classes participate in the `TelephonyConfigRequest` union and the `TelephonyConfigurationResponse` shape:
```python theme={null}
from api.services.telephony.providers.your_provider.config import (
YourProviderConfigurationRequest,
YourProviderConfigurationResponse,
)
TelephonyConfigRequest = Annotated[
Union[
# ...existing entries...
YourProviderConfigurationRequest,
],
Field(discriminator="provider"),
]
class TelephonyConfigurationResponse(BaseModel):
# ...existing entries...
your_provider: Optional[YourProviderConfigurationResponse] = None
```
That's it for backend wiring.
## Frontend
The configuration form is **metadata-driven**. The UI calls `GET /api/v1/organizations/telephony-providers/metadata`, gets back the list of providers and their `ProviderUIField` definitions, and renders each form generically. **No per-provider frontend code is needed** — your `ProviderUIMetadata` declaration is what drives the form.
If you add a new field type that the existing renderer doesn't support (e.g. a file upload), extend the renderer in `ui/src/app/(authenticated)/telephony-configurations/`. The supported `ProviderUIField.type` values today are `text`, `password`, `textarea`, `string-array`, and `number`.
## Audio Format Considerations
Each provider declares its wire format through its `AudioConfig`. Common shapes:
* **Twilio / Plivo**: 8 kHz μ-law, base64-encoded JSON frames
* **Vonage**: 16 kHz Linear PCM as binary frames
* **Asterisk ARI**: 8 kHz Linear PCM via externalMedia
The pipeline sample rate is capped at 16 kHz to satisfy [VAD](/configurations/interruption#what-is-vad), which only accepts 8000 Hz or 16000 Hz; transports handle resampling between the wire format and the pipeline's internal rate.
## Testing
```python theme={null}
# api/tests/telephony/test_your_provider.py
import pytest
from api.services.telephony.providers.your_provider import YourProvider
@pytest.mark.asyncio
async def test_validate_config():
provider = YourProvider({
"api_key": "test_key",
"api_secret": "test_secret",
"from_numbers": ["+1234567890"],
})
assert provider.validate_config() is True
```
For end-to-end testing, save your provider through the telephony-configurations UI and trigger a test call from a workflow.
## Best Practices
1. **Trust the registry** — never import another provider's class directly; resolve through the factory helpers (`get_default_telephony_provider`, `get_telephony_provider_by_id`, etc.).
2. **Sensitive fields** — mark every credential field `sensitive=True` in `ProviderUIMetadata`. The save endpoint masks these on read and preserves the original when the client re-submits a masked value.
3. **Inbound signature verification** — always validate inbound webhook signatures in `verify_inbound_signature`, and fail closed. If your provider signs every webhook (most do), a *missing* signature header means the request did not come from the provider: return `False`, exactly as `providers/twilio/` and `providers/plivo/` do. Never `return True` as a placeholder — these handlers are reachable by anyone who can guess a `workflow_run_id`.
4. **Transports load credentials lazily** — call `load_credentials_for_transport` with the `telephony_configuration_id` from the workflow run. Don't read the org's default config from `transport.py`.
5. **Logging** — use `loguru.logger`.
## Reference Implementations
| Provider | Notable for |
| - | - |
| `providers/twilio/` | Full-featured: outbound, inbound, conference transfers, status callbacks, custom strategies |
| `providers/plivo/` | Recently-added reference; mirrors Twilio's shape with multi-callback signatures |
| `providers/vonage/` | JWT auth, 16 kHz Linear PCM, NCCO responses |
| `providers/cloudonix/` | SIP-based, custom call strategies |
| `providers/telnyx/` | Call-control style: REST-driven inbound answer flow rather than markup response |
| `providers/ari/` | Minimal example — no `routes.py`, no inbound webhook verification, WebSocket-only |
Use ARI as the smallest viable example when your provider doesn't expose HTTP
webhooks, and Twilio as the reference when it does.
# Dograh Managed SIP
Source: https://docs.dograh.com/integrations/telephony/dograh-sip
Connect your own SIP carrier or PBX to the SIP domain Dograh provisions for your organization
## What you get, and what you bring
Every Dograh organization is provisioned with a telephony configuration named
**Dograh Cloudonix SIP**. It gives you a SIP domain that is already connected
to the Dograh voice pipeline — you do not need a Cloudonix account, and there
is nothing to pay for or set up on the Cloudonix side.
What it does **not** include is phone service. Dograh does not sell or resell
phone numbers, and no calls flow until you connect a carrier:
| Dograh provides | You provide |
| - | - |
| A SIP domain and its inbound SIP endpoint | A SIP trunk from your carrier, or your own PBX |
| A fixed outbound origin IP to allow on your side | Phone numbers (DIDs) on that trunk |
| The voice agent runtime on both call directions | Any per-minute charges your carrier bills |
Until you finish the steps below, **Dograh Cloudonix SIP** cannot place a
call. The configuration page shows a setup checklist that tracks exactly
which of these steps are still outstanding.
**Don't have a SIP trunk or PBX?** This page is not the shortest path. Open an
account with a telephony provider instead — see
[Telephony Integration](/integrations/telephony/overview) for the list — and
you can be calling in a few minutes with no SIP configuration at all.
If you would rather bring your own Cloudonix account instead of using the
provisioned domain, follow [Cloudonix Integration](/integrations/telephony/cloudonix)
and add a separate configuration. Self-hosted deployments can also connect their
own Asterisk via [Asterisk ARI](/integrations/telephony/asterisk-ari).
## Prerequisites
* A SIP trunk from a carrier (or a PBX such as Asterisk or FreeSWITCH) that can
accept calls from a fixed IP address and send calls to a hostname
* At least one phone number (DID) on that trunk
* Permission to change the trunk's IP allowlist and routing on the carrier side
## Step 1: Read your SIP endpoints from Dograh
1. Go to **Telephony configurations** and open **Dograh Cloudonix SIP**
2. Expand **SIP connectivity**
3. Choose the **region** closest to your carrier — `Global`, `India`, or `UAE`.
Every value on the panel is region-specific, so set this first
4. Note the two values you will need on the carrier side:
* **Inbound hostname** and **ports** — where your carrier sends calls *to* Dograh
* **Outbound origin IP address** — the address Dograh's calls arrive *from*
## Step 2: Point your carrier at Dograh (inbound)
On your carrier or PBX, route your DIDs to the inbound hostname and port shown
in **SIP connectivity**. UDP and TCP share one port; TLS uses a separate one.
This is the direction that makes your agent answer calls.
## Step 3: Create the outbound trunk (outbound)
Dograh needs to know where to send calls when your agent dials out.
1. On your carrier, allow the **origin IP address** from Step 1 to send SIP
traffic to your trunk. Calls from Dograh are unauthenticated and identified
by source IP, so this step is required
2. Back in Dograh, under **Outbound trunks**, choose **Add trunk** and fill in:
* **Name** — any name you choose. Letters, digits and hyphens only
* **SIP domain** — the hostname of your carrier or PBX, for example
`sip.example.com`. Dograh puts this in both the SIP `To` header and the
Request-URI
* **Region** — the edge Dograh dials your carrier from. The dialog shows
the origin IP that region uses, which is the one to allowlist above
3. Leave **Enabled** on and choose **Save trunk**
You can add more than one trunk — a second carrier, or a failover. Each keeps
its own region, and each lists the numbers that dial out over it.
Without an enabled outbound trunk, outbound calls have nowhere to go. This is
the most common reason a test call from the agent editor fails.
## Step 4: Add your phone numbers
Cloudonix requires a caller ID on **every** outbound call and rejects the call
without one, so this step is required even if you only care about inbound.
1. Choose **Add number** on the trunk whose carrier gave you the number — the
trunk is then filled in for you. (**Add phone number** further down works
too; there you pick the **Outbound trunk** yourself, or leave it unset for a
number that belongs to no trunk.)
2. Enter a number your carrier delivers to you, in
[E.164](https://en.wikipedia.org/wiki/E.164) format — `+`, country code,
then the number, with no spaces or dashes, for example `+14155550123`
3. Mark the number you want outbound calls to present as the **default caller ID**
Add every DID you intend to use. A number your carrier does not own will be
rejected by the carrier even though Dograh accepts it — which is why the trunk
assignment matters as soon as you have more than one carrier. With a single
trunk, Dograh uses it for every number and the field can be left alone.
## Step 5: Route inbound calls to an agent
Only needed if you want to receive calls.
1. In the **Phone numbers** section, edit the number that should be answered
2. Set its **Inbound workflow** to the agent that should pick up
3. Save
Dograh pushes the inbound webhook URL to the Voice Application on the
provisioned domain automatically. You still need the DID bound to that
application — see
[Step 2 of the Cloudonix inbound setup](/integrations/telephony/cloudonix#step-2-create-the-voice-application-and-link-dnids).
## Step 6: Place a test call
1. Open an agent, and make sure it is saved with no validation errors
2. Choose **Phone Call**
3. Select **Dograh Cloudonix SIP**, pick your caller ID, enter a destination
number, and start the call
## Troubleshooting
The setup checklist on the configuration page names the missing step. The
two blocking ones are an enabled trunk (Step 3) and at least one phone
number (Step 4).
* Confirm the origin IP from **SIP connectivity** is allowed on your trunk
* Confirm the **SIP domain** on the trunk resolves and accepts SIP on the
port your carrier expects
* Confirm the caller ID you are dialing from is a number your carrier
actually owns — many carriers reject unowned caller IDs
* Confirm your carrier is sending to the **inbound hostname and port** for
the region selected on the panel, not another region's
* Confirm the called number exists in the **Phone numbers** list and has an
**Inbound workflow** assigned
* Confirm the DID is bound to the Voice Application on the domain
The inbound hostname, ports, and outbound origin IP all change with the
region. Each trunk carries its own **Region**, so edit the trunk to move it,
then update your carrier with the new values.
# Exotel Integration
Source: https://docs.dograh.com/integrations/telephony/exotel
Configure Exotel for voice communication in Dograh AI
# Exotel Integration
Exotel places and receives calls with bidirectional AgentStream media streaming into Dograh agents.
## Walkthrough video
The walkthrough covers, in order:
1. ExoPhone provisioning
2. Dograh telephony configuration
3. Outbound call from a workflow
4. Inbound call landing on a workflow
## Prerequisites
* An [Exotel](https://my.exotel.com/) account with AgentStream / Connect Voice AI enabled
* **KYC verification completed** — mandatory for all Indian accounts post-signup before ExoPhones can carry live voice traffic. Follow [KYC Verification — Indian Accounts](https://docs.exotel.com/business-phone-system/kyc-verification-indian-accounts) (typically \< 30 minutes online).
* Account SID, API Key, and API Token (Dashboard -> Settings -> API Settings)
* A Dograh instance reachable over HTTPS and WSS
* At least one ExoPhone assigned to your account (see provisioning below — don't assume one already exists)
## Supported Exotel front-doors
Per [AgentStream: what to use when](https://docs.exotel.com/exotel-agentstream/agentstream-what-to-use-when), Exotel exposes five entry points to AgentStream (Connect Voice AI, Connect + Flow, ExoML outbound, Flow-Based inbound, ExoML inbound). Dograh's Exotel provider supports **exactly two** of these today:
* **Outbound: Connect Voice AI API** — `POST /v1/Accounts/{sid}/Calls/connect.json` with `StreamUrl` in the API body. No App Bazaar flow involved.
* **Inbound: Flow-Based via Voicebot Applet** — ExoPhone → App Bazaar flow → Voicebot Applet with an HTTPS dynamic-URL pointing at Dograh.
Together these cover the "answer → bot" use case without requiring a gRPC control plane.
### Not currently supported
* **Connect Voice AI with Flow API** (outbound + App Bazaar journey). Dograh's `initiate_call` has no path for dialing with `Url=`, and `transfer_call` raises `NotImplementedError`, so the Connect-applet agent-handoff pattern isn't wired. Operators who need IVR/greeting/DTMF/agent-handoff journeys can trigger Connect + Flow calls directly against Exotel's API from outside Dograh; the flow's Voicebot Applet can still terminate into Dograh's inbound dynamic-URL endpoint.
* **ExoML / Programmable Voice APIs** (gRPC leg events + leg actions), for both outbound and inbound. Would require a gRPC endpoint on Dograh's side and code-owned per-leg control, which is not implemented today.
## ExoPhone provisioning and activation
Provision ExoPhones before configuring Dograh. All calls use HTTP Basic Auth with your API Key / API Token.
1. **List available numbers**
* `GET /v2_beta/Accounts/{account_sid}/AvailablePhoneNumbers/{country}/{type}`
* `{type}` is one of `Landline`, `Mobile`, or `TollFree`.
* Optional filters: `IncomingSMS`, `InRegion`, `Contains`.
* Response fields include `phone_number`, `capabilities`, `country`, `region`, `rental_price`.
* Docs: [Available numbers](https://developer.exotel.com/docs/exophones/api-reference/available-numbers)
2. **Purchase a number**
* `POST /v2_beta/Accounts/{account_sid}/IncomingPhoneNumbers`
* Request body: `PhoneNumber` (required); `VoiceUrl`, `SMSUrl`, `FriendlyName` (all optional).
* Response fields include `sid`, `phone_number`, `friendly_name`, `capabilities`, `country`, `rental_price`, `currency`.
* Docs: [Purchase number](https://developer.exotel.com/docs/exophones/api-reference/purchase-number)
3. **Verify a purchased number**
* `GET /v2_beta/Accounts/{account_sid}/IncomingPhoneNumbers/{exophone_sid}`
* Returns `sid`, `phone_number`, `friendly_name`, `capabilities`, `country`, `region`, `voice_url`, `sms_url`.
* Docs: [Number details](https://developer.exotel.com/docs/exophones/api-reference/number-details)
4. **Assign the number to a Flow**
* In the Exotel dashboard, attach the ExoPhone's Voice URL to an App Bazaar flow (see [Inbound](#inbound) below for the Voicebot Applet wiring).
* **Required before the number can carry inbound traffic**, and part of the setup the reviewer's checklist expects.
> **KYC (mandatory for Indian accounts):** Complete KYC in the Exotel dashboard before expecting purchased numbers to become fully active for voice traffic. A purchased ExoPhone can appear in the API but silently fail on real calls until KYC clears. See [KYC Verification — Indian Accounts](https://docs.exotel.com/business-phone-system/kyc-verification-indian-accounts) for the three-step flow (PAN confirmation → business details → authorised signatory) and the "help me verify" escape hatch if automated checks fail.
## Dograh telephony configuration
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Exotel**
3. Enter:
* **Account SID**
* **API Key** / **API Token** (HTTP Basic auth for Exotel APIs)
* **API Base URL** — default `https://api.in.exotel.com` (India); use `https://api.exotel.com` for other regions
4. Save, then add your ExoPhone under **Phone numbers** in [E.164](https://en.wikipedia.org/wiki/E.164) format (e.g. `+9180XXXXXXXX`)
5. Set an **Inbound workflow** on that phone number in Dograh.
### Programmatic configuration
When wiring Exotel via API or environment rather than the UI, the provider config accepts:
| Field | Purpose |
| - | - |
| `account_sid` | Exotel Account SID |
| `api_key` | Exotel API Key (HTTP Basic username) |
| `api_token` | Exotel API Token (HTTP Basic password) |
| `api_base_url` | `https://api.in.exotel.com` (default) or `https://api.exotel.com`; any other origin is rejected |
| `from_numbers` | List of ExoPhones (E.164) available as `CallerId` |
| `default_from_number` | Default `CallerId` when a workflow does not specify one |
## Outbound
Dograh dials using Exotel Connect Voice AI (the "simplest outbound" pattern — bot is the whole experience, no App Bazaar flow):
`POST {api_base}/v1/Accounts/{AccountSid}/Calls/connect.json`
Request fields:
* `From` — the recipient (callee)
* `CallerId` — the ExoPhone
* `StreamUrl` — Dograh's tokenized WSS endpoint (fixed per call, minted by `ws_auth.build_media_ws_url`)
* `StreamType=bidirectional`
* `StatusCallback` and `StatusCallbackEvents[]=terminal` — **only attached when a `workflow_run_id` is present**
For Indian ExoPhones, Dograh's `_exotel_dial_number()` helper maps the stored E.164 `CallerId` to Exotel's 0-prefixed national form (Exotel's [Connect Voice AI example](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-api) uses `CallerId=0XXXXXXXXXX`). Example: `+917314852338` → `07314852338`. The destination `From` stays in E.164 per the same example (`From=+91XXXXXXXXXX`). Non-India numbers pass through unchanged.
Test from a workflow **Call** button. Confirm two-way audio and that hangup marks the run completed.
Per-call *dynamic* StreamUrl or dashboard-flow-driven outbound would require Connect + Flow or ExoML respectively — see [Supported Exotel front-doors](#supported-exotel-front-doors) for why those aren't implemented here.
## Inbound
Dograh implements **Flow-Based Inbound via the Voicebot Applet's dynamic-URL contract**. Exotel's [Stream and Voicebot Applet reference](https://developer.exotel.com/docs/agentstream/stream-voicebot-applet) explicitly documents the applet's URL parameter as "a WebSocket URL … **or an HTTPS endpoint that dynamically returns the WSS URL**" — that is the contract Dograh implements.
### Wiring
1. In Exotel, open or create an **App Bazaar flow**.
2. Drop a **Voicebot Applet** into the flow.
3. Configure the applet:
* **URL** = `https:///api/v1/telephony/inbound/run`
* **Authentication** = HTTP Basic; username = your Exotel API Key, password = your Exotel API Token (Dograh verifies this in `verify_inbound_signature`).
* **Sample Rate** = `8000`. Dograh's Exotel transport is fixed at 8 kHz today; do not append `?sample-rate=…` — the applet will negotiate a rate the transport doesn't decode and audio breaks.
4. Assign the ExoPhone's Voice URL to this flow in Exotel.
5. In Dograh, assign the matching **inbound workflow** to the number.
### Response contract
On invocation, Dograh validates auth and route mapping, creates a workflow run, and returns JSON:
```json theme={null}
{ "url": "wss:///api/v1/telephony/ws/..." }
```
Exotel's Voicebot Applet then opens the bidirectional WebSocket to that URL.
### Voicebot Applet operational limits
Drawn from Exotel's [applet documentation](https://developer.exotel.com/docs/agentstream/stream-voicebot-applet):
* **Audio format**: raw/slin — 16-bit PCM little-endian, mono, base64-encoded.
* **Sample rate**: `8000` (PSTN). Exotel's applet also supports `16000` and `24000` via `?sample-rate=…`, but Dograh's Exotel transport is hard-coded to 8 kHz today, so leave the sample rate at the default.
* **Chunk size**: min 3.2 KB (100 ms of audio), max 100 KB, must be a multiple of 320 bytes. Smaller chunks risk audio distortion; larger cause timeouts.
* **Custom parameters**: up to 3 key-value pairs appended to the URL (`?param1=value1¶m2=value2`, 256 chars total).
## Status callbacks
Outbound registers a per-run status callback URL:
`/api/v1/telephony/exotel/status-callback/{workflow_run_id}?exotel_auth=...`
The `exotel_auth` token is an HMAC minted from your API token so Exotel's POST does not need an `Authorization` header. The handler also rejects `CallSid` values that do not match the run's stored call id.
Terminal events update the workflow run status.
> **Inbound status callbacks:** the inbound dynamic-URL path currently returns the WSS URL without registering a per-run `StatusCallback` on the Exotel side. If you rely on Exotel-driven terminal events to close inbound runs, be aware that this is a known open item on the PR.
## Phone ownership validation
Exotel does not publish a purpose-built "verify ownership" endpoint. The closest documented mechanism is [Number Details](https://developer.exotel.com/docs/exophones/api-reference/number-details) — but it assumes the number is already on the account and only returns its configuration; it does not confirm ownership by itself.
Dograh therefore validates ownership as a **list + membership check**:
1. Normalize the configured number and generate match-key variants (E.164, national, with/without leading `+`). For Indian numbers stored as `0`-prefixed national forms, an `IN` country hint is applied.
2. Query [`GET /v2_beta/Accounts/{account_sid}/IncomingPhoneNumbers`](https://developer.exotel.com/docs/exophones/api-reference/list-numbers) and iterate the returned ExoPhones.
3. Set-intersect the wanted variants against each entry's `phone_number` / `PhoneNumber` / `FriendlyName` (also India-normalized).
4. Raise `ProviderPhoneNumberLookupError` if no variant matches, with a message pointing the operator back to the Exotel dashboard.
This is the documented workaround given Exotel's public API surface — there is no dedicated ownership-check endpoint to switch to.
## Troubleshooting
* **Connect fails** — verify Account SID, API key/token, base URL region (`api.in.exotel.com` or `api.exotel.com` only), and AgentStream entitlement.
* **No inbound agent** — confirm the ExoPhone has an inbound workflow in Dograh and the Exotel Voicebot Applet points to `/api/v1/telephony/inbound/run`.
* **Auth failed on inbound** — ensure the Voicebot Applet is configured with HTTP Basic (username = API Key, password = API Token) matching the credentials stored in Dograh.
* **`ProviderPhoneNumberLookupError` at config save** — the ExoPhone is not on the Exotel account, the wrong `api_base_url` region is selected, or the number was purchased but has not cleared [KYC](https://docs.exotel.com/business-phone-system/kyc-verification-indian-accounts) yet.
* **One-way audio** — confirm public WSS reachability and firewall/IP rules for Exotel egress; also check the sample rate on the applet matches what your bot pipeline expects.
* **Choppy or dropped audio** — the outgoing chunk size may be out of range; keep chunks ≥ 3.2 KB and ≤ 100 KB, in multiples of 320 bytes. See Exotel's [WSS errors and handling](https://docs.exotel.com/exotel-agentstream/agentstream-wss-errors-and-handling).
* **Status callback 401** — ensure Dograh minted the StatusCallback URL (includes `exotel_auth`) and that the query string is preserved by any intervening proxy.
## References
* [KYC Verification — Indian Accounts](https://docs.exotel.com/business-phone-system/kyc-verification-indian-accounts) (mandatory post-signup step for Indian accounts)
* [ExoPhone — Available numbers](https://developer.exotel.com/docs/exophones/api-reference/available-numbers)
* [ExoPhone — Purchase number](https://developer.exotel.com/docs/exophones/api-reference/purchase-number)
* [ExoPhone — Number details](https://developer.exotel.com/docs/exophones/api-reference/number-details)
* [AgentStream — What to use when](https://docs.exotel.com/exotel-agentstream/agentstream-what-to-use-when) (context for why Dograh uses these two front-doors and not ExoML)
* [AgentStream — Overview & Quickstart](https://docs.exotel.com/exotel-agentstream/overview-and-quickstart)
* [AgentStream — WSS errors and handling](https://docs.exotel.com/exotel-agentstream/agentstream-wss-errors-and-handling)
* [Working with the Stream and Voicebot Applet](https://developer.exotel.com/docs/agentstream/stream-voicebot-applet)
* [Connect Voice AI API](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-api)
* [Exotel Agent-Stream reference implementation](https://github.com/exotel/Agent-Stream)
* [Exotel Agent-Stream echobot](https://github.com/exotel/Agent-Stream-echobot)
# Inbound Calling
Source: https://docs.dograh.com/integrations/telephony/inbound
Configure inbound calling for Twilio, Vonage, Plivo, Telnyx, Cloudonix, Exotel, Vobiz, and Asterisk ARI providers in Dograh AI
## Overview
Dograh AI supports inbound calling across all supported telephony providers. When someone calls your configured phone number, your voice agent automatically answers and handles the interaction. Inbound and outbound calls share the same telephony configuration.
## Supported Providers for Inbound Calls
Industry-leading platform with robust inbound call handling
Application-level inbound routing with high-quality 16kHz audio
Application-level inbound routing on Plivo's global voice cloud
Application-level inbound via Telnyx Call Control API
SIP-based inbound calling with flexible trunk configuration
Connect Voice AI inbound with bidirectional media streaming
Cloud-based telephony with global reach and competitive pricing
Connect to your own Asterisk PBX via the Asterisk REST Interface
## How Inbound Calling Works
Dograh exposes a **single inbound webhook URL** for the whole org:
```
https://api.dograh.com/api/v1/telephony/inbound/run
```
When a call arrives:
1. **Call Received**: Your telephony provider receives the incoming call
2. **Webhook Triggered**: The provider sends a webhook to `/inbound/run`
3. **Org & Agent Resolved**: Dograh identifies the org from the webhook's account credentials, then looks up which agent should answer based on the called number's **Inbound workflow** assignment in your telephony configuration
4. **Audio Streaming**: Real-time audio streams between caller and agent via WebSocket
5. **Conversation Handled**: Your voice agent manages the entire conversation
You don't construct per-workflow URLs anymore — the routing is done by the called number's inbound workflow assignment in Dograh.
## Configuration in Dograh AI
Setting up inbound calling takes three steps:
### Step 1: Configure Telephony Provider
The telephony configuration for inbound calling is **identical** to outbound calling. Add (or open) a configuration at **/telephony-configurations** following your provider's documentation:
* [Twilio Configuration](/integrations/telephony/twilio#configuration)
* [Vonage Configuration](/integrations/telephony/vonage#configuration)
* [Plivo Configuration](/integrations/telephony/plivo#configuration)
* [Telnyx Configuration](/integrations/telephony/telnyx#configuration)
* [Cloudonix Configuration](/integrations/telephony/cloudonix#configuration)
* [Exotel Configuration](/integrations/telephony/exotel#configuration)
* [Vobiz Configuration](/integrations/telephony/vobiz#configuration)
* [Asterisk ARI Configuration](/integrations/telephony/asterisk-ari#part-2-create-the-configuration-in-dograh)
### Step 2: Assign an Inbound Workflow to the Phone Number
1. Go to **/telephony-configurations** and open your configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
This is what tells Dograh which agent to run when a call comes in for that number.
### Step 3: Point Your Provider at `/inbound/run`
Each provider needs its inbound webhook (Twilio Voice URL / Vonage Answer URL / etc.) set to the global Dograh endpoint:
```
https://api.dograh.com/api/v1/telephony/inbound/run
```
If you're self-hosting Dograh, replace `api.dograh.com` with your own domain.
The exact place to set this varies by provider — follow the provider-specific instructions:
* [Twilio Inbound Setup](/integrations/telephony/twilio#inbound-calling-setup)
* [Vonage Inbound Setup](/integrations/telephony/vonage#inbound-calling-setup)
* [Plivo Inbound Setup](/integrations/telephony/plivo#inbound-calling-setup)
* [Telnyx Inbound Setup](/integrations/telephony/telnyx#inbound-calling-setup)
* [Cloudonix Inbound Setup](/integrations/telephony/cloudonix#inbound-calling-setup)
* [Exotel Inbound Setup](/integrations/telephony/exotel#inbound-calling-setup)
* [Vobiz Inbound Setup](/integrations/telephony/vobiz#inbound-calling-setup)
* [Asterisk ARI Inbound Setup](/integrations/telephony/asterisk-ari#inbound-calling)
## Testing Inbound Calls
After completing both the Dograh AI configuration and provider-specific setup:
1. **Test Call**: Call your configured phone number from another phone
2. **Verify Connection**: Ensure your voice agent answers and responds appropriately
3. **Check Logs**: Monitor call logs in both Dograh AI and your provider's dashboard
4. **Audio Quality**: Verify clear two-way audio communication
## Troubleshooting Common Issues
* Verify webhook URLs are correctly configured at your provider
* Check that webhook endpoints are publicly accessible
* Ensure phone numbers are properly linked to your application
* Confirm your voice agent workflow is properly configured
* Check webhook signature validation is working
* Verify WebSocket connections are established successfully
* **Error**: "Configuration error: This phone number is configured for a different telephony provider"
* **Solution**: Check your dashboard settings and update your webhook URL configuration to match the correct provider
* Ensure the phone number is configured for the same provider in both Dograh AI and your telephony console
* **Error**: "Workflow not found"
* **Solution**: Open the called number in **/telephony-configurations** and confirm an **Inbound workflow** is assigned. Without an assignment, Dograh has no agent to route the call to.
* Verify the assigned workflow still exists in your dashboard
* **Error**: "Authentication error: Account credentials do not match"
* **Solution**: Verify your account SID/credentials configuration in Dograh AI matches your telephony provider settings
* Re-enter your provider credentials in the telephony configuration page
* **Error**: "Phone number not configured: This number is not set up for inbound calls"
* **Solution**: Add this phone number under **Phone numbers** in your telephony configuration at **/telephony-configurations**
* Ensure the phone number is also linked to your provider account
* **Error**: "Security error: Webhook signature validation failed"
* **Solution**: Verify your auth token configuration in Dograh AI matches your provider settings
* Ensure webhook requests are coming from your telephony provider's verified IP ranges
* Check that your webhook URL is accessible and responding correctly
* **Error**: "Service temporarily unavailable: Your account has exceeded usage limits"
* **Solution**: Contact your administrator or upgrade your plan to continue receiving calls
* Check your provider account billing and usage limits
* **Error**: "Authentication failed"
* **Solution**: Check your webhook URL configuration and ensure your telephony provider settings match your dashboard configuration
* Verify all credentials are entered correctly and haven't expired
* Test the webhook URL is publicly accessible from your provider's servers
* Check network bandwidth and latency from provider to Dograh AI
* Verify audio pipeline configuration matches provider requirements
* Review WebSocket connection stability
## Best Practices
* **Single Configuration**: Use the same telephony configuration for both inbound and outbound calls
* **Testing**: Always test inbound calling after configuration changes
* **Monitoring**: Monitor both Dograh AI logs and provider dashboards for call analytics
* **Security**: Ensure webhook signature verification is enabled for security
## Next Steps
1. Choose your telephony provider and complete the basic configuration
2. Follow the provider-specific setup instructions
3. Assign an inbound workflow to each phone number that should accept calls
4. Test your inbound calling setup
# Telephony Integration
Source: https://docs.dograh.com/integrations/telephony/overview
Connect voice agents with telephony providers for inbound and outbound calls
## Overview
Dograh AI's telephony integration system provides a unified interface for connecting with various telephony providers. The same configuration powers both outbound calls (initiated from Dograh) and inbound calls (received on a phone number you own).
Dograh does not sell phone numbers or minutes. The phone service is always
yours — there are two ways to bring it.
## Two ways to connect
Open an account with Twilio, Plivo, Telnyx, Vonage or Vobiz, paste the
credentials into Dograh, and call using their numbers. **Start here if you
don't already run phone infrastructure.** Pick your provider from the list
below.
Already have a SIP trunk or a PBX? Point it at Dograh and keep your existing
carrier and numbers. Every organization is provisioned with a
**Dograh Cloudonix SIP** connection for exactly this; self-hosted
deployments can use [Asterisk ARI](/integrations/telephony/asterisk-ari)
instead.
## Supported Providers
Industry-leading cloud communications platform with global reach
High-quality voice with 16kHz audio and excellent international coverage
Cloud communications platform with programmable voice and global PSTN reach
SIP-based telephony with flexible trunk configuration
India-focused cloud telephony with Connect Voice AI streaming
Cloud-based telephony with global reach and competitive pricing
Connect to your own Asterisk PBX via the Asterisk REST Interface
Build your own telephony provider integration
## Configuration
All telephony providers are configured from a single page in Dograh:
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select your provider
3. Enter your credentials and save
4. Open the new configuration and add at least one **phone number**
5. (Optional) Assign an **Inbound workflow** to a phone number to enable inbound calling
A single org can hold multiple configurations (for example, separate Twilio sub-accounts) and multiple phone numbers per configuration. Mark one configuration as the **default outbound** to use it for test calls and campaigns by default.
## Common Features
The telephony integration in Dograh AI supports:
* **Outbound Calls**: Initiate calls to any phone number from a workflow or campaign
* **Inbound Calls**: Route incoming calls to the right voice agent — see the [Inbound Calling guide](/integrations/telephony/inbound)
* **Call Transfer**: Transfer an in-progress call to a human or another number (provider-dependent)
* **Call Status Tracking**: Monitor call lifecycle events (initiated, ringing, answered, completed, failed)
* **WebSocket Audio Streaming**: Real-time, bidirectional audio between caller and agent
* **Webhook Signature Verification**: Inbound webhooks are verified against the matched configuration's credentials
## Inbound Calling
Inbound calls use a **single org-wide webhook URL**:
```
https://api.dograh.com/api/v1/telephony/inbound/run
```
Dograh resolves the org from the webhook's account credentials and the agent from the called number's **Inbound workflow** assignment. See [Inbound Calling](/integrations/telephony/inbound) for the full setup.
## Troubleshooting
* Verify credentials are correctly configured
* Check phone number format ([E.164](https://en.wikipedia.org/wiki/E.164) — the international standard: a `+`, then country code, then the number, no spaces or dashes, e.g. `+1234567890`)
* Ensure webhook URLs are publicly accessible
* Review provider-specific error logs
* Check network bandwidth and latency
* Verify audio codec compatibility
* Review WebSocket connection stability
* Confirm auth tokens / API secrets match between provider and Dograh configuration
* Verify webhook URL matches exactly (including the `/inbound/run` path)
* Check for proxy or load balancer modifications
## Next Steps
* [Set up your first telephony provider](/integrations/telephony/twilio)
* [Configure inbound calling](/integrations/telephony/inbound)
* [Build a custom provider integration](/integrations/telephony/custom)
# Plivo Integration
Source: https://docs.dograh.com/integrations/telephony/plivo
Configure Plivo for voice communication in Dograh AI
## Overview
Plivo is a cloud communications platform that provides global voice and messaging APIs. Dograh AI's Plivo integration uses Plivo's XML and WebSocket streaming to power your voice agents.
## Prerequisites
Before setting up Plivo integration, you'll need:
* A [Plivo account](https://www.plivo.com/)
* Auth ID and Auth Token from your Plivo Console
* A Plivo **Application** with Voice capability (optional — leave the field blank in Dograh and we'll auto-create one for you on save, with the `answer_url` pre-set)
* At least one Plivo phone number
* Dograh AI instance running and accessible
## Configuration
### Step 1: Get Plivo Credentials
1. Log in to your [Plivo Console](https://console.plivo.com/)
2. Find your **Auth ID** and **Auth Token** on the dashboard
3. (Optional) Navigate to **Voice** → **Applications** and create (or open) the application you'll use with Dograh, then copy its **Application ID** (a UUID). Skip this if you want Dograh to auto-create the application for you on save.
4. Navigate to **Phone Numbers** → **Your Numbers** and copy the numbers you plan to use
### Step 2: Configure in Dograh AI
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Plivo** as your provider
3. Enter your credentials:
* Auth ID
* Auth Token
* Application ID — *optional*. Leave blank and Dograh will auto-create a Plivo Application on save (with the `answer_url` already configured) and store its Application ID on this configuration.
4. Click **Save Configuration**
5. Open the configuration you just created and add at least one **phone number** (with country code, in [E.164](https://en.wikipedia.org/wiki/E.164) format — `+` then country code then number, no spaces or dashes, e.g. `+1234567890`). The default caller ID is used for outbound calls.
If Dograh auto-created the Plivo Application for you, you still need to
link your Plivo numbers to that application in the Plivo Console under
**Phone Numbers** → **Your Numbers**. The auto-created application is
named `dograh-` — its Application ID is shown on the saved
configuration.
### Step 3: Test Your Configuration
1. Create a test workflow
2. Click "Call" to verify connection
3. Check call logs for successful connection
## Inbound Calling Setup
Plivo numbers don't carry an `answer_url` directly — the URL lives on a Plivo **Application**, and each number is linked to one application. **When you save an inbound workflow on a phone number, Dograh automatically pushes the webhook URL to your Plivo Application's `answer_url`** (provided the credentials are correct). You don't need to set the webhook by hand. If Dograh auto-created the application during configuration save, the `answer_url` is already set and this push is a no-op.
### Step 1: Link the Phone Number to Your Plivo Application
1. Go to **Phone Numbers** → **Your Numbers** in the [Plivo Console](https://console.plivo.com/)
2. Open the number you want to use for inbound calls
3. Set its **Application** to the same application whose ID you configured in Dograh
4. Save
### Step 2: Assign an Inbound Workflow to the Phone Number in Dograh
1. Go to **/telephony-configurations** and open your Plivo configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
### Step 3: Verify the Answer URL on the Plivo Application
1. Open your Plivo Application in the [Plivo Console](https://console.plivo.com/) under **Voice** → **Applications**
2. Confirm:
* **Answer URL** is set to: `https://api.dograh.com/api/v1/telephony/inbound/run`
* **Answer Method** is `POST`
Dograh pushed this URL automatically when you saved the inbound workflow
in Step 2. The same URL is shared across every number linked to that
application — Dograh routes each inbound call to the right agent based
on the called number's inbound workflow assignment. If the field is
empty, shows a different URL, or Dograh surfaced a sync warning on
save, the auto-push failed — most often because the Auth ID/Token or
Application ID in Dograh is incorrect. Paste the URL into the field
yourself, set the method to `POST`, and save the application. On
self-hosted Dograh, replace `api.dograh.com` with your backend domain.
### Step 4: Verify Setup
* Ensure your Dograh AI instance is publicly accessible
* Verify any firewalls allow Plivo's IP ranges
### Test Inbound Calling
1. Call your configured Plivo phone number from another phone
2. Verify your Dograh AI voice agent answers and responds
3. Check call logs in both Dograh AI dashboard and Plivo Console
## Troubleshooting
Ensure phone numbers include country code in E.164 format: `+1234567890`
* Verify Auth ID and Auth Token are correct - Check for extra spaces in
credentials - Ensure credentials haven't been regenerated in Plivo Console
* Confirm your Auth Token matches exactly - Verify the webhook URL matches
what Plivo sends - Check if you're behind a proxy that modifies requests
* Verify WebSocket connection is established - Check firewall rules for
WebSocket traffic - Ensure audio pipeline is configured correctly
* Verify the phone number is linked to the same Plivo Application whose
ID you configured in Dograh - Confirm the called number exists in your
Dograh telephony configuration and has an **Inbound workflow** assigned
* After assigning the inbound workflow, confirm Dograh successfully
updated the application's `answer_url` (no warning shown on save) -
Verify Dograh AI instance is running and responding
* Confirm the phone number has an **Inbound workflow** assigned in
/telephony-configurations - Check webhook signature validation is working
(Auth Token in Dograh matches Plivo Console) - Verify WebSocket connection
establishes successfully - Review call logs for error messages
## Best Practices
* Test your configuration with a single call before running campaigns
* Monitor Plivo Console for usage and billing
* Use a dedicated Plivo Application for Dograh so the shared `answer_url` doesn't conflict with other systems
# Telnyx Integration
Source: https://docs.dograh.com/integrations/telephony/telnyx
Configure Telnyx for voice communication in Dograh AI
## Overview
Telnyx is a cloud communications platform that provides programmable voice via its Call Control API. Dograh AI's Telnyx integration uses Call Control plus WebSocket media streaming to power your voice agents.
## Prerequisites
Before setting up Telnyx integration, you'll need:
* A [Telnyx account](https://telnyx.com/)
* An **API Key** from the Telnyx Mission Control Portal
* A **Call Control Application** (optional — leave the field blank in Dograh and we'll auto-create one for you on save, with the inbound webhook URL pre-set)
* At least one Telnyx phone number assigned to that Call Control Application
* Dograh AI instance running and accessible
## Configuration
### Step 1: Get Telnyx Credentials
1. Log in to the [Telnyx Mission Control Portal](https://portal.telnyx.com/)
2. Navigate to **API Keys** and create (or copy) an API Key
3. (Optional) Navigate to **Call Control** → **Applications** and create (or open) the application you'll use with Dograh, then copy its **Connection ID** (Call Control App ID). Skip this if you want Dograh to auto-create the Call Control Application for you on save.
4. Navigate to **Numbers** → **My Numbers** and assign your phone numbers to the Call Control Application you'll use with Dograh (if you're letting Dograh auto-create the application, do this after saving the configuration in Step 2)
### Step 2: Configure in Dograh AI
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Telnyx** as your provider
3. Enter your credentials:
* API Key
* Call Control App ID (Connection ID) — *optional*. Leave blank and Dograh will auto-create a Call Control Application on save (with the inbound webhook URL already configured) and store its Connection ID on this configuration.
4. Click **Save Configuration**
5. Open the configuration you just created and add at least one **phone number** (with country code, in [E.164](https://en.wikipedia.org/wiki/E.164) format — `+` then country code then number, no spaces or dashes, e.g. `+1234567890`). The default caller ID is used for outbound calls.
If Dograh auto-created the Call Control Application for you, you still
need to assign your Telnyx numbers to that application in the Telnyx
Portal under **Numbers** → **My Numbers**. The auto-created application
is named `dograh-` — its Connection ID is shown on the saved
configuration.
### Step 3: Test Your Configuration
1. Create a test workflow
2. Click "Call" to verify connection
3. Check call logs for successful connection
## Inbound Calling Setup
Telnyx delivers inbound webhooks at the **Call Control Application** level — the webhook URL is configured once on the application, and applies to every number assigned to it. **When you save an inbound workflow on a phone number, Dograh automatically pushes the webhook URL to your Call Control Application's `webhook_event_url`** (provided the credentials are correct). If Dograh auto-created the application during configuration save, the webhook URL is already set and this step is a no-op.
### Step 1: Assign an Inbound Workflow to the Phone Number
1. Go to **/telephony-configurations** and open your Telnyx configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
### Step 2: Verify the Webhook URL on the Call Control Application
1. Go to **Call Control** → **Applications** in the Telnyx Portal
2. Open the application whose Connection ID you configured in Dograh
3. In **Webhook Settings**, confirm:
* **Webhook URL** is set to: `https://api.dograh.com/api/v1/telephony/inbound/run`
* **HTTP Method** is `POST`
4. Make sure the phone numbers you want to use for inbound are assigned to this application
Dograh pushed this URL automatically when you saved the inbound workflow
in Step 1. The same URL is shared across every number on the Call
Control Application — Dograh matches the inbound call to the right
agent using the called number's inbound workflow assignment. If the
field is empty, shows a different URL, or Dograh surfaced a sync
warning on save, the auto-push failed — most often because the API
Key or Connection ID in Dograh is incorrect. Paste the URL into the
field yourself, set the method to `POST`, and save. On self-hosted
Dograh, replace `api.dograh.com` with your backend domain.
### Step 3: Verify Setup
* Ensure your Dograh AI instance is publicly accessible
* Verify any firewalls allow Telnyx's IP ranges
### Test Inbound Calling
1. Call your configured Telnyx phone number from another phone
2. Verify your Dograh AI voice agent answers and responds
3. Check call logs in both Dograh AI dashboard and Telnyx Portal
## Troubleshooting
Ensure phone numbers include country code in E.164 format: `+1234567890`
* Verify the API Key is correct and active - Check for extra spaces in the
key - Ensure the key hasn't been revoked in the Telnyx Portal
* Telnyx signs webhooks with Ed25519 - confirm the public key on the
application hasn't changed - Verify the webhook URL matches what Telnyx
sends - Check if you're behind a proxy that modifies request bodies
* Verify WebSocket connection is established - Check firewall rules for
WebSocket traffic - Ensure audio pipeline is configured correctly
* Verify the Call Control Application's webhook URL is set to
`https://api.dograh.com/api/v1/telephony/inbound/run` - Ensure the webhook
URL is publicly accessible from the internet - Confirm the called number
is assigned to the same Call Control Application whose Connection ID is
configured in Dograh - Confirm the called number exists in your Dograh
telephony configuration and has an **Inbound workflow** assigned - Verify
Dograh AI instance is running and responding
* Confirm the phone number has an **Inbound workflow** assigned in
/telephony-configurations - Verify the API Key matches the one stored in
your Dograh telephony configuration - Verify WebSocket connection
establishes successfully - Review call logs for error messages
## Best Practices
* Test your configuration with a single call before running campaigns
* Monitor the Telnyx Portal for usage and billing
* Use a dedicated Call Control Application for Dograh so the shared webhook URL doesn't conflict with other systems
# Twilio Integration
Source: https://docs.dograh.com/integrations/telephony/twilio
Configure Twilio for voice communication in Dograh AI
## Overview
Twilio is a cloud communications platform that enables voice calling, messaging, and video capabilities. Dograh AI's Twilio integration provides seamless connectivity for your voice agents.
## Prerequisites
Before setting up Twilio integration, you'll need:
* A [Twilio account](https://www.twilio.com/try-twilio)
* Account SID and Auth Token from your Twilio Console
* At least one Twilio phone number
* Dograh AI instance running and accessible
## Configuration
### Step 1: Get Twilio Credentials
1. Log in to your [Twilio Console](https://console.twilio.com/)
2. Find your **Account SID** and **Auth Token** on the dashboard
3. Navigate to **Phone Numbers** → **Manage** → **Active Numbers**
4. Copy your phone number(s)
### Step 2: Configure in Dograh AI
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Twilio** as your provider
3. Enter your credentials:
* Account SID
* Auth Token
4. Click **Save Configuration**
5. Open the configuration you just created and add at least one **phone number** (with country code, in [E.164](https://en.wikipedia.org/wiki/E.164) format — `+` then country code then number, no spaces or dashes, e.g. `+1234567890`). The default caller ID is used for outbound calls.
### Step 3: Test Your Configuration
1. Create a test workflow
2. Click "Call" to verify connection
3. Check call logs for successful connection
## Inbound Calling Setup
Inbound routing is driven by the phone number itself — there is a **single webhook URL** for the entire org, and Dograh resolves which agent to run from the called number's assigned inbound workflow. **When you save an inbound workflow on a phone number, Dograh automatically pushes the webhook URL to that number's `VoiceUrl` in your Twilio account** (provided the credentials are correct and the number belongs to that account).
### Step 1: Assign an Inbound Workflow to the Phone Number
1. Go to **/telephony-configurations** and open your Twilio configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
### Step 2: Verify the Webhook URL in Twilio Console
1. Go to **Phone Numbers** → **Manage** → **Active Numbers** in Twilio Console
2. Click the phone number you assigned an inbound workflow to in Step 1
3. In the **Voice Configuration** section, confirm:
* **Webhook** is set to: `https://api.dograh.com/api/v1/telephony/inbound/run`
* **HTTP Method** is `POST`
Dograh pushed this URL automatically when you saved the inbound workflow
in Step 1. If the field is empty, shows a different URL, or Dograh
surfaced a sync warning, the auto-push failed — most often because the
credentials are incorrect or the number isn't owned by this Twilio
account. Paste the URL into the field yourself, set the method to
`POST`, and click **Save Configuration**. On self-hosted Dograh,
replace `api.dograh.com` with your backend domain.
### Step 3: Verify Setup
* Ensure your Dograh AI instance is publicly accessible
* Verify any firewalls allow Twilio's IP ranges
### Test Inbound Calling
1. Call your configured Twilio phone number from another phone
2. Verify your Dograh AI voice agent answers and responds
3. Check call logs in both Dograh AI dashboard and Twilio Console
## Troubleshooting
Ensure phone numbers include country code in E.164 format: `+1234567890`
* Verify Account SID and Auth Token are correct - Check for extra spaces in
credentials - Ensure credentials haven't been regenerated in Twilio Console
* Confirm your Auth Token matches exactly - Verify the webhook URL matches
what Twilio sends - Check if you're behind a proxy that modifies requests
* Verify WebSocket connection is established - Check firewall rules for
WebSocket traffic - Ensure audio pipeline is configured correctly
* Verify the Twilio number's webhook is set to
`https://api.dograh.com/api/v1/telephony/inbound/run` - Ensure the webhook
URL is publicly accessible from the internet - Confirm the called number
exists in your Dograh telephony configuration and has an **Inbound
workflow** assigned - Verify Dograh AI instance is running and responding
* Confirm the phone number has an **Inbound workflow** assigned in
/telephony-configurations - Check webhook signature validation is working
(Auth Token in Dograh matches Twilio Console) - Verify WebSocket connection
establishes successfully - Review call logs for error messages
## Best Practices
* Test your configuration with a single call before running campaigns
* Monitor Twilio Console for usage and billing
# VICIdial Integration
Source: https://docs.dograh.com/integrations/telephony/vicidial
Connect Dograh to VICIdial through Asterisk ARI for call control, in-group transfers, and lead updates
## Overview
Use this integration when VICIdial owns the customer call and connects Dograh
to it through Asterisk. Dograh uses Asterisk ARI for audio and VICIdial APIs for
operations that must affect the original customer leg:
* Hang up the customer call when the agent ends the conversation
* Transfer the customer to a VICIdial in-group
* Update selected VICIdial lead fields from gathered workflow context
VICIdial credentials and lead-field controls are an advanced organization
feature. Context Mapping in Transfer Call tools is generally available and can
also route calls for telephony providers that are not connected to VICIdial.
## Prerequisites
Before configuring VICIdial, ensure that:
* Asterisk ARI is connected to Dograh as described in the
[Asterisk ARI integration](./asterisk-ari)
* VICIdial sends the customer call to the Asterisk dialplan that enters the
Dograh Stasis application
* You have VICIdial agent API credentials with permission to hang up and
perform in-group transfers
* If workflows will update leads, you also have non-agent API credentials with
permission to call `update_lead`
## Enable the organization feature
1. Open **Platform Settings** by going to [https://app.dograh.com/settings](https://app.dograh.com/settings)
2. Enable **External PBX integrations**.
This switch reveals VICIdial credentials in Asterisk telephony configurations
and lead capture/update settings in workflow configurations. Disabling it hides
those VICIdial-specific controls without deleting their saved values; Context
Mapping remains available in Transfer Call tools.
## Configure VICIdial credentials
1. Open **Telephony Configurations**.
2. Create or edit an **Asterisk ARI** configuration.
3. In **External PBX**, set **External PBX Type** to **VICIdial**.
4. Configure the agent API:
* **Agent API URL**: normally ends in `/agc/api.php`
* **Agent API User**
* **Agent API Password**
5. To update leads, configure the non-agent API:
* **Non-Agent API URL**: normally ends in `/vicidial/non_agent_api.php`
* **Non-Agent API User**
* **Non-Agent API Password**
6. Save the configuration.
The non-agent API is optional when no lead updates are required. If it is used,
its URL, user, and password must all be present. Credentials are stored with the
telephony configuration and are returned masked by the Dograh API.
## Pass VICIdial call identity to Asterisk
Dograh needs VICIdial's call identity to control the original customer leg.
Configure VICIdial or the connecting dialplan to preserve these SIP headers on
the call that enters the Dograh Stasis application:
| Header | Purpose |
| - | - |
| `X-VICIDIAL-callerid` | Call-control identifier used by the agent API |
| `X-VICIDIAL-user` | Remote-agent user used by the agent API |
| `X-VICIDIAL-lead_id` | Lead identifier used by optional lead updates |
| `X-VICIDIAL-campaign_id` | **(Optional)** Campaign context retained with the call |
| `X-VICIDIAL-ingroup_id` | **(Optional)** Original in-group available to transfer fallback routing |
Hangup and transfer require the call-control identifier and remote-agent user.
Lead updates additionally require the lead ID. The source in-group header is
only required when a transfer mapping uses the `source` fallback.
### Pass lead data in additional headers
Configure VICIdial or the dialplan to attach lead fields as headers, for example
`X-VICIDIAL-first_name`, `X-VICIDIAL-city`, or `X-VICIDIAL-vendor_lead_code`.
Headers with empty values are ignored.
Each lead field is then read individually during call setup, so Dograh captures
only the fields you list:
1. Open the workflow's **General Settings**.
2. Under **External PBX Field Updates**, find **Lead Fields To Capture**.
3. Add one entry per field, named **without** the `X-VICIDIAL-` prefix — enter
`first_name` to capture `X-VICIDIAL-first_name`.
Every field you add costs one extra request to Asterisk while the call is being
set up, which delays the agent's first response. List only the fields your
prompts actually use.
#### Discover which fields your PBX sends
Each inbound call logs the available field names automatically:
```text theme={null}
[ARI org=7] Available vicidial lead fields on channel 1712...: ['callerid',
'city', 'first_name', 'lead_id', 'user'] — add the ones you need under the
workflow's Lead Fields To Capture setting
```
Listing the names costs a single request no matter how many headers are present,
because it reads names only — never values.
Captured fields are available to workflow prompts through template variables
named after the header suffix:
```text theme={null}
Hello {{external_pbx_call.lead.first_name | there}}, I see you're calling
from {{external_pbx_call.lead.city}}.
```
## Configure in-group transfers
In-group mappings select a VICIdial destination from information gathered by
the agent during a call.
1. Open or create a **Transfer Call** tool.
2. Select **Context Mapping** as the destination source.
3. In **Rule 1**, enter a context path, such as `qualified`,
`gathered_context.qualified`, or `initial_context.qualified`. An unprefixed
path checks gathered context first and then initial context.
4. Add one route for each expected value and its destination in-group ID.
5. Optionally select **Add routing rule** to check another context path when the
rule above it does not match, and use the arrows to order the rules.
6. Optionally configure a fallback in-group. Enter `source` to return the
caller to the original in-group captured from the SIP headers.
7. Save the tool and attach it to the workflow.
Rules are evaluated top to bottom. The first rule whose context value matches
one of its routes wins, so put the most specific routing first; the fallback
applies only after every rule misses. The match is case-insensitive. Dograh
performs final variable extraction before resolving the mapping. If no rule or
fallback matches, the transfer fails and the agent can recover instead of
silently hanging up the caller.
## Configure lead field mappings
Lead field mappings copy selected values from gathered context into the
VICIdial lead before transfer or hangup.
1. Open the workflow's **Configurations** dialog.
2. In **External PBX Field Updates**, add a mapping.
3. Enter the gathered-context path and the destination VICIdial lead field.
4. Add any additional mappings and save the workflow configuration.
For example, map `extracted_variables.customer_state` to `state`. A context
path can address a direct gathered-context value or a nested value. Mappings
whose source value is absent are skipped.
The VICIdial adapter ignores mappings that target the API control parameters
`source`, `user`, `pass`, `function`, and `lead_id`. The non-agent API
configuration is required for mappings to be applied. A lead-update failure
does not prevent Dograh from attempting the requested transfer or hangup.
## Configure disposition mapping
Dograh can record a completed call's outcome on the VICIdial lead's `status`
column, so a call Dograh handled appears in the same reports as one an agent
handled. First,
[configure the business outcomes for each workflow](../../developer/call-dispositions#configure-outcomes-for-a-workflow).
You can then map those raw outcomes to the status codes used by your VICIdial
deployment:
1. Open **Platform Settings** and enable **Disposition mapping** under
Preferences.
2. Select **Configure mapping**.
3. Choose the VICIdial status code you want recorded for each Dograh
disposition. A disposition left as itself is sent unchanged.
4. Select **Add disposition** for any custom workflow outcome or end-call tool
reason that is not already listed, then save your preferences.
Use the status codes already configured in your VICIdial deployment. Dograh
does not prescribe the mapping, and VICIdial exposes no API for discovering
those codes automatically. Consult your own VICIdial status configuration or
report when choosing each target value.
The mapped code is written to `gathered_context.mapped_call_disposition`, which
is what VICIdial write-back, the disposition run filter, and reports read. To
send it in a webhook, reference that field explicitly. The automatically added
top-level webhook field contains the raw `gathered_context.call_disposition`.
See [Call Dispositions](../../developer/call-dispositions#understand-the-stored-fields)
for the complete data model.
Map your do-not-call outcomes to `DNC` to have Dograh add the customer's
number to VICIdial's `SYSTEM_INTERNAL` do-not-call list. A lead status stops
the current list being redialed; only the DNC entry travels with the number.
A disposition that does not contain one to six letters, numbers, underscores,
or hyphens is not written at all. VICIdial keeps whatever it recorded for the
call, usually `RAXFER`. An unmapped disposition that already fits this format is
written unchanged. Dograh never truncates a longer outcome into a
plausible-looking but incorrect status.
## Runtime behavior
When the workflow transfers the call, Dograh resolves the configured in-group,
applies available lead updates, asks VICIdial to transfer the customer leg, and
then closes only the local Asterisk media leg.
When the conversation ends without a transfer, Dograh persists final gathered
context, applies available lead updates, asks VICIdial to hang up the customer
leg, and then closes the local Asterisk leg.
## Troubleshooting
* **VICIdial settings are not visible:** enable **External PBX integrations**
in Platform Settings.
* **The local media leg ends but the customer remains connected:** confirm that
`X-VICIDIAL-callerid` and `X-VICIDIAL-user` reach Asterisk and that the agent
API credentials can control the call.
* **A transfer cannot resolve a destination:** inspect the workflow's final
gathered context, the configured context path, and the mapping values.
* **The `source` fallback fails:** ensure `X-VICIDIAL-ingroup_id` is present on
the inbound call.
* **Lead fields are unchanged:** confirm `X-VICIDIAL-lead_id`, the non-agent API
credentials, and the destination field names.
* **Leads keep VICIdial's own status:** the call's disposition has no entry
under **Disposition mapping**, or its mapped code is longer than six
characters. Both are reported in the call logs, naming the disposition to
map.
# Vobiz Integration
Source: https://docs.dograh.com/integrations/telephony/vobiz
Configure Vobiz for voice communication in Dograh AI
## Overview
Vobiz is a cloud-based telephony service provider that offers global reach with competitive pricing. Dograh AI's Vobiz integration provides reliable voice connectivity for your conversational AI agents.
## Prerequisites
Before setting up Vobiz integration, you'll need:
* A [Vobiz account](https://vobiz.ai)
* Auth ID and Auth Token from your Vobiz dashboard
* A Vobiz **Application** (optional — leave the field blank in Dograh and we'll auto-create one for you on save, with the `answer_url` pre-set)
* At least one Vobiz phone number
* Dograh AI instance running and accessible
## Configuration
### Step 1: Get Vobiz Credentials
1. Log in to your Vobiz dashboard
2. Find your **Auth ID** and generate an **Auth Token** for API access
3. (Optional) Navigate to the **Applications** section and create (or open) the application you'll use with Dograh, then copy its **Application ID**. Skip this if you want Dograh to auto-create the application for you on save.
4. Navigate to **Phone Numbers** and copy the numbers you plan to use
### Step 2: Configure in Dograh AI
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Vobiz** as your provider
3. Enter your credentials:
* Auth ID
* Auth Token
* Application ID — *optional*. Leave blank and Dograh will auto-create a Vobiz Application on save (with the `answer_url` already configured) and store its Application ID on this configuration.
4. Click **Save Configuration**
5. Open the configuration you just created and add at least one **phone number** (with country code, in [E.164](https://en.wikipedia.org/wiki/E.164) format — `+` then country code then number, no spaces or dashes, e.g. `+1234567890`). The default caller ID is used for outbound calls.
If Dograh auto-created the Vobiz Application for you, you still need to
attach your Vobiz numbers to that application in the Vobiz Console under
**Applications**. The auto-created application is named
`dograh-` — its Application ID is shown on the saved
configuration.
### Step 3: Test Your Configuration
1. Create a test workflow
2. Click "Call" to verify connection
3. Check call logs for successful connection
## Inbound Calling Setup
Vobiz numbers don't carry an `answer_url` directly — the URL lives on a Vobiz **Application**, and each number is linked to one application. **When you save an inbound workflow on a phone number, Dograh automatically pushes the webhook URL to your Vobiz Application's `answer_url`** (provided the credentials are correct). If Dograh auto-created the application during configuration save, the `answer_url` is already set and this push is a no-op.
### Step 1: Link the Phone Number to Your Vobiz Application
1. Log in to your Vobiz Console and open the **Applications** section
2. Edit the application whose ID you configured in Dograh
3. Attach the phone number you want to use for inbound calls
4. Save
### Step 2: Assign an Inbound Workflow to the Phone Number in Dograh
1. Go to **/telephony-configurations** and open your Vobiz configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
### Step 3: Verify the Answer URL on the Vobiz Application
1. Open your Vobiz Console and navigate to **Applications**
2. Open the application whose ID you configured in Dograh
3. Confirm:
* **Answer URL** is set to: `https://api.dograh.com/api/v1/telephony/inbound/run`
* **Answer Method** is `POST`
Dograh pushed this URL automatically when you saved the inbound workflow
in Step 2. The same URL is shared across every number linked to that
application — Dograh routes each inbound call to the right agent based
on the called number's inbound workflow assignment. If the field is
empty, shows a different URL, or Dograh surfaced a sync warning on
save, the auto-push failed — most often because the Auth ID/Token or
Application ID in Dograh is incorrect. Paste the URL into the field
yourself, set the method to `POST`, and save. On self-hosted Dograh,
replace `api.dograh.com` with your backend domain.
### Step 4: Verify Setup
* Ensure your Dograh AI instance is publicly accessible
* Verify any firewalls allow Vobiz's webhook requests
### Test Inbound Calling
1. Call your configured Vobiz phone number from another phone
2. Verify your Dograh AI voice agent answers and responds
3. Check call logs in both Dograh AI dashboard and Vobiz dashboard
## Troubleshooting
Ensure phone numbers include country code in E.164 format: `+1234567890`
* Verify Auth ID and Auth Token are correct
* Check for extra spaces in credentials
* Ensure credentials haven't been regenerated in Vobiz dashboard
* Confirm your Auth Token matches exactly
* Verify the webhook URL is accessible from Vobiz servers
* Check if you're behind a proxy that modifies requests
* Verify WebSocket connection is established
* Check firewall rules for WebSocket traffic
* Ensure audio pipeline is configured correctly
* Verify the phone number is linked to the same Vobiz Application whose
ID you configured in Dograh - Confirm the called number exists in your
Dograh telephony configuration and has an **Inbound workflow** assigned
* After assigning the inbound workflow, confirm Dograh successfully
updated the application's `answer_url` (no warning shown on save) -
Verify Dograh AI instance is running and responding
* Confirm the phone number has an **Inbound workflow** assigned in
/telephony-configurations - Check webhook signature validation is working
(Auth Token in Dograh matches Vobiz dashboard) - Verify WebSocket connection
establishes successfully - Review call logs for error messages
## Best Practices
* Test your configuration with a single call before running campaigns
* Monitor Vobiz dashboard for usage and billing
* Keep your Auth Token secure and rotate it periodically
* Use a dedicated Vobiz Application for Dograh so the shared `answer_url` doesn't conflict with other systems
# Vonage Integration
Source: https://docs.dograh.com/integrations/telephony/vonage
Configure Vonage (Nexmo) for voice communication in Dograh AI
## Overview
Vonage (formerly Nexmo) is a cloud communications platform that provides global voice, messaging, and video capabilities. Dograh AI's Vonage integration enables high-quality voice interactions with your agents using Vonage's robust infrastructure.
## Prerequisites
Before setting up Vonage integration, you'll need:
* A [Vonage account](https://www.vonage.com/communications-apis/)
* Vonage Application with Voice capability enabled
* Application ID and Private Key from your Vonage Dashboard
* API Key and API Secret from your Vonage Dashboard
* Signature Secret from your Vonage Dashboard
* At least one Vonage phone number linked to the application
* Dograh AI instance running and accessible
## Configuration
### Step 1: Create Vonage Application
1. Log in to your [Vonage Dashboard](https://dashboard.nexmo.com/)
2. Navigate to **Applications** → **Create a new application**
3. Enable **Voice** capability
4. Generate a private key (save this securely - you'll need it)
5. Note your **Application ID**
### Step 2: Get API Credentials
1. Find your **API Key** and **API Secret** in the dashboard under **API Settings**
2. Copy your **Signature Secret** from the same Vonage account. Dograh uses it
to verify [Vonage signed webhooks](https://developer.vonage.com/en/getting-started/concepts/webhooks).
3. Navigate to **Numbers** → **Your Numbers**
4. Copy your phone number(s)
5. Link each inbound number to your Voice application
### Step 3: Configure in Dograh AI
1. Navigate to **/telephony-configurations** and click **Add configuration**
2. Select **Vonage** as your provider
3. Enter your credentials:
* Application ID
* Private Key (entire key including BEGIN/END lines)
* API Key
* API Secret
* Signature Secret
4. Click **Save Configuration**
5. Open the configuration you just created and add at least one **phone number** (without `+` prefix, e.g. `14155551234`). The default caller ID is used for outbound calls.
### Step 4: Test Your Configuration
1. Create a test workflow
2. Click "Call" to verify connection
3. Check call logs for successful connection
## Inbound Calling Setup
Vonage routes inbound Voice API calls through a Voice application. The application owns the Answer URL and Event URL, and the phone number must be linked to that application. Dograh routes the call to the right agent based on the called number's inbound workflow assignment inside Dograh.
When you save an inbound workflow on a phone number, Dograh updates the configured Vonage application's Voice webhooks and enables signed callbacks, provided the API Key, API Secret, and Application ID are correct and a Signature Secret is configured.
Linking the phone number to the Voice application is required. If the number
is not linked, Vonage will not call Dograh's Answer URL, and you may hear a
busy or disconnected tone without seeing any Dograh application logs.
### Step 1: Link Phone Numbers to Your Vonage Application
1. Open the [Vonage Dashboard](https://dashboard.nexmo.com/)
2. Go to **Numbers** → **Your Numbers**
3. Open each number you want to use for inbound calls
4. Set the number's Voice application to the same Vonage Application whose ID you configured in Dograh
Vonage's Numbers API describes this as the number's `app_id`: the application that handles inbound traffic to that number. See the [Numbers API reference](https://developer.vonage.com/en/api/numbers).
### Step 2: Assign an Inbound Workflow to the Phone Number in Dograh
1. Go to **/telephony-configurations** and open your Vonage configuration
2. In the **Phone numbers** section, edit the number that should receive inbound calls
3. Set its **Inbound workflow** to the agent that should answer
4. Save
### Step 3: Verify the Answer URL on the Vonage Application
1. Open your Vonage Application in the [Vonage Dashboard](https://dashboard.nexmo.com/)
2. Under **Capabilities** → **Voice**, confirm:
* **Answer URL** is set to: `https:///api/v1/telephony/inbound/run`
* **HTTP Method** is `POST`
* **Event URL** is set to: `https:///api/v1/telephony/vonage/events`
* **Event Method** is `POST`
* **Signed callbacks** are enabled
Dograh pushes these settings automatically when you save the inbound
workflow in Step 2. If the fields are empty, show a different URL, or
Dograh surfaced a sync warning on save, check the API Key, API Secret,
Application ID, and Signature Secret in Dograh, then save the inbound
workflow again. On self-hosted Dograh, the backend domain must be publicly
reachable by Vonage.
### Step 4: Verify Setup
* Ensure your Dograh AI instance is publicly accessible
* Verify your public backend URL is reachable from the internet
* Use the [Vonage logs](https://dashboard.nexmo.com/logs) or Voice Inspector to confirm Vonage is sending the Answer webhook to Dograh
### Test Inbound Calling
1. Call your configured Vonage phone number from another phone
2. Verify your Dograh AI voice agent answers and responds
3. Check call logs in both Dograh AI dashboard and Vonage Dashboard
## Audio Quality Optimization
Vonage uses higher quality audio (16kHz) which provides:
* Clearer voice reproduction
* Better speech recognition accuracy
* More natural-sounding TTS output
* Reduced transcription errors
## Troubleshooting
* Ensure "Voice" is enabled in your Vonage application
* Verify the application ID matches your configuration
* Check that your phone numbers are linked to the application
* Verify your private key is complete (including BEGIN/END lines)
* Check the Application ID is correct
* Ensure the private key hasn't been regenerated in Vonage Dashboard
* Verify the Signature Secret in Dograh matches the Vonage account's signature secret
* Ensure signed callbacks are enabled on the Vonage Voice application
* Check that the webhook request includes an `Authorization` header
* Confirm the application belongs to the same API Key saved in Dograh
* Remove the '+' prefix for Vonage (use `14155551234` not `+14155551234`)
* Ensure numbers are in [E.164](https://en.wikipedia.org/wiki/E.164) format (country code + number, no spaces or dashes) without the '+'
* Verify numbers are active in your Vonage account
* Verify WebSocket connection is established
* Check audio pipeline is configured for 16kHz PCM
* Monitor WebSocket for binary audio frames
* Review [VAD (Voice Activity Detection)](/configurations/interruption#what-is-vad) behavior — this is what detects when the caller starts and stops speaking
* Check WebSocket heartbeat/ping-pong frames
* Verify no timeout in load balancer/proxy
* Monitor for audio pipeline errors
* Review max call duration settings
* Verify the Vonage application's Answer URL is set to `https:///api/v1/telephony/inbound/run`
* Ensure the Answer URL is publicly accessible
* Confirm the called number is linked to the correct Vonage application
* Confirm the called number exists in your Dograh telephony configuration and has an **Inbound workflow** assigned
* Confirm the Vonage number is linked to the Voice application configured in Dograh
* Confirm the Voice application has the Dograh Answer URL set with method `POST`
* Confirm your backend domain is public; Vonage cannot call `localhost`
* Check Vonage logs for Answer URL delivery errors before debugging Dograh
* Confirm the phone number has an **Inbound workflow** assigned in /telephony-configurations
* Verify API Key matches the one stored in your Dograh telephony configuration (used to identify the org from the inbound webhook)
* Verify WebSocket connection establishes successfully
* Review call logs for error messages
## Best Practices
* **Security**: Private keys are stored securely in the database
* **Signed callbacks**: Keep Vonage signed callbacks enabled and keep the Signature Secret in Dograh up to date
* **Testing**: Use Vonage Voice Inspector for debugging call issues
* **Numbers**: Configure multiple numbers for redundancy
* **Monitoring**: Set up alerts in Vonage Dashboard for failures
* **Cost Management**: Monitor usage to control costs
## Cost Considerations
Vonage pricing includes:
* Per-minute charges for calls
* Phone number rental fees
* Optional features (recording, transcription)
Check [Vonage pricing](https://www.vonage.com/communications-apis/voice/pricing/) for current rates.
## Next Steps
* Test your Vonage integration with a simple workflow
* Review [VAD and interruption behavior](/configurations/interruption) for your workflow's nodes
* Set up monitoring and alerts
* Explore advanced features like call recording
# Webhooks and Callbacks
Source: https://docs.dograh.com/integrations/telephony/webhooks
How Dograh AI handles telephony webhooks and audio streaming
## Overview
Dograh AI uses webhooks to communicate with telephony providers for call events and audio streaming. For outbound calls, Dograh builds these URLs and hands them to the provider when it dials — you never configure them by hand. For inbound calls, you point your provider at a single dispatcher URL; see [Inbound Calls](/integrations/telephony/inbound).
Every webhook path lives under `/api/v1/telephony`.
## Webhook Types
### 1. Answer Webhook
When an outbound call connects, the provider requests instructions. The path is provider-specific, and Dograh appends the routing parameters as a query string:
```
?workflow_id={workflow_id}&workflow_run_id={workflow_run_id}&organization_id={organization_id}
```
| Provider | Method | Path |
| - | - | - |
| Twilio, Cloudonix | `POST` | `/twiml` |
| Plivo | `POST` | `/plivo-xml` |
| Vobiz | `POST` | `/vobiz-xml` |
| Vonage | `GET` | `/ncco` |
Telnyx, Asterisk ARI, and Exotel outbound have no answer webhook. Telnyx is call-control style — Dograh POSTs the stream and event URLs to Telnyx's API instead of returning markup. ARI streams over a WebSocket only. Exotel Connect Voice AI attaches `StreamUrl` at dial time.
```xml theme={null}
```
```json theme={null}
[
{
"action": "connect",
"endpoint": [{
"type": "websocket",
"uri": "wss://your-domain/api/v1/telephony/ws/123/11/789",
"content-type": "audio/l16;rate=16000"
}]
}
]
```
Here `123` is the workflow, `11` the organization, and `789` the workflow run.
### 2. Status Callbacks
Receive call lifecycle events. Each is keyed on the workflow run:
| Provider | Path |
| - | - |
| Twilio | `/twilio/status-callback/{workflow_run_id}` |
| Plivo | `/plivo/hangup-callback/{workflow_run_id}`, `/plivo/ring-callback/{workflow_run_id}` |
| Vobiz | `/vobiz/hangup-callback/{workflow_run_id}`, `/vobiz/ring-callback/{workflow_run_id}` |
| Vonage | `/vonage/events/{workflow_run_id}` |
| Telnyx | `/telnyx/events/{workflow_run_id}` |
| Cloudonix | `/cloudonix/status-callback/{workflow_run_id}`, `/cloudonix/cdr` |
| Exotel | `/exotel/status-callback/{workflow_run_id}` |
Providers report their own vocabulary; Dograh normalizes it into a common set of states:
* `initiated` - Call request received
* `ringing` - Call is ringing
* `in-progress` - Call is connected and streaming
* `answered` - Call was answered
* `completed` - Call ended normally
* `busy` - Line was busy
* `no-answer` - Call not answered
* `canceled` - Call was canceled before connecting
* `failed` - Call failed
* `error` - Provider reported an error
A status Dograh does not recognize is passed through unchanged rather than dropped.
### 3. WebSocket Audio Stream
Real-time audio streaming for voice interaction.
**Endpoint**: `/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}`
When [`TELEPHONY_WS_TOKEN_SECRET`](/developer/environment-variables#telephony) is set, the URL Dograh hands the carrier gains a fourth segment holding the HMAC signature: `/api/v1/telephony/ws/{workflow_id}/{organization_id}/{workflow_run_id}/{token}`. It is a path segment rather than a query parameter because carriers do not reliably forward query strings — Twilio strips them from `` entirely.
Asterisk ARI instead connects to `/api/v1/telephony/ws/ari` and passes the same three values as query parameters, plus `token` when a secret is configured — see [Asterisk ARI](/integrations/telephony/asterisk-ari).
The Telnyx call-events webhook is the other exception: it takes `?token=` in the query string, because Telnyx does preserve query strings on webhook POSTs (the restriction above is specific to carrier media-stream URLs). The route verifies it before any database lookup, so an unauthenticated caller cannot tell an existing run from a missing one by status code or by timing.
The `organization_id` segment is the tenant that owns the workflow. Dograh scopes every workflow and workflow-run lookup by it, so a run belonging to one organization can never be served under another's id.
**Audio Formats**:
* **Twilio / Plivo / Vobiz**: 8kHz μ-law (MULAW), Base64-encoded in JSON messages
* **Vonage**: 16kHz Linear PCM, Binary frames
* **Asterisk ARI**: 8kHz Linear PCM via externalMedia
## How It Works
Dograh AI automatically:
1. Constructs webhook URLs based on your deployment
2. Passes them to the telephony provider when initiating calls
3. Verifies webhook signatures for security:
* **Twilio**: HMAC-SHA1 signature validation
* **Plivo / Vobiz**: HMAC-SHA256 signature validation
* **Vonage**: JWT token verification
4. Processes status updates to track call lifecycle
5. Manages WebSocket connections for audio streaming
6. Handles provider-specific audio formats and protocols
Signature verification is implemented per provider in `verify_inbound_signature`. If you are adding a provider, see [Custom Telephony Provider](/integrations/telephony/custom).
## Local Development
For local development, use the built-in Cloudflare tunnel:
```yaml theme={null}
# docker-compose.yml includes:
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate --url http://api:8000
```
The tunnel URL is automatically detected and used for webhooks.
## Troubleshooting
* Verify your domain/tunnel URL is publicly accessible
* Check firewall rules allow incoming HTTPS traffic
* Test with `curl` from external network
* Providers sign the full URL, query string included — a proxy that rewrites or reorders query parameters will invalidate the signature
* Confirm the backend's public URL matches what the provider was given, including scheme and port
* Check WebSocket upgrade headers are preserved
* Verify no timeout on load balancer/proxy
* Monitor for memory/CPU constraints
* Verify workflow\_run\_id is included in URL
* Check provider console for webhook errors
* Review webhook retry logs
# Tuner Integration
Source: https://docs.dograh.com/integrations/tuner
Connect Dograh to Tuner — the observability, simulation, and testing layer for voice
## Overview
The Tuner integration node automatically sends your completed call data (transcript, metadata, and call outcomes) to Tuner after each call finishes. This lets you monitor agent performance, run evaluations, and track quality trends without any custom code.
Once the node is configured, you can also let Tuner **call your agent** with generated caller scenarios and score how it handles them — see [Simulations](#simulations) below.
## Prerequisites
* A [Tuner account](https://app.usetuner.ai) with an active workspace
* A Dograh voice agent workflow
## Setup
### 1. Create an agent in Tuner
Log in to [Tuner](https://app.usetuner.ai) and create a new agent. When configuring the agent, set the **Provider** to **Dograh**. Enter your **Agent Name** and choose the **Call Direction** (Inbound or Outbound) for your setup. The **Agent Remote ID** is auto-generated after you create the agent.
### 2. Gather your Tuner credentials
You'll need three values from your Tuner account:
| Credential | Where to find it |
| - | - |
| **Agent ID** | Agent Settings → Agent Connection → Agent Remote ID |
| **Workspace ID** | Workspace Settings → General Settings → Workspace ID |
| **API Key** | Workspace Settings → Tuner API Key |
**Agent Remote ID** — open Agent Settings for your agent:
**Workspace ID and API Key** — open Workspace Settings:
### 3. Add the Tuner node to your workflow
In your Dograh workflow editor, click **Add node** and scroll to the **Integrations** section. Select **Tuner**.
Scroll to **Integrations** and select **Tuner**:
The node appears on your canvas with a **Not configured** badge:
### 4. Configure the node
Click on the Tuner node and fill in the following fields:
* **Tuner Agent ID** — The Agent Remote ID from Tuner
* **Tuner Workspace ID** — Your numeric workspace ID
* **Tuner API Key** — Your workspace API key
* **Enabled** — Toggle on to activate the export
Click **Save**, then **Publish** your workflow.
### 5. Verify the connection
Make a test call through your agent. Once the call completes, check the **Call Logs** tab in your Tuner agent dashboard. The call should appear within a few moments.
## Simulations
Everything above sends *real* calls to Tuner. Simulations run the other direction: Tuner places a call **into** your agent over SIP, plays the part of a caller following a generated scenario, and scores the result. Your agent answers exactly as it would for a customer.
Dograh does not speak SIP itself — Cloudonix does it on Dograh's behalf. So the address Tuner dials belongs to Cloudonix, and what you set up below is a route through it to one specific agent.
Simulations use the **inbound** path, which runs on the Cloudonix SIP domain
Dograh provisions for every organization. You do not need a SIP carrier or a
phone number — unlike outbound calling, which requires your own trunk. See
[Dograh Managed SIP](/integrations/telephony/dograh-sip).
### Before you start
* The Tuner node configured and enabled on your workflow, as set up above
* In Tuner, the same agent with its **Call Direction** set to **Inbound**
* An agent that already works — test it from the agent editor first
An inbound simulation only attaches to its scenario when the Tuner agent's
call direction is **Inbound**. Set to Outbound, Tuner matches the returned
call on a different field and files it as ordinary traffic instead.
### 1. Add an extension for simulations
Go to **Telephony configurations** and open **Dograh Cloudonix SIP**. Under **Phone numbers**, choose **Add phone number** and enter a short extension such as `9001`.
You do not need a real phone number. That field accepts SIP URIs and plain extensions alongside PSTN numbers, and an extension is the simplest thing to point a simulation at. Dograh registers it with Cloudonix for you.
### 2. Point the extension at your agent
Edit the extension you just added, set its **Inbound workflow** to the agent that should answer, and save. This is the step that tells Cloudonix where to route calls for that extension.
### 3. Publish the workflow
In the workflow editor, choose **Save**, then **Publish**.
Inbound calls always run the **published** version of a workflow, never the
draft. If the Tuner node is only in your draft, calls will connect and the
conversation will happen normally — but nothing will ever reach Tuner, and
no error appears anywhere.
### 4. Build the SIP URI
Expand **SIP connectivity** on the configuration page, choose your **region**, and copy the **inbound hostname**. Combine it with the extension from step 1:
```text theme={null}
sip:9001@a1b2c3d4-0000-0000-0000-9f8e7d6c5b4a.sip.cloudonix.net
└─┬─┘ └──────────────────────┬───────────────────────────┘
extension inbound hostname
```
| Part | Where to get it | Notes |
| - | - | - |
| **Extension** | Phone numbers — the entry from step 1 | Must match exactly. This decides which agent answers. |
| **Inbound hostname** | SIP connectivity → Hostname | Unique to your organization, and changes with the region. |
| **Port** | SIP connectivity → Ports | Omit only when it is `5060`; otherwise required. |
Set the region **before** copying anything — every value on the panel is region-specific, and the port changes with it:
| Region | Hostname ends with | Port | Include the port? |
| - | - | - | - |
| Global | `.sip.cloudonix.net` | `5060` | Optional — it is the default |
| India | `.in.dimi.tel` | `9060` | Required |
| UAE | `.uae.dimi.tel` | `9081` | Required |
On India or UAE, leaving the port off sends the call to port 5060, where
nothing is listening — and the failure looks like an unreachable agent
rather than a wrong address. Add `:9060` or `:9081` to the URI.
Your inbound hostname accepts calls without a password — being unguessable is
what protects it. Treat it as sensitive and share it only with systems that
need to call your agents.
### 5. Save the URI in Tuner
In Tuner, open **Agent Settings → SIP Settings**, paste the address into **SIP URI**, and choose **Verify & Save**. Tuner immediately places a short test call, so this single action checks the whole path.
Leave **Username** and **Password** empty — Dograh's inbound endpoint does not use SIP authentication.
**Known issue in Tuner.** With both auth fields blank, verification currently
fails with "endpoint did not answer". Entering any non-empty values — for
example `tuner` and `tuner` — works around it. Dograh never checks them, so
the values are irrelevant. Clear both fields once Tuner ships the fix.
### 6. Run a simulation
Start a run from Tuner's **Simulations** tab. Each scenario becomes a real call into your agent, and completed calls appear in that table with transcript, latency, and evaluation results.
### How calls are matched to scenarios
Nothing to configure — this is automatic. Cloudonix reports the caller's SIP call id to Dograh, and Dograh includes it when it exports the finished call. Tuner uses it to attach the call to the scenario that placed it. Without it, the call would still be ingested, just filed as ordinary production traffic.
## Disabling the integration
To temporarily stop exporting calls to Tuner, open the Tuner node configuration and toggle **Enabled** off. Your credentials are preserved — toggle it back on anytime to resume.
## Troubleshooting
| Issue | Solution |
| - | - |
| Calls not appearing in Tuner | Verify all three credentials are correct with no extra whitespace |
| Node shows "Not configured" | Open the node and fill in Agent ID, Workspace ID, and API Key |
| Workflow not sending data | Make sure the workflow is published, not just saved as a draft |
| Wrong agent in Tuner | Confirm the Tuner agent's Provider is set to **Dograh** |
### Simulation issues
| Issue | Solution |
| - | - |
| `603 Declined` | Cloudonix found the route but could not fetch call instructions from Dograh. On a self-hosted deployment, check that your backend is publicly reachable — see below |
| "Endpoint did not answer" on Verify & Save | With blank auth fields this is the [known Tuner issue](#5-save-the-uri-in-tuner). Otherwise confirm the extension exists, has an inbound workflow assigned, and that the region's port is in the URI |
| Agent answers but nothing reaches Tuner | The Tuner node is not in the **published** version of the workflow. Publish and try again |
| Calls land in Call Logs instead of Simulations | Confirm the Tuner agent's **Call Direction** is **Inbound** |
| A `422` logged after Verify & Save | Harmless. The verification call lasts about a second with no conversation, so there is no transcript for Tuner to accept |
### Self-hosted deployments
Cloudonix must reach your backend over the public internet twice per call: once for the webhook when the call arrives, and again for the audio stream. A backend on `localhost` receives neither.
The address is recorded by Cloudonix when you save the inbound workflow, so whenever your public address changes you must do **both** of these:
* Update `BACKEND_API_ENDPOINT` and restart the backend
* **Re-save the inbound workflow on the extension**, which pushes the new URL to Cloudonix
Skipping the second step leaves Cloudonix calling the old address, and every call fails with `603`. A tunnel with a fixed hostname avoids it entirely.
## Learn more
* [Tuner](https://usetuner.ai) — The observability, simulation, and testing layer for voice
* [Tuner documentation](https://docs.usetuner.ai) — Complete Tuner platform docs
# Build an agent
Source: https://docs.dograh.com/sdks/build-an-agent
Assemble a Dograh voice agent programmatically with the SDK and save it as a draft
The SDK mirrors the node-and-edge model of the [Voice Agent Builder](/voice-agent/introduction). You create a `Workflow`, add nodes (`startCall`, `agentNode`, `endCall`, …) with `add()`, connect them with `edge()`, and persist the result via `save_workflow`.
## Prerequisites
* A Dograh [API key](/configurations/api-keys) exported as `DOGRAH_API_KEY`
* An existing agent ID to save drafts against (create one in the Dograh UI or via [`POST /api/v1/workflow/create`](/api-reference/agents/create-from-template))
## Build and save
The example below builds a three-node loan-qualification agent and saves it as a **new draft version** on an existing agent. Your published agent keeps serving calls until you explicitly publish the draft.
```python Python theme={null}
from dograh_sdk import DograhClient, Workflow
with DograhClient(api_key="YOUR_API_KEY") as client:
wf = Workflow(client=client, name="loan_qualification")
greeting = wf.add(
type="startCall",
name="greeting",
prompt="You are Sarah from Acme Loans. Greet the caller warmly.",
)
qualify = wf.add(
type="agentNode",
name="qualify",
prompt="Ask about loan amount, purpose, and monthly income.",
)
done = wf.add(
type="endCall",
name="done",
prompt="Thank them and end the call politely.",
)
wf.edge(greeting, qualify, label="interested", condition="Caller wants to continue.")
wf.edge(qualify, done, label="done", condition="All qualification questions answered.")
client.save_workflow(workflow_id=123, workflow=wf)
```
```typescript TypeScript theme={null}
import { DograhClient, Workflow } from "@dograh/sdk";
const client = new DograhClient({ apiKey: "YOUR_API_KEY" });
const wf = new Workflow({ client, name: "loan_qualification" });
const greeting = await wf.add({
type: "startCall",
name: "greeting",
prompt: "You are Sarah from Acme Loans. Greet the caller warmly.",
});
const qualify = await wf.add({
type: "agentNode",
name: "qualify",
prompt: "Ask about loan amount, purpose, and monthly income.",
});
const done = await wf.add({
type: "endCall",
name: "done",
prompt: "Thank them and end the call politely.",
});
wf.edge(greeting, qualify, { label: "interested", condition: "Caller wants to continue." });
wf.edge(qualify, done, { label: "done", condition: "All qualification questions answered." });
await client.saveWorkflow(123, wf);
```
## Edit an existing agent
Load an agent into an editable `Workflow`, mutate it, then save:
```python Python theme={null}
wf = client.load_workflow(workflow_id=123)
wf.name = "loan_qualification_v2"
client.save_workflow(workflow_id=123, workflow=wf)
```
```typescript TypeScript theme={null}
const wf = await client.loadWorkflow(123);
wf.name = "loan_qualification_v2";
await client.saveWorkflow(123, wf);
```
## Discover node types
Each node's `type` string and required fields come from the backend's node-spec catalog. Fetch it at runtime to validate what you can build:
```python Python theme={null}
types = client.list_node_types()
for spec in types.node_types:
print(spec.name, [p.name for p in spec.properties])
```
```typescript TypeScript theme={null}
const types = await client.listNodeTypes();
for (const spec of types.node_types) {
console.log(spec.name, spec.properties.map(p => p.name));
}
```
For a full description of each node type and its fields, see the [Nodes](/voice-agent/start-call) section of the Voice Agent Builder docs.
# Introduction
Source: https://docs.dograh.com/sdks/introduction
Build and operate Dograh voice AI agents programmatically from Python or TypeScript
Dograh ships official SDKs for **Python** and **TypeScript** that wrap the Dograh REST API and the workflow builder. Use them to create or edit agents, place outbound calls, and inspect runs from your own code.
* **Python** — [`dograh-sdk`](https://pypi.org/project/dograh-sdk/) on PyPI
* **TypeScript** — [`@dograh/sdk`](https://www.npmjs.com/package/@dograh/sdk) on npm
Both packages are generated from the same backend OpenAPI spec, so the method surface is equivalent — only the naming convention differs (`snake_case` in Python, `camelCase` in TypeScript).
## Install
```bash Python theme={null}
pip install dograh-sdk
```
```bash TypeScript theme={null}
npm install @dograh/sdk
```
## Authenticate
Generate an API key at [`/api-keys`](https://app.dograh.com/api-keys) (or `http://localhost:3010/api-keys` for self-hosted). See [API Keys](/configurations/api-keys) for details.
Both SDKs read the API key from the `DOGRAH_API_KEY` environment variable by default, and the base URL from `DOGRAH_API_URL` (defaults to `http://localhost:8000`). You can also pass them explicitly.
```python Python theme={null}
from dograh_sdk import DograhClient
client = DograhClient(
base_url="https://app.dograh.com",
api_key="YOUR_API_KEY",
)
```
```typescript TypeScript theme={null}
import { DograhClient } from "@dograh/sdk";
const client = new DograhClient({
baseUrl: "https://app.dograh.com",
apiKey: "YOUR_API_KEY",
});
```
For self-hosted deployments, swap `baseUrl` for your backend URL (e.g. `http://localhost:8000`).
## Quick tour
List the agents in your workspace:
```python Python theme={null}
workflows = client.list_workflows()
for wf in workflows:
print(wf.id, wf.name)
```
```typescript TypeScript theme={null}
const workflows = await client.listWorkflows();
for (const wf of workflows) {
console.log(wf.id, wf.name);
}
```
## Next steps
* [Build an agent](/sdks/build-an-agent) — assemble nodes and edges, save as a draft
* [Place an outbound call](/sdks/outbound-calls) — trigger a call from an agent to a phone number
* [MCP Server](/integrations/mcp) — let Claude and other coding agents drive the SDK for you
# Place an outbound call
Source: https://docs.dograh.com/sdks/outbound-calls
Trigger a Dograh voice agent to call a phone number from the SDK
Use the SDK to place a test outbound call from a specific agent to a phone number. This is the same endpoint used by the **Test Call** button in the Dograh UI.
## Prerequisites
* A Dograh [API key](/configurations/api-keys) exported as `DOGRAH_API_KEY`
* A published agent (you need the agent ID)
* A configured telephony provider — see [Telephony](/integrations/telephony/overview) for Twilio, Vonage, and other setups
## Place the call
```python Python theme={null}
from dograh_sdk import DograhClient
from dograh_sdk._generated_models import InitiateCallRequest
with DograhClient(api_key="YOUR_API_KEY") as client:
client.test_phone_call(
body=InitiateCallRequest(
workflow_id=123,
phone_number="+14155551234",
)
)
```
```typescript TypeScript theme={null}
import { DograhClient } from "@dograh/sdk";
const client = new DograhClient({ apiKey: "YOUR_API_KEY" });
await client.testPhoneCall({
body: {
workflow_id: 123,
phone_number: "+14155551234",
},
});
```
## Inspect the run
Every call creates a **run** you can inspect afterwards. See [Calls & runs](/core-concepts/calls-and-runs) for what's tracked, or use the [Runs API](/api-reference/agents/runs/list) to list and fetch runs programmatically.
## Bulk campaigns
For placing many calls at once (say, from a CSV), use [Campaigns](/core-concepts/campaigns) rather than looping over `test_phone_call` — campaigns handle pacing, retries, and progress tracking.
# Add to Website
Source: https://docs.dograh.com/voice-agent/add-to-website
Add your Dograh agent to any website so visitors can talk to it by voice or chat with it by text.
### How to add it
Add your agent to any website using the Configure Widget dialog in your agent's settings.
Step 1: Open the agent settings by clicking the gear icon in the top-right of the agent editor.
Step 2: Scroll to the **Add to Website** section and click **Configure Widget**.
Step 3: Enable embedding, add your website's domain to **Allowed Domains**, choose a **Widget Type** (Voice or Chat) and an embed mode (**Floating Widget**, **Inline Component**, or **Headless (Bring Your Own UI)**), customize the button (position, color, text) if applicable, and click **Save Configurations**.
Step 4: Copy the generated embed code and paste it into your web page to test your agent.
## Widget types
Each embed widget is either a voice widget or a chat widget — pick the type in the Configure Widget dialog. Both types support all three embed modes.
| Type | How visitors interact |
| - | - |
| **Voice** | Visitors talk to your agent over a live audio call (WebRTC, microphone required). |
| **Chat** | Visitors type messages in a chat panel and the agent replies as text. No microphone. |
How chat conversations behave:
* The conversation starts when the visitor opens the chat (clicks the chat button) — the agent greets them first. Page loads alone never start a conversation.
* A chat session lasts up to **1 hour**. When it expires, the visitor is offered a **Start new chat** button, which begins a fresh conversation.
* Reloading the page starts a fresh conversation on the next open — chat history isn't carried across page loads.
* Each conversation counts once toward the embed token's usage limit, same as one voice call.
* Chat conversations appear in your agent's call history with a full transcript.
## Embed modes
| Mode | What it renders | When to use |
| - | - | - |
| **Floating Widget** | A pill-shaped CTA button anchored to a corner of the page. For chat widgets it toggles a chat panel. | You want a turn-key experience that doesn't disturb your existing layout. |
| **Inline Component** | A panel rendered inside a `
` that you place in your page. | You want the agent embedded in a specific section (landing-page hero, support tab, etc.). |
| **Headless** | No UI. Only the audio/chat pipeline plus a JavaScript API on `window.DograhWidget`. | You want full control over the UI — your own buttons, design system, framework state, animations. |
## Prerequisites
These apply to all three modes:
* **Voice widgets:** serve your page over **HTTPS** or from `http://localhost`. Browsers refuse microphone access on plain HTTP origins or `file://`. Chat widgets have no microphone requirement, though HTTPS is still recommended.
* If you set **Allowed Domains** in the dashboard, include your test origin (e.g. `localhost`) — otherwise the widget's requests are rejected. Leave the list empty to allow all domains.
* The embed snippet you copy from the dashboard is a single `
```
Because it's built in JavaScript at page load, you can put anything your page knows in it — a logged-in customer's name, their plan, cart contents. Replace the object inside `JSON.stringify(...)` in the generated snippet, for example:
```js theme={null}
{
customer_name: currentUser.firstName,
plan: currentUser.plan,
cart: { items: cart.length, total: cart.total }
}
```
Each key is then available in any node prompt as `{{initial_context.}}`:
```text theme={null}
Greet {{initial_context.customer_name | there}} and mention their {{initial_context.plan}} plan.
```
Values can be strings, numbers, booleans, or nested objects. This works for voice and chat widgets alike, and the values are recorded on the conversation so you can see what the agent was given.
### Update context after the page loads
The attribute is fixed at page load, which doesn't fit a single-page app — the visitor logs in, changes route, or fills a cart long after the snippet ran. For that, call `setContext()`:
```js theme={null}
window.DograhWidget.setContext({
customer_name: user.firstName,
plan: user.plan
});
```
Each call merges into the context already collected, so you can add details as they arrive and re-send a name to correct it. `getContext()` returns the current set.
Context is read when a conversation starts, so `setContext()` applies to the **next** conversation — calling it mid-call or mid-chat doesn't change the one in progress (the widget logs a console warning if you do). For chat widgets, "next" includes the fresh conversation started by **Start new chat** after a session expires.
The widget script loads asynchronously, so `window.DograhWidget` may not exist yet when your app's code first runs. Call `setContext()` from an event that fires after load — a `window.load` listener, or a user action like clicking your own "Chat with us" button. See [Lifecycle callbacks](#lifecycle-callbacks-all-modes) for the same timing rule.
Use whichever fits: `data-dograh-context` for what the page knows at render, `setContext()` for what it learns later. They merge, and `setContext()` wins on a repeated name.
Context comes from the page, so a visitor can both read it and change it before it reaches your agent. Never pass secrets, and don't let it gate what the agent will do or disclose — treat `plan: "pro"` as a hint for phrasing, not proof of entitlement. For data the agent must trust, pass an opaque id like `customer_id` and let Dograh fetch the real details from your API with [Pre-Call Data Fetch](/voice-agent/pre-call-data-fetch).
Limits, applied per conversation: up to 50 variables, 64 characters per name, 2000 characters per value, and 8 KB in total. Anything past a limit is dropped and the conversation still starts. The names `provider` and `runtime_configuration` are reserved and ignored.
## Floating Widget
Renders a pill-shaped button anchored to a corner of the page.
* **Voice:** clicking the button (microphone icon + text) starts a call; clicking again ends it. The button auto-updates its label and color across the call lifecycle: configured text → "Connecting…" → "End Call" → "Retry" on failure.
* **Chat:** clicking the button (chat icon + text) opens a chat panel anchored to the same corner; the agent greets the visitor and the conversation happens in the panel. Clicking the button (or the panel's ×) closes the panel without ending the conversation — reopening shows the same transcript.
Configure **Button Text**, **Button Color**, and **Position** (top/bottom + left/right) from the dashboard.
Every other word a visitor sees is editable too, under a collapsible section in the same dialog, so you can run the widget in your site's language: **Chat Panel Text** for chat widgets (end chat button, conversation-ended message, start-new-chat and retry buttons, message placeholder, and the send/close screen-reader labels), or **Voice Call Text** for voice widgets. Leave a field blank to keep the English default.
The host page writes no JavaScript — pasting the embed snippet is the entire integration. If you want to subscribe to call lifecycle events (e.g. analytics), see [Lifecycle callbacks](#lifecycle-callbacks-all-modes) below
## Inline Component
Renders a panel inside a `
` you place in your page.
* **Voice:** a status panel (status icon + status text + CTA button). Status changes update the panel in place.
* **Chat:** a call-to-action screen first; clicking the button replaces it with a chat panel that fills the container. No extra JavaScript is needed.
Configure **Button Text**, **Button Color**, and **Call to Action Text** from the dashboard, plus the **Chat Panel Text** / **Voice Call Text** section described under [Floating Widget](#floating-widget). Inline voice widgets show the most copy of any mode — a heading and subtext for each of ready, connecting, connected, ended, failed, and lost — so that section carries the full set here.
### Plain HTML
Place a container `
` where you want the widget to render. The widget auto-attaches to it.
```html theme={null}
```
### React
Because React mounts after the widget script may have already loaded, integrate via `initInline` on first mount and `refresh` on remount. Poll for `window.DograhWidget` to handle the async script load.
```tsx theme={null}
import { useEffect } from 'react';
declare global {
interface Window {
DograhWidget?: {
initInline: (options: { container: HTMLElement }) => void;
refresh: () => void;
getState: () => { isInitialized: boolean };
};
}
}
export function Assistant() {
useEffect(() => {
let retries = 0;
const tryInit = () => {
const container = document.getElementById('dograh-inline-container');
if (window.DograhWidget && container) {
const { isInitialized } = window.DograhWidget.getState();
if (isInitialized) window.DograhWidget.refresh();
else window.DograhWidget.initInline({ container });
} else if (retries++ < 50) {
setTimeout(tryInit, 100);
}
};
tryInit();
}, []);
return ;
}
```
## Headless Mode
In Headless mode the widget injects no UI of its own. You render whatever buttons, banners, or chat interfaces you want, and drive the agent through the JavaScript API.
### JavaScript API (voice widgets)
| Method / Callback | Description |
| - | - |
| `window.DograhWidget.start()` | Begin a voice call. Must be called from inside a user-gesture handler (e.g. `click`) so the browser grants microphone access. |
| `window.DograhWidget.end()` | End the active call. |
| `window.DograhWidget.onCallStart(cb)` | Fires when `start()` is invoked (status `connecting`). No payload. |
| `window.DograhWidget.onCallConnected(cb)` | Fires when the WebRTC connection is established. Payload: `{ agentId, workflowRunId, token }`. |
| `window.DograhWidget.onCallDisconnected(cb)` | Fires only if the call had connected, when teardown runs. Payload: `{ agentId, workflowRunId, token, durationSeconds }`. |
| `window.DograhWidget.onCallEnd(cb)` | Fires whenever the call session is torn down (including failed-to-connect attempts). No payload. |
| `window.DograhWidget.onStatusChange(cb)` | Fires on every status change. Callback receives `(status, text, subtext)`. Status values: `idle`, `connecting`, `connected`, `failed`. |
| `window.DograhWidget.onError(cb)` | Fires on errors (mic permission denied, server error, etc.). Callback receives an `Error` object. |
| `window.DograhWidget.setContext(vars)` | Merge visitor context for the next call — see [Pass context to the agent](#pass-context-to-the-agent). Works in every embed mode, not just headless. |
All `on*` setters are single-listener — calling the same one again replaces the previous handler.
### JavaScript API (chat widgets)
| Method / Callback | Description |
| - | - |
| `window.DograhWidget.startChat()` | Start a conversation. The agent's greeting arrives via `onMessage`. |
| `window.DograhWidget.sendMessage(text)` | Send a visitor message. Returns a Promise that resolves with the updated transcript (array of turns), or `null` if the message couldn't be delivered. |
| `window.DograhWidget.getMessages()` | Current transcript as an array of turns: `{ id, status, user_message, assistant_message }`, each message being `{ text, created_at }`. |
| `window.DograhWidget.onMessage(cb)` | Fires once per new agent reply. Callback receives `(text, turn)`. |
| `window.DograhWidget.onChatStateChange(cb)` | Fires on every chat state change. States: `idle`, `starting`, `ready`, `waiting` (agent is replying), `ended`, `expired`, `error`. |
| `window.DograhWidget.onError(cb)` | Fires on errors. Callback receives an `Error` object. |
| `window.DograhWidget.setContext(vars)` | Merge visitor context for the next conversation — see [Pass context to the agent](#pass-context-to-the-agent). Works in every embed mode, not just headless. |
In chat mode `start()` aliases `startChat()` and `end()` is a no-op teardown (chat sessions need none), so generic snippets keep working. Sends are serialized — `sendMessage` while a reply is pending (`waiting`) resolves to `null`.
```html theme={null}
```
**About timing.** The widget script loads asynchronously, so `window.DograhWidget` may not exist at the moment your inline `
```
### React + TypeScript
```tsx theme={null}
import { useEffect, useState } from 'react';
type CallStatus = 'idle' | 'connecting' | 'connected' | 'failed';
declare global {
interface Window {
DograhWidget: {
start: () => void;
end: () => void;
onStatusChange: (cb: (status: CallStatus, text?: string, subtext?: string) => void) => void;
onError: (cb: (err: Error) => void) => void;
};
}
}
export function TalkButton() {
const [status, setStatus] = useState('idle');
useEffect(() => {
window.DograhWidget.onStatusChange((s) => setStatus(s));
window.DograhWidget.onError((err) => console.error('Dograh error:', err.message));
}, []);
const isLive = status === 'connected' || status === 'connecting';
const label = { idle: 'Talk to AI', connecting: 'Connecting…', connected: 'End Call', failed: 'Retry' }[status];
return (
);
}
```
`start()` must run inside a real user-gesture handler (`click`, `touchend`, etc.). Browsers refuse to grant microphone access to scripts that request it outside of one — calling `start()` from a `setTimeout` or on page load will fail with a permission error.
## Lifecycle callbacks (all modes)
The `on*` callbacks in the [Headless JavaScript API](#javascript-api-voice-widgets) work in **all three embed modes**, not just Headless. Use them for analytics or to trigger UI in the host page even when the widget is rendering its own UI (Floating or Inline). The call callbacks (`onCall*`) fire for voice widgets; for chat widgets use `onMessage` and `onChatStateChange` the same way.
```js theme={null}
window.DograhWidget.onCallConnected(({ agentId, workflowRunId }) => {
analytics.track('voice_call_started', { agentId, workflowRunId });
});
window.DograhWidget.onCallDisconnected(({ workflowRunId, durationSeconds }) => {
analytics.track('voice_call_ended', { workflowRunId, durationSeconds });
});
```
`onCallConnected` and `onCallDisconnected` only fire when the call actually establishes a media connection — failed-to-connect attempts (e.g. denied mic, network failure) don't trigger them, so analytics stay clean.
# Agent Node
Source: https://docs.dograh.com/voice-agent/agent
Agent node contains the prompts that drives the conversation with the Voice Agent
## Video Tutorial
Agent Node is the core building block of every Dograh workflow. It holds the LLM prompt that drives the conversation, any tools the agent can call, and knowledge base documents to reference. You can have multiple Agent Nodes in a single workflow, each handling a distinct phase of the conversation.
The Edges connected with Agent Nodes are pathways that the LLMs can take depending on how the conversation has been going so far.
## What goes in an Agent Node
**Prompt**
The instructions for how the agent should behave at this step. Be explicit about sequence: if the agent should confirm identity before discussing payment, say so in the prompt. Vague prompts produce inconsistent behavior.
**Tools**
External functions the agent can call during this step - for example, looking up account balances or updating a CRM record. Add tools via the "HTTP & Tools" or "MCP" tabs in the node settings panel.
**Allow Interruption**
When on, the caller can interrupt the agent mid-utterance.
**Add Global Prompt**
When on and a Global Node exists, the Global Prompt is prepended to this node's Prompt at runtime. On by default.
**Knowledge Base Documents**
Files the agent can reference when answering questions - product guides, FAQs, policies. Attach them via the "Manage Documents" button in the node settings panel.
## Configuring edges
Each outgoing edge has a condition written in plain language. The LLM evaluates the conversation and picks the edge whose condition best matches.
Write edge conditions as specifically as possible:
| Vague (avoid) | Specific (use) |
| - | - |
| "Debtor responds" | "Debtor confirms their identity and is ready to discuss the account" |
| "Call ends" | "Debtor commits to a specific payment option and states the amount" |
| "Problem" | "Debtor disputes owing the debt or requests validation documentation" |
If the agent keeps routing to the wrong edge, make the condition more specific. Conditions like "user responds" or "conversation ends" are too broad and will route unpredictably.
## Extracting variables
Agent Node can extract structured data from the conversation after the node completes. Enable this in the node settings under "Enable Variable Extraction."
Once enabled, configure two things:
* **Extraction Prompt:** overall instructions telling the LLM how to approach the extraction.
* **Variables to Extract:** a list of variables, each with a name, a data type (`string`, `number`, `boolean`), and a per-variable hint describing what to capture.
For example:
```text theme={null}
Extraction Prompt:
Extract the payment outcome from this debt collection call.
Variables:
- amount_user_will_pay (string): Amount the user said they will pay.
- payment_method (string): How they said they will pay, e.g. card, bank transfer.
- sentiment (string): User tone: cooperative, hesitant, angry, refused.
```
Each extracted value is stored in `gathered_context`. It is not available in any node's Prompt - reference it in [Webhook node](./webhook) payloads as `{{gathered_context.variable_name}}`, or read it from the run record after the call completes.
## Using multiple Agent Nodes
Break complex conversations into phases. Each Agent Node handles one part of the flow. A debt collection workflow, for example, might chain three Agent Nodes: identity verification, offer presentation, and objection handling.
Nodes pass context forward automatically, but not into prompts. Variables extracted in one node are stored in `gathered_context` and merged with variables from later nodes as the call progresses. `gathered_context` is not available in any node's Prompt - it can only be referenced in [Webhook node](./webhook) payloads and in the run record after the call completes. If a later Agent Node needs to react to something extracted earlier, that logic has to live in the prompt's own instructions, not in a `{{gathered_context...}}` reference.
## Common mistakes
**No outgoing edges.** Every Agent Node needs at least one edge. A dead-end node has no exit path and will trap the call.
**Putting global instructions in the Agent prompt.** Rules that apply across the entire workflow - persona, tone, compliance guardrails - belong in the [Global Node](./global), not repeated in every Agent Node.
**Vague edge conditions.** The LLM picks the edge that best matches the conversation state. Overly broad conditions cause unpredictable routing between nodes.
## Next Steps
Terminate the conversation with a closing message.
Write shared instructions that apply to every node.
Understand how `gathered_context` is built and where it can be referenced.
Evaluate agent performance automatically after each call.
# API Trigger
Source: https://docs.dograh.com/voice-agent/api-trigger
Trigger outbound calls from external systems like n8n, Zapier, or your own backend
The API Trigger node lets you initiate outbound calls to your voice agent programmatically. When you add an API Trigger node to your workflow, Dograh generates a unique endpoint URL that external systems can call to start a conversation.
This is useful when you want to trigger calls from your own backend, a CRM, or workflow tools like n8n and Zapier.
## Prerequisites
* A configured [telephony provider](/integrations/telephony/overview) — outbound calls will fail without one
* An [API key](/configurations/api-keys) for authentication
## Finding your trigger URL
When you add an API Trigger node to your workflow, Dograh assigns it a unique UUID. The trigger node exposes two URLs that share this UUID:
* A **production URL** for the published workflow
* A **test URL** for the latest draft
You can copy either URL from the trigger node's settings dialog.
```
POST https://your-dograh-instance/api/v1/public/agent/{uuid} # Production
POST https://your-dograh-instance/api/v1/public/agent/test/{uuid} # Test
```
If you are using the hosted version, replace `your-dograh-instance` with `api.dograh.com`.
### Test vs production
| Mode | URL | Runs |
| - | - | - |
| Production | `/api/v1/public/agent/{uuid}` | The published version of the agent. |
| Test | `/api/v1/public/agent/test/{uuid}` | The latest draft. Falls back to the published version if no draft exists. |
Use the test URL while iterating on changes so production traffic continues to hit the published version.
The production URL only executes a **published** workflow. If you update the workflow but do not publish it, the production trigger will continue to run the older published version.
Once you publish your draft, both URLs run the same definition.
A common pitfall is editing the workflow, saving the draft, and then calling the production trigger URL expecting the new behavior. That will not work until you publish the workflow. Use the test URL to verify draft changes before publishing.
The request body, headers, and response shape are identical for both URLs.
## Making a request
Authenticate by passing your API key in the `X-API-Key` header. The request body requires a `phone_number` and accepts optional `initial_context`, `telephony_configuration_id`, and `from_phone_number_id` fields.
```bash Production URL theme={null}
curl -X POST https://your-dograh-instance/api/v1/public/agent/{uuid} \
-H "Content-Type: application/json" \
-H "X-API-Key: dg_your_api_key" \
-d '{
"phone_number": "+14155550100",
"initial_context": {
"customer_name": "Jane",
"appointment_date": "March 15"
}
}'
```
```bash Test URL theme={null}
curl -X POST https://your-dograh-instance/api/v1/public/agent/test/{uuid} \
-H "Content-Type: application/json" \
-H "X-API-Key: dg_your_api_key" \
-d '{
"phone_number": "+14155550100",
"initial_context": {
"customer_name": "Jane",
"appointment_date": "March 15"
}
}'
```
Use the test URL when you want to verify draft changes before publishing.
### Response
A successful request returns a `workflow_run_id` that you can use to [retrieve run details](/api-reference/runs/get-run), recordings, and transcripts.
```json theme={null}
{
"status": "initiated",
"workflow_run_id": 12345,
"workflow_run_name": "WR-API-7823"
}
```
### Error responses
| Status | Cause |
| - | - |
| `400` | Telephony provider not configured, or call failed to initiate |
| `401` | Missing or invalid API key |
| `403` | API key does not have access to this agent |
| `404` | Trigger not found or not active |
## Initial context
`initial_context` is a JSON object containing any information you want your voice agent to access during the call. You can reference these values in your prompts using [template variables](/voice-agent/template-variables) — values enclosed in `{{` and `}}`.
For example, if your request includes:
```json theme={null}
{
"phone_number": "+14155550100",
"initial_context": {
"user": {
"name": "John"
}
}
}
```
You can reference the user's name in your agent prompt as `{{user.name}}` — in Agent prompts, `initial_context` fields are referenced directly by name (not prefixed with `initial_context.`). See [template variables](/voice-agent/template-variables) for the exact syntax in prompts versus webhook payloads.
See [Context & Variables](/core-concepts/context-and-variables) for more on how data flows through a call.
### Override the greeting for one call
Pass `greeting_override` inside `initial_context` to replace the Start Call node's saved greeting for one API-triggered call. A valid override takes precedence over the saved greeting. If you omit it, Dograh uses the saved greeting; if neither exists, the Start node follows its normal no-greeting behavior.
For a text greeting:
```json theme={null}
{
"phone_number": "+14155550100",
"initial_context": {
"greeting_override": {
"type": "text",
"text": "Hi, please confirm account {{account_id}}."
}
}
}
```
For a pre-recorded greeting, pass the recording's string ID from the Recordings page:
```json theme={null}
{
"phone_number": "+14155550100",
"initial_context": {
"greeting_override": {
"type": "audio",
"recording_id": "callback-welcome"
}
}
}
```
Dograh renders a text override after Pre-Call Data Fetch completes, so the override can reference variables returned by the fetch. The override follows the same playback restrictions as a saved greeting. In particular, static text greetings are not spoken by realtime speech-to-speech models; use an audio override for those configurations.
Pre-Call Data Fetch can also return `greeting_override`. Because fetched context is applied after trigger context, a valid fetched override replaces one supplied by the trigger.
## Choosing a telephony configuration and caller ID
By default, calls are placed through your organization's default outbound [telephony configuration](/integrations/telephony/overview). To route a specific call through a different configuration — for example, to dial out from a regional number — pass `telephony_configuration_id` in the request body.
```json theme={null}
{
"phone_number": "+14155550100",
"telephony_configuration_id": 42
}
```
The id is shown on each row in **Telephony configurations** (`https://app.dograh.com/telephony-configurations` for hosted or `http://localhost:3010/telephony-configurations` for local). The configuration must belong to the same organization as the API Trigger; otherwise the request returns `404`.
To use a specific caller ID from that configuration, also pass `from_phone_number_id`:
```json theme={null}
{
"phone_number": "+14155550100",
"telephony_configuration_id": 42,
"from_phone_number_id": 84
}
```
`from_phone_number_id` is the ID of a phone number configured under the selected telephony configuration. The number must be active and belong to that configuration; otherwise the request returns `400`. You can retrieve configured numbers with [List phone numbers](/api-reference/telephony-configs/phone-numbers/list).
For full endpoint details including all parameters and response fields, see the [API reference](/api-reference/runs/trigger).
# Editing a Workflow
Source: https://docs.dograh.com/voice-agent/editing-a-workflow
Learn the fundamentals of building workflows with nodes, pathways, and conversation flow in the voice agent builder.
## Overview
A workflow is a graph of connected nodes representing conversation stages, with pathways defining agent movement between stages.
## Global Node
Contains instructions applicable across the entire call:
* Company context
* Overall agent goal
* Tone and personality
* Common objection handling
## Node-Level Toggles
### Add Global Prompt
* Recommended to inject [Global Node](/voice-agent/global) instructions into most nodes
### Allow Interruption
* **Default "On"**: Bot stops and responds to user interruption
* **"Off"**: Bot continues speaking, ignoring user input
### Detect Voicemail
* Available only in Start Node
* Automatically disconnects if voicemail detected
## Variable Extraction
Supported in Agent and End Call Nodes:
* Extract structured variables
* Define name, type, and description
* Examples: customer name, budget, interest
## [Webhook Node](/voice-agent/webhook)
* Executed at call end
* Sends payload to specific endpoint
* Use for:
* Storing call data
* Triggering automation
* Syncing with CRMs
## Editing Workflows
* Selectively delete nodes/pathways
* Completely reset workflow
## Pathways and Tool Calls
* Define agent node movement
* Use [tool calls](/voice-agent/tools/http-api) to transition between nodes
* **"tool"** is a reserved keyword for function calls and actions
# End Call
Source: https://docs.dograh.com/voice-agent/end-call
You can use End Call node to configure how the Agent says its final message right before the call is terminated
## Video Tutorial
End Call is the terminal node in a workflow. It fires right before Dograh terminates the connection, giving your agent one last chance to speak. Every path through your workflow must eventually reach an End Call node.
## When it runs
End Call executes after all conversation logic is complete. The agent delivers its closing message, then the call drops.
You can have multiple End Call nodes in one workflow, each reached by a different edge condition. For example, one for a successful outcome and one for a wrong-number path. Each can have its own distinct Prompt.
## Fields
**Name**
Short identifier shown in the canvas and call logs. Use names that describe the ending context, for example "Successful close" or "Polite decline".
**Prompt**
System instructions for what the agent says before the call ends. You can make this conditional by describing different outcomes in the prompt.
For example:
```text theme={null}
If the caller committed to a payment, confirm the arrangement: the
exact amount and expected processing time. If no commitment was made,
thank them for their time and let them know they can call back to
discuss options. Always close professionally.
```
The Prompt cannot reference `gathered_context` - variables extracted by this or earlier nodes are not available in any node's Prompt. To act on extracted data, send it out via a [Webhook node](./webhook) instead.
**Add Global Prompt**
When on and a Global Node exists, the Global Prompt is prepended to this node's Prompt at runtime. On by default.
**Enable Variable Extraction**
When on, an LLM extraction pass runs after this node's turn to capture structured data from the conversation into `gathered_context`.
## Common mistakes
**Leaving the Prompt empty.** An empty Prompt means the agent says nothing before the line drops. The caller experiences an abrupt silence.
**Routing all paths into one End Call when outcomes need different closing lines.** Multiple End Call nodes are supported. If a wrong-number path and a successful-commitment path need different closing lines, use two End Call nodes with distinct Prompts rather than cramming both into one.
**Agent Nodes that never route to End Call.** Any node without a downstream path to End Call leaves calls in an unresolvable state.
Check every Agent Node for a path to End Call before publishing. Nodes with no downstream connection to End Call will drop the call without a closing message.
## Next Steps
Automatically evaluate calls after they end.
Sync call outcomes to your CRM or external systems.
Configure the conversation logic that routes into End Call.
Learn where `gathered_context` can and cannot be referenced.
# Global Node
Source: https://docs.dograh.com/voice-agent/global
Global Node contains the common prompt that is prepended to the prompt of every node, in which `Add Global Prompt` is turned on.
## Video Tutorial
You should have only one Global node per Voice Agent.
Global Node is a shared prompt layer for your entire workflow. Write your agent's persona, tone guidelines, and compliance rules once in the **Global Prompt** field. Any Agent Node or Start Call node with "Add Global Prompt" turned on will receive this content prepended to its own prompt before each LLM call.
## What goes in the Global Node
Use Global Node for instructions that apply across every step of the conversation:
* **Persona:** Who is the agent? What company do they represent?
* **Tone rules:** How formal or warm should they be?
* **Compliance guardrails:** What must the agent never say?
* **Objection handling:** How should the agent respond to pushback?
Avoid putting step-specific logic here. Instructions like "offer the settlement option after the debtor confirms their identity" belong in the Agent Node prompt, not in Global.
## How it works
Global Node does not run as a separate step in the call. It has no edges and cannot be connected to other nodes. The Global Prompt is silently prepended to each node's prompt where "Add Global Prompt" is enabled.
The LLM sees one combined prompt: the global content first, followed by the node-specific instructions. The caller never notices the seam.
## Enabling it on an Agent Node
Each Agent Node and Start Call node has an "Add Global Prompt" toggle in its settings panel. Nodes with the toggle off receive no global content for that step.
Turn the toggle off selectively when a specific node needs different behavior. An escalation node, for example, may need a more formal tone that should not inherit your default persona.
## Common mistakes
**Putting node-specific logic in the Global Node.** Global is for rules that always apply, not for conversation-specific decisions. Sequence and outcome logic belong in each Agent Node prompt.
**Leaving the Global Node prompt empty with toggles on.** A blank Global Node prepended to every prompt does nothing harmful, but it is also pointless. Either fill it in or leave the toggle off.
**Adding a second Global Node.** Each workflow supports exactly one. Adding a second throws an error.
## Next Steps
Configure how your agent opens the conversation.
Build the core conversation logic that inherits your global prompt.
# Voice Agent Builder
Source: https://docs.dograh.com/voice-agent/introduction
Dograh's voice agent builder lets you design a conversation as a graph of nodes and pathways. Learn the graph model, the available node types, and where to start.
The Voice Agent Builder is a visual, graph-based editor for designing what your voice agent says and does during a call. Instead of writing one giant prompt, you break the conversation into **nodes** (stages of the conversation) connected by **pathways** (the routes the LLM can take between stages, based on how the conversation is going).
You can create a new agent from the [dashboard](https://app.dograh.com/workflow). Describe whether you need an "Inbound" or "Outbound" agent and your use case — this is sent to an LLM to generate a starting workflow with default prompts and pathways, which you can then edit.
## The graph model
A workflow is a directed graph:
* **Nodes** represent a stage of the conversation or an action (e.g. greet the caller, collect an address, transfer the call).
* **Pathways (edges)** connect nodes and represent the routes the LLM can take depending on what the caller says. The LLM decides at runtime which pathway to follow.
A workflow should have only one **Start Call** node and typically ends at an **End Call** node, with **Agent** nodes and other node types in between. See [Editing a Workflow](/voice-agent/editing-a-workflow) for how nodes, pathways, and node-level toggles fit together.
## Node types
| Node | What it does |
| - | - |
| [**Start Call**](/voice-agent/start-call) | Starts the call and configures the agent's greeting. Should have only one per workflow. |
| [**Agent**](/voice-agent/agent) | Holds the prompt that drives conversation at a given stage; connects to other nodes via pathways. |
| [**Global**](/voice-agent/global) | Common instructions (tone, objection handling) prepended to every node with "Add Global Prompt" enabled. |
| [**QA**](/voice-agent/qa) | Runs automated post-call quality analysis against criteria you define. |
| [**API Trigger**](/voice-agent/api-trigger) | Exposes an endpoint so external systems (n8n, Zapier, your backend) can start outbound calls. |
| [**End Call**](/voice-agent/end-call) | Configures the agent's final message before the call is terminated. A workflow can have multiple End Call nodes, each with a distinct Prompt. |
| [**Webhook**](/voice-agent/webhook) | Sends call results to an external system (CRM, Zapier, n8n) when a run ends. |
Beyond nodes, you can extend an agent with:
* [**Tools**](/voice-agent/tools/introduction) — let the LLM call external APIs, transfer calls, or invoke MCP servers mid-conversation.
* [**Knowledge Base**](/voice-agent/knowledge-base) — attach documents the agent can reference during a call.
* [**Pre-Call Data Fetch**](/voice-agent/pre-call-data-fetch) — enrich context with an HTTP call before the agent starts speaking.
* [**Pre-recorded Audio**](/voice-agent/pre-recorded-audio) — mix in real recordings alongside LLM-generated speech.
* [**Template Variables**](/voice-agent/template-variables) — reference `initial_context` and `gathered_context` values in prompts and payloads.
## Where to start
1. **New to Dograh?** Follow [Editing a Workflow](/voice-agent/editing-a-workflow) to learn the fundamentals of nodes and pathways.
2. **Want to test immediately?** Use a [Web Call](/core-concepts/calls-and-runs#web-calls) from the agent editor — no telephony setup required.
3. **Ready to go live?** Configure a [telephony provider](/integrations/telephony/overview) and add an [API Trigger](/voice-agent/api-trigger) or set up inbound routing.
4. **Need the call to hit your systems?** Add a [Webhook](/voice-agent/webhook) node to sync results to your CRM or automation tool.
5. **Want to embed the agent on a website?** See [Add to Website](/voice-agent/add-to-website).
## Related
* [Core Concepts: Workflows and Agents](/core-concepts/workflows-and-agents)
* [Core Concepts: Context & Variables](/core-concepts/context-and-variables)
* [Editing a Workflow](/voice-agent/editing-a-workflow)
# Knowledge Base
Source: https://docs.dograh.com/voice-agent/knowledge-base
Upload documents that your voice agent can reference during live conversations to provide accurate, context-aware responses.
The Knowledge Base lets you upload documents that your voice agents can reference during conversations. Instead of encoding all information into prompts, you can provide source documents and let the agent retrieve relevant content on the fly.
An embedding API key (configured in **AI Models Configuration → Embedding**) is required for **Chunked Search** mode. **Full Document** mode does not require embeddings.
## How It Works
1. You **upload** a document (PDF, DOCX, TXT, or JSON) to the Knowledge Base
2. You **choose a retrieval mode** — Full Document or Chunked Search
3. Dograh **processes** the document based on the selected mode
4. You **attach** the document to one or more workflow nodes
5. During a call, the agent **retrieves** information from the document and uses it to generate accurate responses
## Supported File Types
| Format | Extension |
| - | - |
| PDF | `.pdf` |
| Word | `.docx`, `.doc` |
| Text | `.txt` |
| JSON | `.json` |
Maximum file size: **5 MB**
## Uploading Documents
1. Go to **Knowledge Base Files** in the dashboard
2. Click **Upload New** or drag and drop a file
3. Choose a **retrieval mode** (see below)
4. Click **Upload & Process** — the document will be processed according to the selected mode
### Retrieval Modes
When uploading a document, you choose how the agent retrieves information from it:
The entire document text is provided to the agent whenever it queries the knowledge base. No chunking or embedding is performed.
**Best for:** menus, price lists, FAQs, short reference sheets, and any document where the agent needs access to all the information at once.
Full Document mode does not require an embedding API key.
The document is split into smaller chunks and indexed with vector embeddings. When the agent queries the knowledge base, only the most relevant chunks are returned.
**Best for:** large documents like policies, manuals, or contracts where only a portion is relevant to any given question.
## Attaching Documents to Nodes
Once a document is processed, you can attach it to any **Start Call** or **Agent** node in your workflow:
1. Open the node edit dialog
2. Scroll to the **Knowledge Base Documents** section
3. Select one or more documents for the agent to reference
The agent will only search documents attached to the current node, so attach only the documents relevant to that conversation step.
## Best Practices
* **Use Full Document for small reference files** — menus, price lists, and FAQs work best when the agent sees the entire document
* **Use Chunked Search for large documents** — policies, manuals, and contracts are too large to fit in context, so chunked retrieval finds the relevant sections
* **Keep documents focused** — a single topic per document produces better retrieval results than a large multi-topic file
* **Use clear, structured content** — headings, lists, and short paragraphs help the chunking process
* **Attach selectively** — only attach documents relevant to a specific node rather than attaching everything everywhere
* **Keep documents up to date** — re-upload when source information changes to avoid stale answers
# Pre-Call Data Fetch
Source: https://docs.dograh.com/voice-agent/pre-call-data-fetch
Fetch customer data from your CRM or ERP before the call starts, so your voice agent can greet callers by name and reference their account details.
Pre-Call Data Fetch allows you to enrich the call context with external data before the voice agent starts speaking. Configure it on the [**Start Call**](/voice-agent/start-call) node for every call, inbound calls only, or outbound calls only. While the response is loading, the caller hears a ring-back tone. Once the data arrives, it is merged into the call's [initial context](/core-concepts/context-and-variables#initial_context) and becomes available as template variables in your prompts and greetings.
## How It Works
1. A call arrives (inbound) or is initiated (outbound).
2. Dograh sends a **POST** request to your configured endpoint with a standardized payload.
3. The caller hears a ring-back tone while waiting for the response.
4. Your API responds with a JSON object containing an `initial_context` object.
5. The variables are merged into the call's initial context.
6. The voice agent starts with full access to the fetched data via `{{variable_name}}` syntax.
## Configuration
Open the [**Start Call**](/voice-agent/start-call) node editor and expand **Advanced Settings**. Choose a **Pre-Call Data Fetch** mode and configure the endpoint when the mode is not disabled:
| Field | Description |
| - | - |
| **Pre-Call Data Fetch** | **Disabled**, **Always**, **Inbound calls only**, or **Outbound calls only**. Calls without an inbound or outbound direction run the fetch only in **Always** mode. |
| **Endpoint URL** | The URL Dograh will send the POST request to. |
| **Authentication** | Optional credential for authenticating the request. Supports API key, bearer token, basic auth, and custom header. |
## Request Format
Dograh sends a `POST` request with the following JSON payload:
```json theme={null}
{
"event": "call_inbound",
"call_inbound": {
"agent_id": 123,
"from_number": "+12137771234",
"to_number": "+12137771235"
}
}
```
| Field | Description |
| - | - |
| `event` | Always `"call_inbound"`. |
| `call_inbound.agent_id` | The workflow (agent) ID. |
| `call_inbound.from_number` | The caller's phone number (`caller_number` from initial context). |
| `call_inbound.to_number` | The called phone number (`called_number` from initial context). |
The `Content-Type` header is set to `application/json`. If you configured a credential, the corresponding authentication header is included.
## Expected Response Format
Your API should return a **JSON object** with a `2xx` status code. The variables to inject into the call context should be placed inside the `initial_context` key:
```json theme={null}
{
"call_inbound": {
"initial_context": {
"customer_name": "Jane Doe",
"account_status": "active",
"loyalty_tier": "gold",
"open_tickets": 2
}
}
}
```
You can also place `initial_context` at the top level:
```json theme={null}
{
"initial_context": {
"customer_name": "Jane Doe",
"account_status": "active"
}
}
```
The legacy `dynamic_variables` key is still accepted as a drop-in alias for `initial_context`, so existing integrations keep working without any changes. Use `initial_context` for new integrations. If a response contains both keys, `initial_context` takes precedence.
After the response is received, you can reference these values anywhere template variables are supported:
* **Greeting**: `Hello {{customer_name}}, thank you for calling!`
* **Prompt**: `The customer is a {{loyalty_tier}} member with {{open_tickets}} open support tickets.`
If the response is not a valid JSON object, does not contain `initial_context` (or the legacy `dynamic_variables`), or the request fails or times out, the call proceeds normally without the additional context. The pre-call fetch never blocks or fails a call.
## Nested Variables
If your `initial_context` contains nested objects, you can access them using dot notation:
```json theme={null}
{
"call_inbound": {
"initial_context": {
"customer": {
"name": "Jane Doe",
"address": {
"city": "Los Angeles"
}
}
}
}
}
```
Access in prompts as `{{customer.name}}` and `{{customer.address.city}}`.
## Timeout
The request has a **10-second timeout**. If your API does not respond within this window, the call proceeds without the fetched data. Design your endpoint to respond as quickly as possible to minimize the ring-back tone duration.
## Testing with Test Calls
When a real phone call comes in, the `caller_number` and `called_number` context variables are automatically set by the telephony provider and included in the pre-call data fetch request as `from_number` and `to_number`. However, when you make a test call — either a **web call** (WebRTC) or a **phone test call** from the workflow editor — these variables are not available by default.
To simulate telephony data during testing:
1. Open your workflow and go to **Settings**.
2. Under **Context Variables**, add the following variables:
* `caller_number` — set to a phone number you want to simulate as the caller (e.g., `+12137771234`).
* `called_number` — set to the number that would be dialed (e.g., `+12137771235`).
3. Save the settings.
Now when you make a test call (web or phone), these values will be sent in the pre-call data fetch request to your endpoint, allowing you to test the full flow as if a real inbound call were coming in.
These context variables are only used during test calls from the workflow editor. On production inbound calls and campaign outbound calls, the actual telephony data is used and these values are ignored.
## Example Integration
A simple Node.js endpoint that looks up a customer by phone number:
```javascript theme={null}
app.post("/dograh/pre-call", async (req, res) => {
const { call_inbound } = req.body;
const customer = await db.customers.findOne({
phone: call_inbound.from_number,
});
if (!customer) {
return res.json({});
}
res.json({
call_inbound: {
initial_context: {
customer_name: customer.name,
account_status: customer.status,
loyalty_tier: customer.tier,
},
},
});
});
```
# Pre-recorded Audio
Source: https://docs.dograh.com/voice-agent/pre-recorded-audio
Build hybrid voice agents that combine pre-recorded audio with dynamic text generation for lower latency, reduced TTS costs, and natural-sounding conversations.
Custom recordings allow you to build **hybrid voice agents** that use your own pre-recorded audio for key parts of the conversation, while falling back to LLM-generated speech (via a cloned voice) for dynamic responses. This gives you the best of both worlds — the emotional depth of real human speech and the flexibility of AI-generated dialogue.
## Why use custom recordings?
* **Reduced TTS cost** — Pre-recorded audio is played directly, so you are not charged for TTS synthesis on those segments.
* **Emotional variance** — Real recordings carry natural intonation and emotion that TTS cannot fully replicate.
* **Lower latency** — Playing a pre-recorded clip is faster than synthesizing text at runtime.
## Prerequisites
* A TTS provider that supports **voice cloning** (e.g., Cartesia, ElevenLabs, or Deepgram).
* An API key for your chosen TTS provider, configured in [Voice settings](/configurations/voice).
## Step 1: Clone your voice
Clone your voice with your TTS provider so that dynamically generated speech sounds similar to your recordings. For example, with Cartesia:
1. Go to Cartesia and navigate to **Instant Clone**.
2. Record a short audio clip (up to 10 seconds) of your voice.
3. Give the clone a name and select your language.
4. Copy the **Voice ID** — you will need it in the next step.
You can use any TTS provider that supports voice cloning. The steps will vary by provider, but the key output is always a **Voice ID** tied to your cloned voice.
## Step 2: Configure the cloned voice in Dograh
1. Go to your agent's **Model Configuration** in the Dograh dashboard.
2. Under voice settings, select **Add Voice ID manually**.
3. Paste the Voice ID from your cloned voice.
4. Make sure the **provider** matches where you cloned your voice (e.g., Cartesia).
5. Enter the provider's API key if you haven't already.
6. Save the configuration.
## Step 3: Upload recordings
Navigate to the **Recordings** page in the Dograh dashboard. Recordings are shared across all agents in your organization. You can either upload pre-recorded audio files or record directly in the browser.
For each recording:
1. Click **Upload Recording**.
2. Choose an audio file or click **Record** to record in the browser.
3. Verify the transcription is correct — edit it if needed.
4. Click **Upload**.
You can rename a recording's ID at any time by clicking the edit icon next to it in the recordings list.
## Step 4: Build the workflow
Open your agent's workflow and write the conversation flow in natural language. To insert a recording, type **`@`** in the prompt editor — this will show a list of all available recordings in your organization.
For any user question that falls outside your recordings, the agent automatically generates a dynamic response using the LLM, which is then synthesized using your cloned voice via TTS.
## Tips for best results
* **Record in a quiet environment** to improve audio quality and consistency with the cloned voice.
* **Use pro cloning services** (when available) and provide more sample audio for a higher-quality voice clone.
* **Keep recordings concise** — short, focused clips work best for specific conversation moments.
* **Review call recordings** after testing to identify where the transition between pre-recorded and dynamic audio can be improved.
# QA
Source: https://docs.dograh.com/voice-agent/qa
QA node allows you to run post-call quality analysis on your Voice Agent runs
## Video Tutorial
The QA node runs an LLM quality review on the call transcript after each conversation ends. Use it to score agent performance, flag compliance issues, and surface patterns across calls, without listening to recordings manually.
## When it runs
QA Node runs post-call only. It is not part of the live call flow - it reads the completed transcript after the conversation ends. It has no edges and cannot connect to any other node in the workflow.
This means QA does not affect call behavior. It only measures it.
## Where it sits on the canvas
Add QA Node anywhere on the canvas. It does not need to connect to other nodes - most builders place it off to the side to make clear it is a standalone evaluator. The node runs automatically for every eligible call once the workflow is published.
## Configuring the System Prompt
The core configuration is a single **System Prompt**: instructions to the reviewer LLM that describe how to evaluate each call. The LLM reads the transcript and returns a structured JSON result.
The default prompt already covers common quality dimensions out of the box:
* **Tags**: named issues detected in the transcript. The default tag set includes `DEAD_AIR`, `USER_FRUSTRATED`, `ASSISTANT_IN_LOOP`, `ASSISTANT_REPLY_IMPROPER`, `USER_NOT_UNDERSTANDING`, `HEARING_ISSUES`, `UNCLEAR_CONVERSATION`, `USER_REQUESTING_FEATURE`, `ASSISTANT_LACKS_EMPATHY`, and `USER_DETECTS_AI`
* **Call quality score**: 1-10 rating
* **Overall sentiment**: positive, neutral, or negative
* **Summary**: 1-2 sentence description of the call segment
You can replace the default System Prompt with your own instructions when you need domain-specific evaluation. Write the prompt as instructions to the QA reviewer LLM, and specify the JSON structure you want it to return.
For compliance use cases, for example:
```text theme={null}
You are a compliance reviewer for a debt collection workflow.
Review the transcript and return a JSON object with the following fields:
- "identity_verified": true or false — did the agent confirm the caller's identity before discussing the account?
- "options_presented": true or false — did the agent offer all three payment options before accepting a decision?
- "tone_score": 1-5 — how calm and professional was the agent throughout?
- "summary": one sentence describing the call outcome.
```
Be specific about what counts as a pass or a valid score. Include the exact behaviors you want the LLM to look for. Vague instructions produce inconsistent results across calls.
## Additional settings
| Setting | Default | What it does |
| - | - | - |
| Enabled | On | Toggle to disable QA without removing the node |
| Use Workflow's LLM | Off | When on, uses the same LLM configured for the workflow instead of the default QA model |
| Minimum Call Duration (seconds) | 15 | Calls shorter than this are skipped |
| Include Voicemail Calls | Off | When off, voicemail calls are skipped |
| Sample Rate (%) | 100 | Run QA on a percentage of calls rather than all of them |
## Viewing QA Results
After a call completes, the QA analysis runs automatically. You can view the results on the **Run Detail** page for each individual run.
Open a run and scroll to the **QA Analysis** section. The result shows the full JSON output your QA System Prompt requested - tags, scores, sentiment, and summary for the default prompt, or whatever structure you defined.
## Common mistakes
**Expecting QA to affect the live call.** QA Node is read-only at runtime. It evaluates what happened; it does not change what happens.
**Connecting QA Node to other nodes.** QA has no edges. Drawing a connection from QA to another node is not possible - edges will snap back when you try.
**Writing a vague System Prompt.** "Was the agent good?" produces unhelpful scores. Define specific, observable behaviors and the exact JSON fields you want back.
**Not specifying output format.** The LLM follows your instructions, but if you do not specify a return format, the output may be inconsistent across calls. Always describe the exact JSON structure in the System Prompt.
## Next Steps
Configure the conversation logic that QA evaluates.
Send call outcomes to external systems after the call ends.
Learn how `gathered_context` is built during the call.
Configure the final message before the call drops.
# Start Call
Source: https://docs.dograh.com/voice-agent/start-call
You can use Start Call node to Start the call and configure how Agent greets the user when the conversation starts.
## Video Tutorial
You should have only one Start Call node per Voice Agent.
Start Call is the entry point for every conversation. It fires the moment a call connects and controls the first thing your agent says. Every workflow must have exactly one.
## When it runs
Start Call executes before any Agent Node. No other node can precede it in the call flow.
After Start Call completes, the conversation moves to the next node - typically your first Agent Node. If the caller hangs up immediately or reaches a wrong-number condition, Start Call can route directly to End Call instead.
## Fields
**Name**
Short identifier shown in the canvas and call logs. Has no runtime effect on what the agent says.
**Greeting Type**
Whether the greeting is spoken via TTS from text or played from a pre-recorded audio file. Defaults to "Text (TTS)".
**Greeting Text**
Optional fixed line spoken via TTS the moment the call connects, before the AI's conversational turn begins. Supports `{{template_variables}}`. Leave empty to skip the greeting and let the Prompt handle the opening.
For example:
```text theme={null}
Hi {{initial_context.debtor_name}}, this is Alex from Apex Recovery Solutions.
```
See [Pre-recorded Audio](./pre-recorded-audio) to use a recording file instead of TTS text.
**Prompt** (required)
System instructions for the AI's opening conversational turn. Runs after Greeting Text (if set). Supports `{{initial_context.field_name}}` and variables from Pre-Call Data Fetch.
For example:
```text theme={null}
After greeting the caller, confirm you are speaking with
{{initial_context.debtor_name}}. If they confirm, move to Debt Disclosure.
If they say wrong number, move to End Call.
```
If a variable referenced in Greeting Text or Prompt is missing from the API request payload, the agent speaks the raw placeholder out loud - for example, "Hi `{{initial_context.debtor_name}}`" - instead of the caller's name. Test with a complete payload before publishing.
**Allow Interruption**
When on, the caller can interrupt the agent mid-utterance. Off by default on Start Call.
**Add Global Prompt**
When on and a Global Node exists, the Global Prompt is prepended to this node's Prompt at runtime. On by default.
**Delayed Start**
When on, the agent waits before speaking after the call connects. Useful for outbound calls where the called party needs a moment to settle. Duration can be set between 0.1 and 10 seconds.
**Enable Variable Extraction**
When on, an LLM extraction pass runs after this node's turn to capture structured data from the conversation into `gathered_context`.
**Tools**
Functions the agent can call during the opening turn. Add via the "HTTP & Tools" tab (HTTP API and built-in tools) or the "MCP" tab (MCP server tools).
**Knowledge Base Documents**
Documents the agent can reference during this step. Attach via "Manage Documents."
**Pre-Call Data Fetch**
Choose whether Dograh makes a POST request before **all**, **inbound**, or **outbound** calls, or leave the fetch **disabled**. Dograh merges the JSON response into the call context as template variables before rendering the greeting and Start-node prompt.
## Outgoing edges
Start Call can have multiple outgoing edges, each with a user-defined label and a condition written in plain language. The LLM reads the conversation and picks the edge whose condition best matches.
Each edge needs two things:
* **Label:** a short name shown on the canvas (you define this)
* **Condition:** the instruction that tells the LLM when to take this edge
A typical Start Call has two edges:
```text theme={null}
Label: "caller confirmed"
Condition: "The caller confirmed they are the right person and are ready to continue."
Label: "wrong number"
Condition: "The caller says this is the wrong number, asks not to be called, or refuses to confirm their identity."
```
You can add more edges if the opening turn needs to branch further.
## Common mistakes
**Leaving Greeting Text empty with no Prompt.** If both Greeting Text and Prompt are empty, the agent says nothing at the start of the call. The caller hears silence.
**Confusing Node Name with the agent's spoken name.** Node Name is a canvas label for your own reference. The name the agent introduces itself as comes from the Greeting Text or Prompt content, or the persona you define in the [Global Node](./global).
**Adding a second Start Call node.** Each workflow has one entry point. A second Start Call node will throw an error.
## Next Steps
Build the core conversation logic that follows the greeting.
Set persona and tone rules that apply across all nodes.
Learn how `initial_context` variables flow through a call.
Configure the agent's final message before the call drops.
# Template Variables
Source: https://docs.dograh.com/voice-agent/template-variables
You can use Template Variables in your prompts for your Agent nodes, or when constructing the payload for the Webhook Node
### Template Rendering
You reference template variables with `{{double_brace}}` syntax. The data comes from [`initial_context`](/core-concepts/context-and-variables#initial_context) — set via the [API Trigger](/voice-agent/api-trigger), a [campaign](/core-concepts/campaigns) sheet, or a [Pre-Call Data Fetch](/voice-agent/pre-call-data-fetch) that enriches the context when the call starts — and, in Webhook payloads only, from [`gathered_context`](/core-concepts/context-and-variables#gathered_context) (variables extracted during the call).
**The syntax depends on where you use it:**
| Where | `initial_context` | `gathered_context` |
| - | - | - |
| Agent node prompts | `{{field_name}}` (referenced directly) | Not available |
| Webhook Node payloads | `{{initial_context.field_name}}` | `{{gathered_context.field_name}}` |
#### Agent node prompts
In an Agent node prompt, reference each `initial_context` field **directly by name**. Nested values are supported with dot notation.
Example: if the initial context is
```json theme={null}
{
"initial_context": {
"user": {
"name": "John"
}
}
}
```
write your prompt to access the user's name as below:
Prompt: `You are Alice, who is talking to {{user.name}}.`
Variables extracted during the call (`gathered_context`) are **not** available in Agent prompts — a prompt can only reference `initial_context` fields. To act on extracted data, send it to a [Webhook Node](/voice-agent/webhook).
#### Webhook Node payloads
When constructing a [Webhook Node](/voice-agent/webhook) payload, the context objects are nested under their names, so reference them with the `initial_context.` and `gathered_context.` prefixes:
Payload value: `{{initial_context.user.name}}` or `{{gathered_context.call_disposition}}`
### Using Template Variables for Testing
Template variables defined in your workflow **Settings > Context Variables** are included in test calls (both web and phone) made from the workflow editor. This is useful for simulating data that would normally come from telephony or an API trigger.
For example, you can set `caller_number` and `called_number` as context variables to test [Pre-Call Data Fetch](/voice-agent/pre-call-data-fetch#testing-with-test-calls) without needing a real inbound call.
Browser voice tests are treated as inbound calls by default. To simulate an outbound call with web, set the `direction` template-context variable to `outbound`.
These context variables are only used during test calls from the workflow editor. On production inbound calls and campaign outbound calls, the actual telephony data is used and these values are ignored.
### Nodes
Dograh Voice Agents are composed of various nodes. These nodes can provide instructions to the voice agent, help you setup a [trigger](/voice-agent/api-trigger) where you can trigger the voice agent to call someone, or help you setup a [webhook](/voice-agent/webhook), where you can update the results of the call in your CRM or trigger a downstream workflow in n8n. In the next steps, we will be documenting the nodes that you can use in building the voice agent.
# Call Transfer
Source: https://docs.dograh.com/voice-agent/tools/call-transfer
Transfer live telephony calls using static destinations, ordered context mappings, or dynamic resolution.
The Call Transfer tool lets your AI agent transfer an active telephony call to a phone number or SIP endpoint. You can configure a fixed destination, select one with ordered context rules, use a template from call context, or resolve the destination at transfer time by calling your HTTP endpoint.
Use this tool when the caller should speak to a human operator, department, escalation queue, support queue, or another phone system.
To hand the caller to **another Dograh agent** rather than to a phone number, use [Transfer to Agent](/voice-agent/tools/transfer-agent) instead. It carries conversation history as a labeled transcript across the handoff and works on web calls as well as telephony.
## Supported providers
Call transfer is available for telephony calls using Twilio, Telnyx, or Asterisk ARI providers.
Web calls do not support call transfer.
## How call transfer works
When the LLM decides a transfer is needed, it calls the Call Transfer tool. Dograh resolves the destination, validates the transfer setup, and then starts the provider transfer.
For a successful transfer:
1. The agent calls the Call Transfer tool.
2. Dograh resolves the destination from static configuration, ordered context mappings, or a dynamic HTTP resolver.
3. Dograh optionally plays a pre-transfer message.
4. Dograh starts dialing the destination through the telephony provider.
5. The caller hears hold music while the destination leg is connecting.
6. When the destination answers, Dograh connects the caller and ends the AI agent's involvement.
By default, the receiving person joins without a conversation summary. For Twilio calls, you can enable a spoken introduction before the two parties are connected.
## Transfer introduction
In your Call Transfer tool, enable **Play a transfer introduction to both parties**. This setting is off by default and supports Twilio calls using your configured TTS provider. Other telephony providers, external PBX transfers, and realtime speech-to-speech agents skip the introduction and use their usual transfer behavior.
The default instructions ask for a short summary of the caller's request, essential details, and language preference. Edit **Introduction instructions** to change what is included or request a language, such as Brazilian Portuguese. Your configured TTS voice must support that language.
Dograh uses the current conversation, including history from earlier agents, and generates audio with the active agent's TTS configuration. After the destination answers, each party hears the same recording on their own line. Speaking does not interrupt playback. They can talk to each other once both have finished hearing it; playback may start at slightly different times on the two lines.
Preparation has an eight-second budget while the caller hears hold music. Introductions are limited to 25 words and 15 seconds of audio. If preparation fails or exceeds its budget, the transfer proceeds without the introduction. If Twilio cannot fetch or play the audio, a fallback connects the parties without it.
Twilio fetches the recording from your publicly reachable Dograh backend using a temporary URL. The recording expires after five minutes; no public storage bucket is required.
Both the caller and whoever answers the destination hear the introduction. For a ring group, that can be any member who picks up. Include only details you intend to share with both parties.
## Destination source
You must choose one destination source for each Call Transfer tool.
### Static / Template
Use this when the transfer destination is already known before the transfer happens.
Static destinations can be:
* A fixed E.164 phone number, such as `+1234567890`
* A SIP endpoint, such as `PJSIP/sales-queue`
* A template value from context, such as `{{initial_context.transfer_destination}}`
This is the simplest option for fixed support lines, fixed departments, or cases where the destination is provided before the call starts.
### Context Mapping
Use this when a finite set of context values should select the transfer destination. You can add multiple rules and order them. Dograh evaluates the rules from top to bottom and uses the first route whose value matches, ignoring case and surrounding whitespace.
For each rule, enter a context field and one or more value-to-destination mappings. An unprefixed field such as `department` reads `gathered_context.department` first and falls back to `initial_context.department` when the gathered value is absent. You can use `gathered_context.department` or `initial_context.department` when you want to read only one context.
Mapped and fallback destinations can be:
* A VICIdial in-group, such as `sales`
* A SIP endpoint, such as `PJSIP/1001`
* An E.164 PSTN number, such as `+1234567890`
* A context template, such as `{{initial_context.transfer_destination}}`
Dograh performs final variable extraction before evaluating context mappings. If no rule matches, it uses the optional fallback destination. If there is no fallback, or a selected destination template resolves to an empty value, the transfer fails before dialing and the agent can continue the call.
### Dynamic HTTP Resolver
Use this when the destination must be decided during the conversation.
Dograh sends a `POST` request to your resolver endpoint before starting the transfer. Your endpoint returns the destination and may return a custom message to play before dialing.
Common use cases:
* Route by region, timezone, or language
* Look up the right account team in your customer system
* Route by customer tier or account status
* Route by issue type, language, or department
* Use business logic from your backend instead of hardcoding numbers in Dograh
## Dynamic resolver request
The resolver request body is a flat JSON object.
Dograh builds it from:
* **LLM parameters**: values the agent extracts from the conversation when it calls the transfer tool
* **Preset parameters**: values Dograh injects from fixed values or templates such as `{{initial_context.account_id}}` or `{{gathered_context.billing_issue_type}}`
If both define the same key, preset parameters take precedence.
Example request body:
```json theme={null}
{
"account_id": "acct_123",
"plan": "enterprise",
"billing_issue_type": "invoice_dispute"
}
```
Dograh does not send the full conversation transcript to the resolver. Add the specific values you need as LLM parameters or preset parameters.
## Dynamic resolver response
Your resolver must return a JSON object with `transfer_context.destination`.
`transfer_context.custom_message` is optional.
```json theme={null}
{
"transfer_context": {
"destination": "+1234567890",
"custom_message": "Please wait while I connect you with our enterprise billing team."
}
}
```
Follow this response shape exactly. Dograh reads specific keys from the resolver response. For dynamic transfer resolution, an invalid response does not fall back silently: the transfer fails before dialing.
Field behavior:
* `destination`: Required. E.164 phone number or SIP endpoint.
* `custom_message`: Optional. If returned, this message is played before the provider transfer starts.
If the resolver fails, times out, returns invalid JSON, or omits `transfer_context.destination`, the transfer fails gracefully and the agent can continue handling the caller.
## Dynamic resolver configuration
When using a dynamic resolver, configure:
* **Resolver URL**: HTTPS or HTTP endpoint Dograh calls with `POST`
* **Resolver Timeout**: How long Dograh waits for the resolver response
* **Resolver Wait Message**: Optional message spoken while Dograh waits, such as "One moment while I find the right team."
* **LLM Parameters**: Values the agent should extract from the conversation, such as `billing_issue_type` or `requested_department`
* **Preset Parameters**: Values injected by Dograh from context or fixed values
* **Custom Headers**: Static headers sent to your resolver endpoint
* **Credential**: Optional saved credential used to authenticate the resolver request
The dynamic resolver uses `POST` only. If you need custom backend behavior, implement it behind your resolver endpoint and return the expected `transfer_context` shape.
## Pre-transfer messages
Call Transfer supports a common pre-transfer message setting for static, context-mapped, and dynamic destinations.
For static transfers:
1. Dograh resolves the static or template destination.
2. Dograh plays the configured pre-transfer message, if any.
3. Dograh starts the provider transfer.
For dynamic transfers:
1. Dograh may play the resolver wait message while calling your resolver.
2. If the resolver returns `custom_message`, Dograh plays that message.
3. If the resolver does not return `custom_message`, Dograh uses the configured pre-transfer message.
4. Dograh starts the provider transfer.
Resolver `custom_message` overrides the configured pre-transfer message for that transfer attempt.
## Transfer timeout
Transfer timeout is the maximum time Dograh waits for the destination to answer after the provider transfer starts.
This timeout applies to static, context-mapped, and dynamic transfers.
The resolver timeout is separate. It controls only how long Dograh waits for your HTTP resolver before dialing starts.
## Destination formats
### Twilio and Telnyx
Use E.164 phone numbers:
```text theme={null}
+1234567890
```
The number must be reachable from your telephony provider.
### Asterisk ARI
Use SIP endpoints:
```text theme={null}
PJSIP/sales-queue
SIP/1001
```
Asterisk ARI transfers only work with SIP endpoints configured on your Asterisk server. External phone numbers require PSTN trunk configuration in your Asterisk setup.
## Example: route enterprise billing calls
Use this setup when the agent identifies a billing issue and your backend decides whether to route the caller to standard billing, enterprise billing, or collections.
Configure LLM parameters:
```json theme={null}
[
{
"name": "billing_issue_type",
"type": "string",
"description": "The billing issue the caller needs help with, such as invoice_dispute, payment_failed, refund_request, or plan_change.",
"required": true
},
{
"name": "requested_department",
"type": "string",
"description": "The department the caller is asking for, if they explicitly mention one.",
"required": false
}
]
```
Configure preset parameters:
```json theme={null}
[
{
"name": "account_id",
"type": "string",
"value_template": "{{initial_context.account_id}}",
"required": true
},
{
"name": "plan",
"type": "string",
"value_template": "{{initial_context.plan}}",
"required": true
}
]
```
Your resolver receives:
```json theme={null}
{
"billing_issue_type": "invoice_dispute",
"requested_department": "billing",
"account_id": "acct_123",
"plan": "enterprise"
}
```
Your resolver returns:
```json theme={null}
{
"transfer_context": {
"destination": "+18005550199",
"custom_message": "Please wait while I connect you with our enterprise billing team."
}
}
```
## Best practices
* Keep resolver latency low. Aim for 2-3 seconds.
* Use a resolver wait message if the resolver may take noticeable time.
* Send only the parameters your resolver needs.
* Prefer preset parameters for values already available in `initial_context` or `gathered_context`.
* Put the most specific context-mapping rules first and configure a fallback only when it is safe for every unmatched value.
* Make LLM parameter descriptions explicit so the agent knows what to extract.
* Test both successful transfer and resolver failure paths before going live.
* Keep destination validation and routing logic in your backend when routing depends on account data or business rules.
## Troubleshooting
### The transfer tool is not called
Check that the tool is attached to the correct node and that the node prompt clearly tells the agent when to transfer.
### Static transfer fails with no destination
Static mode requires a configured destination. Use a fixed destination or a template that resolves to a non-empty value.
### Dynamic transfer fails before dialing
Check the resolver response. It must include:
```json theme={null}
{
"transfer_context": {
"destination": "+1234567890"
}
}
```
Also verify the resolver URL, authentication headers, credential, and timeout.
### Resolver receives missing parameters
Confirm the parameter is configured as either:
* an LLM parameter, if the agent should extract it from the conversation
* a preset parameter, if Dograh should inject it from context
For known context values, prefer preset parameters like `{{initial_context.account_id}}` or `{{gathered_context.billing_issue_type}}`.
### Destination does not connect
Verify that the phone number or SIP endpoint is valid and reachable from the configured telephony provider.
# End Call
Source: https://docs.dograh.com/voice-agent/tools/end-call
Allow your AI agent to terminate calls when conversations reach a natural conclusion or specific conditions are met.
The End Call tool enables your AI agent to terminate calls programmatically when conversations reach a natural conclusion, specific conditions are met, or when instructed by the caller.
## How It Works
The End Call tool provides a clean way to terminate conversations:
1. **Agent Decision**: Your AI agent determines the call should end based on conversation context
2. **Optional Message**: Agent can provide a closing message before ending the call
3. **Clean Termination**: Call is terminated gracefully through the telephony provider
4. **Call Disposition**: Call disposition is updated in the system
## Configuration
### Basic Settings
* **End Message**: Optional closing message played before termination
* **Reason**: Optional reason code for call termination tracking
## Agent Usage
Your AI agent can end calls during conversations:
```
end_call(message="Thank you for calling. Have a great day!", reason="conversation_complete")
```
Common scenarios:
* Conversation has reached natural conclusion
* User explicitly requests to end the call
* Agent has completed all requested tasks
* Call timeout or error conditions
# HTTP API
Source: https://docs.dograh.com/voice-agent/tools/http-api
Create REST API tools that your AI agent can invoke during conversations to integrate with external systems.
HTTP API tools allow you to attach extrernal REST API calls directly to workflow nodes, enabling your voice agents to call any internal or external system during live conversations based on LLM judgment and your prompts. This works similar to function calling on any agentic platform and is 100% open source and fully customizable.
## Video Tutorial
## What is an HTTP API Tool?
An HTTP API Tool is a REST API definition that the LLM can invoke at runtime.
**Typical use cases:**
* Call your own backend endpoints
* Trigger n8n automations
* Sync data with a CRM
* Fetch data from external APIs (weather, pricing, availability, etc.)
* Write/update/read data using Rest API
**The LLM decides:**
* which tool to call
* when to call it
* what parameters to send
**All based on:**
* Your prompts: node-level instructions in simple english (or any language)
* Tool name
* Tool description
* Parameter definitions
## Defining an HTTP API Tool
### 1. Tool Name
* Must be clear and action-oriented.
* **Examples:** `capture_lead_interest`, `fetch_weather`, 'create\_crm\_contact', etc
### 2. Tool Description
* Extremely important
* This is how the LLM decides **when** to use the tool.
* Write it in plain, explicit English.
**Bad:**
"API to capture data"
**Good:**
"This tool is to capture interest. Use this tool when the user clearly expresses interest in the product or wants to be contacted"
### 3. Endpoint Configuration
* Full URL (**must include `http://` or `https://`**)
* Supports **REST** methods
Common mistake: forgetting https\:// in the URL.
### 4. Authentication & Headers
* Add custom authentication
* Add custom headers
* Works with internal services and third-party APIs
### 5. Parameters
Each parameter must have:
* Name
* Type
* Description
* Required/Optional flag
**Parameter descriptions matter more than types.**
Guidelines:
* Start with string parameters when possible
* Be explicit in what the value represents
* Mark only truly mandatory fields as required
Example:
* interest (string):
"Set to true if the user clearly shows intent to buy or wants follow-up. Otherwise false."
### Send the workflow run ID
Every call and text chat has a reserved `initial_context.workflow_run_id` that external context cannot override. To send it, add a **Preset Parameter**:
| Field | Value |
| - | - |
| Name | `workflow_run_id` |
| Type | `number` |
| Value template | `{{initial_context.workflow_run_id}}` |
| Required | Yes |
The preset is sent in the request body for POST/PUT/PATCH or query parameters for GET/DELETE. With a custom body template, include `{{workflow_run_id}}` where needed. For UI tool tests, enter a sample numeric value.
### Body format
POST, PUT, and PATCH requests send a JSON body (`application/json`) by default. Some APIs only accept form-encoded bodies and reject JSON. For those, set **Body Format** on the Parameters tab to **Form-encoded** (`body_format: "form"` in the API). Parameters are then sent as `application/x-www-form-urlencoded` fields:
* A list value repeats the field name once per item, e.g. `To=a&To=b`.
* An object value is sent as a JSON string, because form fields cannot nest.
A body template works the same way in either format. Its top-level keys become the form fields.
### What the agent receives
The agent gets the response's `status_code` and parsed body as `data`. `status` is `success` for responses below 400, and `error` for any 4xx or 5xx response, so a rejected request is never reported to the agent as a success. Timeouts and connection failures also come back with `status: "error"` and an `error` message.
## Attaching Tools to Workflow Nodes
* You can attach **multiple tools to a single node**
* All the tools that you have created will be available for selection in the node
* Tools are only callable when attached to that node
* The LLM will choose which one to call
Inside the node, guide the LLM using **simple English instructions**.
Example:
"If the user shows interest in speaking to sales or wants a callback, immediately call the capture\_lead\_interest tool and set interest to true."
This instruction is often the deciding factor for correct tool usage.
This instruction is often the **deciding factor** for correct tool usage.
## Tool Invocation Logic (How the LLM Thinks)
The LLM considers:
* User's spoken intent
* Node prompt instructions
* Tool name and description
* Parameter descriptions
If these align clearly, the tool is called automatically.
Poor naming or vague descriptions lead to:
* Missed tool calls
* Wrong parameters
* Hallucinated values
## Key Best Practices
* Name tools clearly
* Write detailed, action-based descriptions
* Keep parameters simple at first
* Always include http/https in URLs
* Use plain English in node instructions
* Attach only relevant tools to each node
**Well-defined tools + clear prompts = reliable, production-grade voice agents.**
# Tools
Source: https://docs.dograh.com/voice-agent/tools/introduction
Extend your voice agent's capabilities by giving it tools to perform actions during live conversations.
Tools let your AI agent take actions during a conversation — transfer calls, hand the caller to another agent, end calls, call external APIs, or invoke remote MCP servers — based on the context of the conversation and your prompt instructions.
When a tool is attached to a workflow node, the LLM decides **when** to invoke it and **what parameters** to pass, based on the user's spoken intent and your node-level instructions.
## Tool Types
Dograh provides two categories of tools:
### Built-in Tools
Pre-configured tools that handle common telephony operations out of the box:
* [**Call Transfer**](/voice-agent/tools/call-transfer) — Transfer the active call to a phone number or SIP endpoint
* [**Transfer to Agent**](/voice-agent/tools/transfer-agent) — Hand the live call to another Dograh agent, carrying conversation history as a labeled transcript
* [**End Call**](/voice-agent/tools/end-call) — Terminate the call when the conversation is complete
### Custom Tools
Tools you define to integrate with any external system:
* [**HTTP API**](/voice-agent/tools/http-api) — Call any REST API endpoint during a conversation (e.g., CRM updates, data lookups, triggering automations)
* [**MCP Tool**](/voice-agent/tools/mcp-tool) — Connect an external MCP server and expose its remote tools to the LLM during a conversation
## How Tools Work
1. You **define** a tool with a name, description, and parameters
2. You **attach** the tool to one or more workflow nodes
3. During a call, the LLM reads your node prompt, the tool description, and the caller's intent to decide whether to invoke the tool
4. The tool executes and returns a result that the agent can use to continue the conversation
## Best Practices
* **Attach only relevant tools to each node** — fewer tools means more reliable invocations
* **Write clear tool descriptions** — the LLM uses these to decide when to call the tool
* **Guide the LLM in your node prompt** — explicitly describe when a tool should be used
* **Test tool behavior** — verify your agent invokes tools at the right moments using web or phone calls
# MCP Tool
Source: https://docs.dograh.com/voice-agent/tools/mcp-tool
Connect an external MCP server to your voice agent so the LLM can call remote tools during a live conversation.
This page is about using an external MCP server as a **tool inside a voice agent**. If you want Claude, Cursor, or another coding agent to control Dograh itself over MCP, see [Dograh MCP Server](/integrations/mcp).
MCP tools let a Dograh voice agent call tools exposed by a remote [Model Context Protocol](https://modelcontextprotocol.io/) server during a live conversation. Dograh discovers the remote tool catalog, turns those tools into LLM-callable functions, and forwards invocations to the MCP server over authenticated Streamable HTTP.
## What to configure
An MCP tool in Dograh has four important pieces:
* **Name**: the server label shown in Dograh
* **Description**: tells the LLM when this MCP server is relevant
* **URL**: the remote MCP server endpoint (`http://` or `https://`)
* **Credential**: the auth Dograh should send when connecting to that server
You can also set a **tool filter** to allow only specific remote MCP tools to be exposed.
## Authentication
Most hosted MCP servers expect:
```http theme={null}
Authorization: Bearer
```
So before creating the MCP tool, create a credential in Dograh with:
* **Credential Type**: `Bearer Token`
* **Token**: the access token issued by your MCP server
Then select that credential on the MCP tool.
If the remote MCP server documentation says to use Bearer auth, choose **Bearer Token** in the credential dialog. Dograh will translate that into the exact `Authorization: Bearer ` header on the MCP connection.
Dograh also supports other credential types, but **Bearer Token** is the default thing to try for third-party MCP servers unless their docs say otherwise.
## How it works
The runtime path is:
1. When you save or refresh the MCP tool, Dograh opens a short-lived authenticated MCP session and fetches the remote tool catalog.
2. Dograh stores that catalog as the tool's discovered tools so the UI can show you which remote functions exist.
3. When a call starts, Dograh opens one live MCP session per attached MCP server and reuses your selected credential for that session.
4. For each node, Dograh exposes only the MCP tools allowed by the server-level filter and the node-level selection.
5. Dograh namespaces those remote tools into ordinary LLM function definitions so they can safely coexist with HTTP API tools, call transfer, end call, and other tools.
6. During the conversation, the LLM sees only the tool name, description, and argument schema. It does **not** see the secret.
7. When the LLM calls one of those tools, Dograh forwards the invocation to the MCP server over the active authenticated session, receives the result, and feeds that result back into the agent turn.
In short: **Dograh handles discovery, authentication, session management, tool registration, and result plumbing; the LLM only decides when to call the tool and with which arguments.**
## Creating an MCP tool
1. Go to **Tools** and create a new tool.
2. Choose **MCP Server**.
3. Enter a clear name and a description that explains when the LLM should use this server.
4. Paste the MCP server URL.
5. Select the credential. In most cases this should be a **Bearer Token** credential.
6. Save the tool and confirm that Dograh discovered the remote tools.
If the server exposes many tools, use filtering to keep only the ones your agent actually needs.
## Attaching it to a node
After the MCP tool is created:
1. Open the workflow node where the tool should be available.
2. Add the MCP tool from the node's tool list.
3. Select only the remote MCP functions that should be callable on that node.
4. In the node prompt, tell the LLM exactly when to use those functions.
The tighter the node-level selection and prompt guidance, the more reliable MCP tool usage becomes.
## Best practices
* Use one MCP server per logical integration when possible.
* Keep the tool description explicit about **when** the LLM should use that server.
* Expose only the minimum remote functions needed for each node.
* Prefer a **Bearer Token** credential unless the MCP server specifies another auth scheme.
* Test discovery first, then test a real phone/web call to confirm the LLM invokes the right MCP function with the right arguments.
If the remote MCP server is temporarily unavailable, Dograh degrades gracefully and the call can continue without those MCP tools rather than crashing the entire conversation.
# Transfer to Agent
Source: https://docs.dograh.com/voice-agent/tools/transfer-agent
Hand a live call from one Dograh agent to another, carrying conversation history so the caller can continue where they left off.
The Transfer to Agent tool hands the caller from the agent they are talking to over to another Dograh agent, without dropping the call. The caller stays on the same call throughout: they hear a short handover line, then a ringer, then the next agent picks up.
Use this tool to split one phone number across several focused agents — a router that picks a language or a department, a specialist that handles billing, a closer that takes payment — instead of building one large agent that tries to do everything.
## Transfer to Agent vs. Call Transfer
Both tools move the caller somewhere else, but they are not interchangeable.
| | Transfer to Agent | [Call Transfer](/voice-agent/tools/call-transfer) |
| - | - | - |
| Destination | Another Dograh agent, by `workflow_id` | A phone number or SIP endpoint |
| Who answers | A Dograh agent | A human, queue, or external phone system |
| Context | A labeled transcript of the previous agents' conversation | Optional spoken introduction on Twilio; otherwise no conversation context is passed |
| Availability | Any cascade call, including web calls | Telephony only (Twilio, Telnyx, Asterisk ARI) |
If the caller should end up talking to a person, use Call Transfer. If they should end up talking to another one of your agents, use this tool.
## How it works
1. The agent calls the Transfer to Agent tool.
2. The current agent speaks the configured handover message in its own voice.
3. The caller hears a ringer while Dograh prepares the destination agent.
4. Dograh labels the available conversation as history from the previous agent, including anything the caller said during the handoff.
5. The destination agent takes over at its **Start Call** node. By default it plays that node's greeting. With **Play greeting** turned off, it skips the greeting and opens with a reply based on the conversation history.
After a transfer is accepted, Dograh suppresses the source agent's next generation while the handover message and ringer play. If the transfer is refused, the agent can explain the result and continue helping the caller.
## What the destination agent receives
The destination receives one historical message containing the available conversation, with a heading such as `Conversation with previous agent "Reception":`. Caller and agent speech are labeled separately. This applies to every handoff, with **no minimum number of turns** and no LLM summary call during the transfer.
Earlier agents' history stays in the same transcript across repeated transfers. Speech added while the destination is being prepared is included and marked as happening during the transfer. The destination is told that this is historical context and that the transfer is already complete.
The source agent's prompts, tool calls, and tool results are filtered out. Spoken text accompanying a tool call, such as the handover announcement, is retained. Later node transitions preserve the inherited transcript when summarizing the current agent's own conversation.
Because context travels with the caller, the destination agent should not re-ask for information the caller already gave. Say so in its prompt.
## Configuration
A Transfer to Agent tool has three settings. Everything else about how a handoff sounds is fixed.
* **Agent** (`workflow_id`, required) — the id of the Dograh agent to transfer to. It must belong to the same organization.
* **Handover message** (`message`) — spoken by the current agent, in its own voice, just before the handover. Supports [template variables](/voice-agent/template-variables). Maximum 500 characters. Defaults to "Let me connect you with the right person. One moment please." Leave it empty to hand over without saying anything.
* **Play greeting** (`play_greeting`, default `true`) — whether the destination agent opens with its Start Call greeting. Turn it off when the destination should continue the conversation rather than introduce itself, for example an agent that greets callers on its own phone number but is also a transfer target. It then opens with a reply generated from the conversation history.
Set the handover message in the language the destination agent speaks. The default is English, which will sound wrong in front of a Portuguese or Spanish agent.
### One tool per destination
Each Transfer to Agent tool points at exactly one agent. An agent that can hand the caller to three places gets three tools, and the model chooses between them the way it chooses between any other tools — by their names and descriptions.
Name each tool after its destination and describe precisely when it should fire:
```
Name: Transfer to Billing
Description: Hand the call to the billing agent. Call this only once the
caller has said their question is about an invoice, a charge,
or a refund. Do not call it for delivery or account questions.
```
If the model picks the wrong destination, fix the tool descriptions before you touch the prompt — the description is what it routes on.
## Availability
Agent transfer works on every cascade call, including web calls, which is where it differs most from Call Transfer.
It is **not** available on speech-to-speech (realtime) agents. The tool still registers there, but it refuses and tells the agent to keep helping the caller itself, so the call degrades into a normal conversation rather than failing silently.
## Creating one
```json theme={null}
{
"name": "Transfer to Billing",
"category": "transfer_agent",
"description": "Hand the call to the billing agent once the caller says their question is about an invoice, a charge, or a refund.",
"definition": {
"type": "transfer_agent",
"config": {
"workflow_id": 117,
"message": "Let me put you through to our billing team. One moment."
}
}
}
```
The destination agent must exist before you create the tool, because the tool stores its id. When you are building a set of agents programmatically, create the destination agents first, then the tools, then the agent that routes between them.
## Prompting the routing agent
* Tell the agent that a turn is either spoken words or a tool call, never both.
* Do not tell it to announce the transfer; the tool's handover message already does that.
* Give each destination an explicit, mutually exclusive firing condition.
* End the prompt with success criteria naming which tool fires for which outcome, and stating that exactly one of them should ever fire.
See [tool call guidance](/voice-agent/tools/introduction#best-practices) for the general rules.
## Troubleshooting
### `destination_not_found` when creating the tool
The `workflow_id` does not exist in your organization. A transfer can only hand the call to your own agents. Check the id on the destination agent's settings page.
### The agent says transferring is unavailable
The call is running on a speech-to-speech agent, where agent transfer is not supported. Switch the agent to a cascade model configuration, or use [Call Transfer](/voice-agent/tools/call-transfer) to reach a human instead.
### The transfer is refused as already under way
Two transfer tools fired for the same call. Tighten the firing conditions in the tool descriptions so only one can match, and state in the prompt that exactly one transfer may happen.
### The transfer is refused as misconfigured
The tool's `workflow_id` is missing or is not an integer. Re-create the tool with a valid agent id.
### The destination agent re-asks something the caller already answered
Check that the destination agent's prompt tells it to use the conversation history from previous agents. Add an instruction that the caller has already spoken to a colleague and should not be asked to repeat themselves.
# Webhook
Source: https://docs.dograh.com/voice-agent/webhook
Webhook node allow you to sync the result of Voice Agent runs to your external systems, like CRM or to other workflow orchestrator like Zapier or n8n.
## Video Tutorial
You can have multiple Webhook Nodes for a single Voice Agent, if you want to sync the result of the call at multiple places.
### Creating the Webhook Payload
The payload can contain a valid JSON, and you can reference context variables while constructing it using double-brace syntax — for example `{{workflow_run_id}}`, [`{{initial_context.*}}`](/core-concepts/context-and-variables#initial_context), and [`{{gathered_context.*}}`](/core-concepts/context-and-variables#gathered_context), including the [final call outcome](../developer/call-dispositions) `{{gathered_context.call_disposition}}`.
For the full list of available variables, authentication options, and receiver examples, see the [Webhook Payloads developer reference](/developer/webhooks).
An example of the payload is given below
```json theme={null}
{
"call_id": "{{workflow_run_id}}",
"first_name": "{{initial_context.first_name}}",
"rsvp": "{{gathered_context.rsvp}}",
"disposition": "{{gathered_context.call_disposition}}",
"duration": "{{cost_info.call_duration_seconds}}",
"recording_url": "{{recording_url}}",
"transcript_url": "{{transcript_url}}"
}
```