# Create Agent Folder Source: https://docs.revring.ai/api-reference/agent-folders/create POST /agent-folders Create a new folder for organizing agents. Folder names must be unique within your organization. # Delete Agent Folder Source: https://docs.revring.ai/api-reference/agent-folders/delete DELETE /agent-folders/{id} Delete a folder. Agents in the folder are moved to Uncategorized (not deleted). # List Agent Folders Source: https://docs.revring.ai/api-reference/agent-folders/list GET /agent-folders Returns all folders for your organization plus the count of uncategorized agents (agents not in any folder). # Update Agent Folder Source: https://docs.revring.ai/api-reference/agent-folders/update PATCH /agent-folders/{id} Rename a folder or change its color. # Add Phone Number Source: https://docs.revring.ai/api-reference/agents/add-phone-number POST /agents/{id}/phone-numbers Assign a phone number to an agent # Batch Get Agents Source: https://docs.revring.ai/api-reference/agents/batch-get POST /agents/batch-get Fetch multiple agents by ID in a single request. Unknown IDs are omitted from the response. Maximum 100 IDs per request. # Create Agent Source: https://docs.revring.ai/api-reference/agents/create POST /agents Create a new AI voice agent with specified configuration # Create Custom Tool Source: https://docs.revring.ai/api-reference/agents/create-custom-tool POST /agents/{id}/custom-tools Add a custom tool that the agent can invoke via function calling # Delete Agent Source: https://docs.revring.ai/api-reference/agents/delete DELETE /agents/{id} Delete an agent # Delete Custom Tool Source: https://docs.revring.ai/api-reference/agents/delete-custom-tool DELETE /agents/{id}/custom-tools/{toolId} Remove a custom tool from an agent # Delete Environment Source: https://docs.revring.ai/api-reference/agents/delete-environment DELETE /agents/{id}/environments/{name} Removes the environment. Refused while inbound numbers are still bound to it (409 environment_in_use): move them first. `production` cannot be deleted. # Duplicate Agent Source: https://docs.revring.ai/api-reference/agents/duplicate POST /agents/{id}/duplicate Create a copy of an existing agent. Copies all configuration, custom tools, and knowledge base assignments. Phone numbers are not copied and must be assigned to the new agent separately. The new agent's name is the original name with ` (Copy)` appended. # Get Agent Source: https://docs.revring.ai/api-reference/agents/get GET /agents/{id} Retrieve specific agent by ID # Get Agent Version Source: https://docs.revring.ai/api-reference/agents/get-revision GET /agents/{id}/revisions/{revision} One version in full: the complete configuration snapshot plus a field-level comparison against the previous version. # Link Knowledge Base Source: https://docs.revring.ai/api-reference/agents/link-knowledge-base POST /agents/{id}/knowledge-bases Link a knowledge base to an agent. The agent will use the knowledge base for retrieval-augmented generation during calls. # List Agents Source: https://docs.revring.ai/api-reference/agents/list GET /agents List agents with filtering, sorting, and pagination. Default page size is 25 (maximum 100). To hydrate a known set of agents in one call, use `POST /agents/batch-get` instead. # List Custom Tools Source: https://docs.revring.ai/api-reference/agents/list-custom-tools GET /agents/{id}/custom-tools Get custom tools configured for an agent # List Agent Environments Source: https://docs.revring.ai/api-reference/agents/list-environments GET /agents/{id}/environments Environments this agent can run calls in. `production` is always first and reflects the published version. See the Versions and Publishing guide. # List Agent Knowledge Bases Source: https://docs.revring.ai/api-reference/agents/list-knowledge-bases GET /agents/{id}/knowledge-bases List knowledge bases linked to an agent # List Agent Phone Numbers Source: https://docs.revring.ai/api-reference/agents/list-phone-numbers GET /agents/{id}/phone-numbers Get phone numbers assigned to an agent for inbound calls # List Agent Versions Source: https://docs.revring.ai/api-reference/agents/list-revisions GET /agents/{id}/revisions Paginated version history for an agent, newest first. Every configuration change (agent fields, custom tools, phone numbers, knowledge base links) is recorded automatically with who made it and which fields changed. History remains readable after an agent is deleted; the final version is a `deleted` record holding the last configuration. # Publish Agent Source: https://docs.revring.ai/api-reference/agents/publish POST /agents/{id}/publish Make a version live for new calls. With an empty body the current saved draft becomes the live version and is recorded as a `published` entry in the version history (who published, when). With `{ "revision": N }` an existing version becomes live without changing the draft. In `auto` publish mode every save is already live, so this only records a version. In `manual` mode calls keep using the published version until you publish again. # Remove Phone Number Source: https://docs.revring.ai/api-reference/agents/remove-phone-number DELETE /agents/{id}/phone-numbers/{phoneId} Remove a phone number from an agent # Restore Agent Version Source: https://docs.revring.ai/api-reference/agents/restore-revision POST /agents/{id}/revisions/{revision}/restore Set the agent's configuration back to this version. Applies all agent fields and reconciles custom tools to the snapshot, using the same validation as a normal update. Phone numbers and knowledge base links are never changed by a restore. Trunk, messaging connection, and folder references are restored only if they still exist, and any that do not are reported in `skippedStaleRefs`. The restore is recorded as a new `restored` version, so the configuration you had before it stays in the history. # Unlink Knowledge Base Source: https://docs.revring.ai/api-reference/agents/unlink-knowledge-base DELETE /agents/{id}/knowledge-bases/{kbId} Remove a knowledge base from an agent # Update Agent Source: https://docs.revring.ai/api-reference/agents/update PATCH /agents/{id} Update agent configuration # Update Custom Tool Source: https://docs.revring.ai/api-reference/agents/update-custom-tool PATCH /agents/{id}/custom-tools/{toolId} Update custom tool configuration # Update Phone Number Source: https://docs.revring.ai/api-reference/agents/update-phone-number PATCH /agents/{id}/phone-numbers/{phoneId} Changes which environment inbound calls on this number run on (null for production), or splits them across versions with `trafficSplit`. # Create or Update Environment Source: https://docs.revring.ai/api-reference/agents/upsert-environment PUT /agents/{id}/environments/{name} Creates the environment when the name is new, otherwise updates the fields you send. Up to 10 environments per agent. `production` cannot be edited here (409 environment_builtin): publish instead. A `revision` must exist in the agent's version history. # Create Alert Rule Source: https://docs.revring.ai/api-reference/alerts/create POST /alert-rules # Delete Alert Rule Source: https://docs.revring.ai/api-reference/alerts/delete DELETE /alert-rules/{id} # List Alert Events Source: https://docs.revring.ai/api-reference/alerts/events GET /alert-rules/{id}/events Past notifications (newest first, up to 100) with the delivery result for each channel. # Get Alert Rule Source: https://docs.revring.ai/api-reference/alerts/get GET /alert-rules/{id} # List Alert Rules Source: https://docs.revring.ai/api-reference/alerts/list GET /alert-rules # Send Test Alert Source: https://docs.revring.ai/api-reference/alerts/test POST /alert-rules/{id}/test Sends an `alert.test` notification to the rule's channels with the current value. # Update Alert Rule Source: https://docs.revring.ai/api-reference/alerts/update PATCH /alert-rules/{id} Changes take effect at the next check (within 5 minutes); a firing rule whose new condition is not met resolves. # Get Alert Value Source: https://docs.revring.ai/api-reference/alerts/value GET /alert-rules/{id}/value The rule's metric right now. Nothing is sent. # Get Active Calls Count Source: https://docs.revring.ai/api-reference/analytics/active GET /analytics/active Get the current number of active (ongoing) calls. Not affected by date filters. Also returns `concurrency`: `active` (calls dialing or connected), `limit` (your plan's concurrent calls) and `available`, counted exactly like the check that refuses new calls with 429 concurrency_limit, so a dialer can pace itself. # Get Analytics Overview Source: https://docs.revring.ai/api-reference/analytics/overview GET /analytics/overview Get analytics overview with time-series data and summary cards # Compare Versions Source: https://docs.revring.ai/api-reference/analytics/versions GET /analytics/versions An agent's calls grouped by the version they ran on (environment and revision), to compare an A/B split. Default period: the last 7 days; at most 90. # Batch Get Calls Source: https://docs.revring.ai/api-reference/calls/batch-get POST /calls/batch-get Fetch multiple calls by ID in a single request. Unknown IDs are omitted from the response. Maximum 100 IDs per request. # Cancel Call Source: https://docs.revring.ai/api-reference/calls/cancel POST /calls/{id}/cancel Cancel a queued call. Only calls with status `QUEUED` can be cancelled. # End Call Source: https://docs.revring.ai/api-reference/calls/end POST /calls/{id}/end Hang up a call that is in progress, immediately. The caller is disconnected, the call is recorded with hangupCause `API_ENDED`, and the recording, summary, post-call analysis and post-call webhook are produced as for any other call. Use it from your own logic, for example when your system decides the call reached voicemail or should stop. # Export Calls Source: https://docs.revring.ai/api-reference/calls/export GET /calls/export CSV of calls, newest first, up to 10,000 per file: call details, sentiment, summary, recording URL, one column per call variable (var.*), extracted field (extraction.*) and success criterion result (evaluation.*). Cells that would run as a spreadsheet formula are prefixed with an apostrophe. # Get Call Source: https://docs.revring.ai/api-reference/calls/get GET /calls/{id} Retrieve call details by ID # List Calls Source: https://docs.revring.ai/api-reference/calls/list GET /calls List calls with filtering, sorting, and pagination # Re-run Analysis Source: https://docs.revring.ai/api-reference/calls/reanalyze POST /calls/{id}/reanalyze Re-run extraction, evaluation and caller sentiment on one ended call with the agent's current settings. A field or criterion removed from the agent is cleared on the call. # Re-run Analysis (bulk) Source: https://docs.revring.ai/api-reference/calls/reanalyze-bulk POST /calls/reanalyze Re-run extraction, evaluation and caller sentiment with the agent's current settings on up to 50 of its ended calls in a time window, newest first. Calls with no caller speech or with deleted content are skipped. # Send Call Source: https://docs.revring.ai/api-reference/calls/send POST /calls Initiate an outbound call with an agent # Add Campaign Contacts Source: https://docs.revring.ai/api-reference/campaigns/add-contacts POST /campaigns/{id}/contacts # Cancel Campaign Source: https://docs.revring.ai/api-reference/campaigns/cancel POST /campaigns/{id}/cancel Stop for good; contacts not yet called become canceled. # Create Campaign Source: https://docs.revring.ai/api-reference/campaigns/create POST /campaigns Create a campaign, optionally with contacts (up to 10,000) and optionally started at once with `start: true`. Duplicate numbers are skipped. # Delete Campaign Source: https://docs.revring.ai/api-reference/campaigns/delete DELETE /campaigns/{id} Delete a draft, completed or canceled campaign and its contacts. Cancel a running campaign first. Calls it placed are kept. # Export Campaign Results Source: https://docs.revring.ai/api-reference/campaigns/export GET /campaigns/{id}/export CSV with one row per contact and its last call: status, outcome, attempts, duration, summary, recording, `var.*`, `extraction.*` and `evaluation.*` columns. # Get Campaign Source: https://docs.revring.ai/api-reference/campaigns/get GET /campaigns/{id} # List Campaigns Source: https://docs.revring.ai/api-reference/campaigns/list GET /campaigns # List Campaign Contacts Source: https://docs.revring.ai/api-reference/campaigns/list-contacts GET /campaigns/{id}/contacts # Pause Campaign Source: https://docs.revring.ai/api-reference/campaigns/pause POST /campaigns/{id}/pause Stop new dials. Calls in progress finish and are recorded. # Start Campaign Source: https://docs.revring.ai/api-reference/campaigns/start POST /campaigns/{id}/start Start a draft campaign or resume a paused one. # Update Campaign Source: https://docs.revring.ai/api-reference/campaigns/update PATCH /campaigns/{id} Change settings of a campaign that is not completed or canceled. Takes effect on the next dial. # Delete Conversation Source: https://docs.revring.ai/api-reference/conversations/delete DELETE /conversations/{id} Clear a conversation thread and its logged messages, so the next text from that contact starts fresh. Use it to re-test an agent as a new contact, or to forget a contact. # List Conversations Source: https://docs.revring.ai/api-reference/conversations/list GET /conversations List managed conversation threads (one per contact per agent number), newest activity first. Offset-paginated. # Add Do-Not-Call Numbers Source: https://docs.revring.ai/api-reference/do-not-call/add POST /do-not-call Campaigns never dial these numbers. # List Do-Not-Call Numbers Source: https://docs.revring.ai/api-reference/do-not-call/list GET /do-not-call # Remove Do-Not-Call Number Source: https://docs.revring.ai/api-reference/do-not-call/remove DELETE /do-not-call/{e164} # Import Agents Source: https://docs.revring.ai/api-reference/imports/import POST /imports Import agents from Retell AI or ElevenLabs Agents. A dry run (the default) returns a per-setting report and creates nothing. With `dryRun: false` the agents are created with their custom tools and knowledge bases. Importing an agent again updates the agent it created before. See [Import Agents](/platform/importing-agents) for what carries over and how to read the report. # List Source Agents Source: https://docs.revring.ai/api-reference/imports/list-agents POST /imports/agents List the voice agents on your Retell AI or ElevenLabs account, to choose which to import. # Introduction Source: https://docs.revring.ai/api-reference/introduction How to access and use the RevRing API You can use the RevRing API in automation tools such as N8N, Make, GoHighLevel, and more to automate any kind of call sequence or logic! ## Creating an API Key To begin using the RevRing API, you must first create an API Key from the dashboard. API Keys are sensitive credentials, so ensure to store them securely and limit access. Create a new API Key from the RevRing Dashboard ## Authentication All API endpoints are authenticated using the `x-api-key` header: ```bash theme={null} curl --request GET \ --url https://api.revring.ai/v1/agents \ --header 'x-api-key: ' ``` # Add Source Source: https://docs.revring.ai/api-reference/knowledge-bases/add-source POST /knowledge-bases/{id}/sources Add a source to a knowledge base. Supports three source types: - **Text**: Send JSON with `sourceType: "text"`, `displayName`, and `content` - **URL**: Send JSON with `sourceType: "url"` and `originalUrl`; add `crawl` to read linked pages of the same site and/or refresh on a schedule - **File**: Send `multipart/form-data` with a `file` field (max 20MB) Maximum of 50 sources per knowledge base. Sources are processed asynchronously. # Create Knowledge Base Source: https://docs.revring.ai/api-reference/knowledge-bases/create POST /knowledge-bases Create a new knowledge base. After creation, add sources (text, URL, or file) to populate it. # Delete Knowledge Base Source: https://docs.revring.ai/api-reference/knowledge-bases/delete DELETE /knowledge-bases/{id} Delete a knowledge base and all its sources. This also removes all agent links. # Delete Source Source: https://docs.revring.ai/api-reference/knowledge-bases/delete-source DELETE /knowledge-bases/{id}/sources/{sourceId} Remove a source from a knowledge base # Get Knowledge Base Source: https://docs.revring.ai/api-reference/knowledge-bases/get GET /knowledge-bases/{id} Retrieve a knowledge base by ID, including its sources # Get Source Source: https://docs.revring.ai/api-reference/knowledge-bases/get-source GET /knowledge-bases/{id}/sources/{sourceId} Get a specific source and its processing status # List Knowledge Bases Source: https://docs.revring.ai/api-reference/knowledge-bases/list GET /knowledge-bases List all knowledge bases for your organization # Refresh Source Source: https://docs.revring.ai/api-reference/knowledge-bases/refresh-source POST /knowledge-bases/{id}/sources/{sourceId}/refresh Read a URL source again now, with its crawl settings. Runs in the background. Agents keep using the current content until the new content is fully indexed; unchanged content is not replaced, and a failed read keeps the current content. # Update Knowledge Base Source: https://docs.revring.ai/api-reference/knowledge-bases/update PATCH /knowledge-bases/{id} Update knowledge base name or description # Get Message Source: https://docs.revring.ai/api-reference/messages/get GET /messages/{id} Retrieve a single message from your organization's log by ID. # List Messages Source: https://docs.revring.ai/api-reference/messages/list GET /messages Retrieve the message log for your organization, newest first. Offset-paginated. # Send Message Source: https://docs.revring.ai/api-reference/messages/send POST /messages Send an SMS. Provide either a raw `body` or set `generate` to true to compose the message with AI from the agent's SMS prompt and optional inline history. Sends through your connected provider. # Create Messaging Connection Source: https://docs.revring.ai/api-reference/messaging-connections/create POST /messaging-connections Connect a messaging provider. Credentials are verified with the provider before the connection is saved. The Auth Token is stored securely and is never returned. # Delete Messaging Connection Source: https://docs.revring.ai/api-reference/messaging-connections/delete DELETE /messaging-connections/{id} Delete a messaging connection. Cannot delete if agents are still using it. # Get Messaging Connection Source: https://docs.revring.ai/api-reference/messaging-connections/get GET /messaging-connections/{id} Retrieve a specific messaging connection by ID. Secrets are never returned. # List Messaging Connections Source: https://docs.revring.ai/api-reference/messaging-connections/list GET /messaging-connections Retrieve all messaging provider connections for your organization. Secrets (such as the provider Auth Token) are never returned. # Update Messaging Connection Source: https://docs.revring.ai/api-reference/messaging-connections/update PATCH /messaging-connections/{id} Update a messaging connection. If credentials change they are re-verified with the provider. Provide authToken only to rotate it. # Connect Twilio Source: https://docs.revring.ai/api-reference/sip-trunks/connect-twilio POST /sip-trunks/connect/twilio Connects Twilio numbers in one step: creates an Elastic SIP trunk in your Twilio account with origination to sip.revring.ai and a fresh termination credential, moves the chosen numbers onto it, and creates the matching RevRing trunk. With agentId, the numbers are also assigned to that agent for inbound calls (and the trunk becomes its outbound trunk if it has none). If any step fails, everything created in Twilio is removed and your numbers keep their current routing. # Create SIP Trunk Source: https://docs.revring.ai/api-reference/sip-trunks/create POST /sip-trunks Create a new SIP trunk. Provide phone numbers that will route through this trunk. # Delete SIP Trunk Source: https://docs.revring.ai/api-reference/sip-trunks/delete DELETE /sip-trunks/{id} Delete a SIP trunk. Cannot delete if agents are using it. # Get SIP Trunk Source: https://docs.revring.ai/api-reference/sip-trunks/get GET /sip-trunks/{id} Retrieve a specific SIP trunk by ID # List SIP Trunks Source: https://docs.revring.ai/api-reference/sip-trunks/list GET /sip-trunks Retrieve all SIP trunks for your organization. SIP trunks connect your AI agents to the phone network. # List Available Phone Numbers Source: https://docs.revring.ai/api-reference/sip-trunks/list-available-numbers GET /sip-trunks/{id}/available-numbers Returns the trunk's phone numbers split into those that are available to assign and those already assigned to agents. Useful when assigning an inbound number to an agent to see what is free without having to track assignments manually. # List Twilio Numbers Source: https://docs.revring.ai/api-reference/sip-trunks/list-twilio-numbers POST /sip-trunks/connect/twilio/numbers Lists the phone numbers on a Twilio account and where each one routes today, so you can choose which to connect. Numbers already on a RevRing trunk are flagged. # Update SIP Trunk Source: https://docs.revring.ai/api-reference/sip-trunks/update PATCH /sip-trunks/{id} Update SIP trunk configuration including phone numbers # Cancel Test Run Source: https://docs.revring.ai/api-reference/test-suites/cancel-run POST /test-suites/{id}/runs/{runId}/cancel Cancel a running or queued test run. Already completed or cancelled runs cannot be cancelled. # Create Test Suite Source: https://docs.revring.ai/api-reference/test-suites/create POST /test-suites Create a new test suite targeting a specific agent # Create Test Case Source: https://docs.revring.ai/api-reference/test-suites/create-test POST /test-suites/{id}/tests Add a test case to a test suite. Each test case includes a script for the tester to follow and a scoring rubric for evaluation. # Create Test From Call Source: https://docs.revring.ai/api-reference/test-suites/create-test-from-call POST /test-suites/{id}/tests/from-call Draft a test case from a real call: the caller's side becomes the script and the call's failed success criteria (or `focus`) the scoring rubric. The call's variables are replayed. Saved to the suite unless `save` is false. # Delete Test Suite Source: https://docs.revring.ai/api-reference/test-suites/delete DELETE /test-suites/{id} Delete a test suite and all its test cases and runs # Delete Test Case Source: https://docs.revring.ai/api-reference/test-suites/delete-test DELETE /test-suites/{id}/tests/{testId} Remove a test case from a test suite # Create Suite From Failing Calls Source: https://docs.revring.ai/api-reference/test-suites/from-failing-calls POST /test-suites/from-failing-calls Build a test suite from an agent's recent calls that failed one evaluation criterion (each call becomes a test, as in Create Test From Call) and start a run. # Generate Test Cases Source: https://docs.revring.ai/api-reference/test-suites/generate-tests POST /test-suites/{id}/generate-tests Use AI to generate test case suggestions based on a natural language prompt. Generated tests are returned as suggestions and are not automatically added to the suite. # Get Test Suite Source: https://docs.revring.ai/api-reference/test-suites/get GET /test-suites/{id} Retrieve a test suite by ID # Get Test Run Source: https://docs.revring.ai/api-reference/test-suites/get-run GET /test-suites/{id}/runs/{runId} Retrieve a specific test run with its results # Get Suggested Fix for a Run Source: https://docs.revring.ai/api-reference/test-suites/get-run-suggested-fix GET /test-suites/{id}/runs/{runId}/suggest-fix Fetch the run-level suggestion. Poll this every couple of seconds after requesting one: `status` stays `running` while it is being prepared and becomes `ready` when `result` holds the suggestion. `failed` means it could not be prepared; request a new one. # Get Suggested Fix Source: https://docs.revring.ai/api-reference/test-suites/get-suggested-fix GET /test-suites/{id}/attempts/{attemptId}/suggest-fix Fetch the suggestion for a failed test attempt. Poll this every couple of seconds after requesting one: `status` stays `running` while it is being prepared and becomes `ready` when `result` holds the suggestion. `failed` means it could not be prepared; request a new one. # Get Test Case Source: https://docs.revring.ai/api-reference/test-suites/get-test GET /test-suites/{id}/tests/{testId} Retrieve a specific test case # List Test Suites Source: https://docs.revring.ai/api-reference/test-suites/list GET /test-suites List all test suites for your organization with pagination # List Test Runs Source: https://docs.revring.ai/api-reference/test-suites/list-runs GET /test-suites/{id}/runs List test runs for a test suite with pagination # List Test Cases Source: https://docs.revring.ai/api-reference/test-suites/list-tests GET /test-suites/{id}/tests List all test cases in a test suite # Start Test Run Source: https://docs.revring.ai/api-reference/test-suites/start-run POST /test-suites/{id}/runs Start a new test run. This initiates real phone calls between a tester and the target agent for each test case. The suite must have at least one test case. # Suggest a Fix Source: https://docs.revring.ai/api-reference/test-suites/suggest-fix POST /test-suites/{id}/attempts/{attemptId}/suggest-fix Ask for a prompt change that would make a failed test attempt pass. Preparing a suggestion takes time, especially for agents with long prompts, so this starts the work and waits up to 105 seconds. If the suggestion is ready in that time it is returned directly; if not, the response is `status: running` and you fetch it with Get Suggested Fix. Pass `wait=0` to skip the wait and always get `status: running` straight away. This is the recommended pattern, because the request never depends on how long preparation takes. Asking again while a suggestion is already being prepared joins the one in progress instead of starting another, and a suggestion prepared within the last 10 minutes is returned immediately, so repeating a request after a timeout is instant. Suggestions are available for failed attempts only. # Suggest a Fix for a Run Source: https://docs.revring.ai/api-reference/test-suites/suggest-run-fix POST /test-suites/{id}/runs/{runId}/suggest-fix Ask for one prompt change that would make every failed test in a run pass. Requesting a fix per attempt produces separate prompts that do not know about each other, and applying one can undo another. This endpoint analyzes all of the run's failed attempts together and returns a single corrected prompt with a per-attempt breakdown, so failures that share a root cause get one coherent edit. The wait, polling, and reuse behavior is identical to the per-attempt Suggest a Fix: the request waits up to 105 seconds, `wait=0` returns `status: running` immediately for polling with Get Suggested Fix for a Run, asking again joins a preparation already in progress, and a suggestion prepared within the last 10 minutes is returned instantly. The run must be finished and must contain at least one failed attempt. On runs with more than 12 failures, the most recent 12 are analyzed and the rest are listed in `result.skippedAttemptIds`. # Update Test Suite Source: https://docs.revring.ai/api-reference/test-suites/update PATCH /test-suites/{id} Update test suite configuration # Update Test Case Source: https://docs.revring.ai/api-reference/test-suites/update-test PATCH /test-suites/{id}/tests/{testId} Update a test case's script, rubric, or configuration # Create Voice Source: https://docs.revring.ai/api-reference/voices/create POST /voices Create a custom voice for your organization from an audio sample. Send `multipart/form-data` with a display name and a clear recording of a single speaker (about 10 to 30 seconds). The new voice can be assigned to any agent via its `voiceId`. # Delete Voice Source: https://docs.revring.ai/api-reference/voices/delete DELETE /voices/{id} Delete one of your organization's custom voices. Built-in voices cannot be deleted. Any agent still using the voice falls back to a default voice. # Design Voices Source: https://docs.revring.ai/api-reference/voices/design POST /voices/design Generate candidate voices from a plain-text description. Returns one or more options, each with a short audio preview. Save the one you like with Create Voice. # Get Voice Source: https://docs.revring.ai/api-reference/voices/get GET /voices/{id} Retrieve a specific voice by ID # List Voices Source: https://docs.revring.ai/api-reference/voices/list GET /voices Retrieve the voices available to your organization. Use a voice's `id` as an agent's `voiceId` to set how it speaks. # Preview Voice Source: https://docs.revring.ai/api-reference/voices/preview GET /voices/{id}/preview Get a short spoken preview of a voice as an MP3. Optionally provide your own text. # Rename Voice Source: https://docs.revring.ai/api-reference/voices/rename PATCH /voices/{id} Rename one of your organization's custom voices. Built-in voices cannot be changed. # Add Studio Voice Source: https://docs.revring.ai/api-reference/voices/studio-add POST /voices/studio Add a Studio library voice to your organization's voices. The returned voice `id` is used like any other voice (an agent's `voiceId`, or a per-call `voiceId`). Adding the same library voice again returns the existing voice. Calls that use a Studio voice are billed an additional 2 cents per minute. # Search Studio Library Source: https://docs.revring.ai/api-reference/voices/studio-library GET /voices/studio/library Search the Studio library of premium voices. Results are display-only; add one to your organization with Add Studio Voice. Calls that use a Studio voice are billed an additional 2 cents per minute. # Preview Studio Voice Source: https://docs.revring.ai/api-reference/voices/studio-preview GET /voices/studio/library/{voiceId}/preview Get a short spoken preview of a Studio library voice as an MP3, before adding it. Optionally provide your own text. # Inbound Message Webhook Source: https://docs.revring.ai/api-reference/webhooks/message-received webhook message-received Sent when someone texts an agent whose inbound mode is 'notify'. RevRing forwards the incoming message so your system can reply (via the Send Message API). In 'auto_reply' mode RevRing replies automatically and no webhook is sent. # Post-Call Webhook Source: https://docs.revring.ai/api-reference/webhooks/post-call webhook post-call Called after a call ends with complete call details including transcript, recording, and summary. # Pre-Call Webhook Source: https://docs.revring.ai/api-reference/webhooks/pre-call webhook pre-call Called before an inbound call is answered. Return variables to be merged with the agent's default variables for this call. # Disable Webhook Signing Source: https://docs.revring.ai/api-reference/webhooks/signing-disable DELETE /webhooks/signing-secret Removes the secret; webhooks are sent unsigned again. # Webhook Signing Secret Source: https://docs.revring.ai/api-reference/webhooks/signing-secret POST /webhooks/signing-secret Creates the signing secret, or replaces it. Every webhook RevRing sends your organization (pre-call, post-call, message received) is then signed with `X-RevRing-Signature` (see the Webhooks guide for verification). The full secret is returned only by this call. After a rotation the new secret is live within a minute. # Get Webhook Signing State Source: https://docs.revring.ai/api-reference/webhooks/signing-state GET /webhooks/signing-secret Whether webhooks sent to your organization are signed, and the last four characters of the live secret. # What's New Source: https://docs.revring.ai/changelog Platform changes that affect how you build with RevRing, newest first. ## October 6, 2026 ### Do-not-call requests, handled automatically Turn on `doNotCallDetection` (or **Detect do-not-call requests** on an agent's Advanced tab): when a caller asks not to be called again, the call shows `doNotCallRequested: true`, the post-call webhook carries it, and their number joins your do-not-call list so campaigns never dial it. Declining an offer or asking for a call back later does not count. [Guide](/platform/campaigns#add-callers-who-ask-automatically) ### A/B test agent versions Split an inbound number's calls between versions of an agent with `trafficSplit` (2 to 5 environments with weights), then compare them with `GET /v1/analytics/versions`: failure rate, call length, negative sentiment and success-criteria pass rates per version. [Guide](/platform/versioning#split-calls-between-versions) ### Response latency on every call Each call's `metrics.latency` now shows how quickly the agent answered: the caller's wait from the end of their speech to the agent's reply, broken into turn detection, transcription, thinking and voice, each as p50, p90 and max across the call's turns. [Guide](/platform/webhooks#response-latency) ### One agent, many languages Pass `language` when you place a call (or from your pre-call webhook for inbound calls) to run that call in the caller's language, and give the agent `languagePresets`: a voice and opening line per language, used whenever a call runs in that language. Agents imported from ElevenLabs bring their per-language opening lines. [Guide](/platform/multilingual#one-agent-many-languages) ### Alerts Hear about problems before your customers do. Create alerts on failed calls, calls failing a success criterion, negative caller sentiment, tool errors, undelivered post-call webhooks or call volume, for one agent or all, over 15 minutes to 24 hours. Each alert sends one webhook or Slack message when it fires and one when it resolves, with a test button and a history of every notification. Open **Alerts** in the dashboard or use `/v1/alert-rules`. [Guide](/platform/alerts) ### Knowledge from whole websites, kept current A URL source can now read a whole help center or documentation site: add `crawl` with `maxPages` (up to 50) and an optional `includePath`, and RevRing follows the site's links under that path into one source. Set `refreshDays` to read the pages again on a schedule, or refresh a source on demand with `POST /v1/knowledge-bases/{id}/sources/{sourceId}/refresh`. Agents keep answering from the current content during a refresh, and new content replaces it only once it is fully indexed. [Guide](/platform/knowledge-bases#whole-websites) ### Caller sentiment, re-run analysis and call export Every call with caller speech now carries `sentiment` (positive, neutral, negative or unknown) on the call and in the post-call webhook; Retell-format webhooks fill `user_sentiment` from it. After changing an agent's extraction fields or success criteria, re-run the analysis on past calls (`POST /v1/calls/{id}/reanalyze`, up to 50 at once with `POST /v1/calls/reanalyze`, or **Re-run analysis** on a call). Download call history as CSV with sentiment, variables, extracted data and evaluation results (`GET /v1/calls/export`, or **Export CSV** on Call Logs). [Guide](/platform/webhooks#caller-sentiment) ### Transfer events on your webhook Add `transfer.started`, `transfer.bridged` and `transfer.cancelled` to an agent's `webhookEvents` (or turn on **Also send transfer events**) to hear about transfers as they happen: started (warm or cold, with the destination), connected, or not completed (declined, voicemail or failed). Agents using the Retell compatibility format receive Retell's `transfer_started`, `transfer_bridged` and `transfer_cancelled`. [Guide](/platform/webhooks#transfer-events) ### Listen to calls live Open any call in progress in the dashboard and select **Listen** to hear both sides as it happens. Nobody on the call can hear you or knows you are listening, so the call is never affected. The transcript on the same page updates every few seconds while the call runs. ### Remove personal details from call transcripts Set an agent's `storageMode` to `redact_pii` (or choose it under **Advanced > Call data storage**) and names, phone numbers, emails, addresses, dates of birth, ID, card, account and medical record numbers, and passwords are replaced with labels like `[phone_number]` in every call's transcript and summary, before the call is stored or sent to your webhook. Pick categories with `piiCategories`. Calls show `piiRedacted: true`. Agents imported from Retell AI or ElevenLabs carry their PII settings over. [Guide](/platform/data-controls) ## October 5, 2026 ### Connect Twilio in one step Bring Twilio numbers over with your Account SID and Auth Token: RevRing creates the Elastic SIP trunk, points it here, moves the numbers you pick and can assign them to an agent. From the dashboard (**SIP Trunks > Connect Twilio**) or `POST /v1/sip-trunks/connect/twilio`. [Guide](/platform/telephony#connect-twilio-in-one-step) ### Fix failing calls Build a test suite from an agent's recent calls that failed a success criterion and run it, in one step from the dashboard Overview or `POST /v1/test-suites/from-failing-calls`. Success criteria now count a skipped required action as `fail`; an outcome the call never reached stays `unknown`. [Guide](/platform/test-suites#fix-failing-calls) ### Create a test from a real call One click on a call (or `POST /v1/test-suites/{id}/tests/from-call`) turns it into a test case: the caller's side becomes the script, the failed success criteria the rubric. [Guide](/platform/test-suites#from-a-real-call) ### Call started event Set `webhookEvents: ["call.started"]` on an agent to get a webhook when each call connects, in RevRing's shape or Retell's `call_started`. [Guide](/platform/webhooks#call-started-event) ### Route inbound calls from your pre-call webhook The pre-call webhook's answer can now pick the agent or environment for the call, override the opening line, voice or prompt, or reject the call. Retell and ElevenLabs inbound webhook answers work unchanged. [Guide](/platform/webhooks#route-personalize-or-reject-the-call) ### Manage RevRing from Claude, Cursor and VS Code A management MCP server at `https://api.revring.ai/v1/mcp` lets AI tools list and edit agents, place and end calls, and read transcripts and results with your API key. [Guide](/mcp-server) ### Keep your webhook handler when you switch Set `postCallWebhookFormat` to `retell` or `elevenlabs` and the post-call webhook arrives in that platform's payload shape, filled from the RevRing call record. The importer sets it automatically. [Guide](/platform/webhooks#compatibility-formats) ### Knowledge bases hold 50 sources The limit per knowledge base is now 50 sources (was 10). ### Campaigns Call a contact list with one agent. Contacts without a time zone are called in the local time of their number's area code, and `voicemailStrategy: "last_attempt"` leaves a voicemail only on the final attempt. Calling windows follow each contact's own time zone, retries happen automatically by outcome (no answer, busy, voicemail, declined, failed), new calls are paced to suit your carrier, numbers on your do-not-call list are never dialed, and a CSV export carries every contact's outcome with extraction and evaluation columns. Campaign calls carry `campaignId` on the call record and in the post-call webhook. [Guide](/platform/campaigns) ### Import agents from Retell AI and ElevenLabs Bring existing voice agents over with your API key for the other platform. A dry run reports how every setting carries over (prompt, opening, variables, tools, transfers, voicemail, knowledge, post-call analysis) and what is left to you; importing creates the agents with their tools and knowledge bases, and importing again updates them. [Guide](/platform/importing-agents) ### End a live call from your server `POST /v1/calls/{id}/end` hangs up a call in progress immediately; it is recorded as `API_ENDED` and still gets its recording, summary and post-call webhook. Use it when your own logic decides the call should stop. [Reference](/api-reference/calls/end) ### Concurrency headroom `GET /v1/analytics/active` now also returns `concurrency` (`active`, `limit`, `available`), counted exactly like the check behind `concurrency_limit`, so dialers can pace calls instead of retrying. [Reference](/api-reference/analytics/active) ### Spoken text never reads a placeholder aloud If an opening line, voicemail message, hold or intro message references a `{{variable}}` that has no value on a call, the placeholder is dropped and the sentence tidied ("Hi , this is Maya" becomes "Hi, this is Maya") instead of being read out. ### Webhook signing Every webhook RevRing sends (pre-call, post-call, message received) can now be signed. Create a secret with `POST /v1/webhooks/signing-secret` and verify the `X-RevRing-Signature` header on the raw body. Opt-in per organization; nothing changes until you create a secret. [Guide](/platform/webhooks#verify-webhook-sources) ### Webhook retries A post-call webhook that fails (connection error, timeout, non-2xx) is retried 1, 5 and 30 minutes later. Handlers should be idempotent on the call `id`. The outcome is on the call record as `postCallWebhookStatus` and `postCallWebhookAttempts`. [Guide](/platform/webhooks#response-expectations) ### Test runs against a live version Test runs use the saved draft by default. Pass `environment` (`production` or an environment name) on `POST /test-suites/{id}/runs` to test the version callers get. [Guide](/platform/versioning#testing-before-you-publish) ## October 4, 2026 ### Drafts, publishing and environments Agents can now be edited without going live. Switch an agent to manual publishing (`publishMode: "manual"`), edit the draft, test it, and publish when ready; earlier versions can be made live in one call. Named environments pin a version with their own variable defaults and can be bound to inbound numbers, the web widget and `POST /calls`. Every call record carries the version it ran on. [Guide](/platform/versioning) ### Post-call data extraction Define typed fields per agent (`postCallExtraction`) and get them filled from each conversation on the call record and in the post-call webhook as `extraction`. [Guide](/platform/webhooks#post-call-data-extraction) ### Post-call evaluation Write success criteria per agent (`postCallEvaluation`) and every call is judged pass, fail or unknown with a one-sentence reason. Pass rates per criterion are on the analytics overview and the dashboard. [Guide](/platform/webhooks#post-call-evaluation) ### SIP trunk address validation A trunk's proxy must now be a hostname or IP that resolves. Typos such as a misspelled carrier domain are refused when the trunk is created instead of producing a trunk that can never place a call. [Reference](/api-reference/sip-trunks/create) ### Noise suppression for every agent Background noise and other voices on the caller's side are suppressed before the agent listens. On by default for all agents; turn it off per agent with `noiseCancellation: "off"`. # Introduction Source: https://docs.revring.ai/index Welcome to the RevRing AI documentation website ## Setting up Get your first RevRing agent set up and running in minutes. Follow our quickstart guide to handle your first AI phone call ## Platform Guides for telephony setup, inbound and outbound calling, tools, and webhooks. Connect SIP trunks from Twilio, Telnyx, or any telephony provider. Define and configure tools for agents to use during calls. Handle inbound calls on any of your phone numbers. Send outbound calls through our dashboard or API. ## API Reference View our full API reference and copy examples to build on top of the RevRing API. View all available RevRing API endpoints and how to use them. ## Need help? Contact us anytime through Slack or email! # MCP Servers Source: https://docs.revring.ai/mcp-server Manage agents and calls, and search RevRing documentation, from AI tools like Claude, Cursor and VS Code RevRing hosts two MCP (Model Context Protocol) servers: * **Management server** at `https://api.revring.ai/v1/mcp`: your AI tool works with your RevRing account. It can list and edit agents, place and end calls, and read transcripts and results. * **Documentation server** at `https://docs.revring.ai/mcp`: your AI tool searches the RevRing docs. ## Management server ``` https://api.revring.ai/v1/mcp ``` Authenticate with an API key from **Developer > API Keys**, sent as a bearer token (`Authorization: Bearer rr_...`) or as `x-api-key`. Everything the tools do is scoped to that key's organization and runs through the same validation as the REST API. | Tool | What it does | | - | - | | `list_agents` | List agents, optionally filtered by name | | `get_agent` | Full configuration of one agent | | `create_agent` | Create an agent (same fields as [Create Agent](/api-reference/agents/create)) | | `update_agent` | Change an agent's settings (same fields as [Update Agent](/api-reference/agents/update)) | | `list_calls` | Recent calls, filtered by agent, status, direction, numbers or dates | | `get_call` | One call with transcript, summary, extraction, evaluation and recording | | `place_call` | Call a number with an agent | | `end_call` | Hang up a call in progress | | `list_voices` | Voices available to your organization | Read-only tools are marked as such, so clients can ask before running anything that changes your account or places a call. **Claude Code** ```bash theme={null} claude mcp add --transport http revring https://api.revring.ai/v1/mcp --header "Authorization: Bearer $REVRING_API_KEY" ``` **Cursor** (`~/.cursor/mcp.json`) and **VS Code** (`.vscode/mcp.json`, under `servers` with `"type": "http"`): ```json theme={null} { "mcpServers": { "revring": { "url": "https://api.revring.ai/v1/mcp", "headers": { "Authorization": "Bearer rr_your_api_key" } } } } ``` Then ask, for example: *"List my agents and show me the last five failed calls for the support agent"* or *"Raise the support agent's turn timeout to 10 seconds."* An API key gives full access to your organization. Use a dedicated key for AI tools so you can revoke it on its own. ## Documentation server RevRing hosts an MCP (Model Context Protocol) server that lets AI tools search our documentation directly. This means tools like Cursor, VS Code Copilot, Claude, and Claude Code can pull accurate, up-to-date information from RevRing docs while helping you build integrations. ``` https://docs.revring.ai/mcp ``` Whether you're writing API integration code, configuring agents programmatically, or building custom workflows — connecting this MCP server gives your AI assistant direct access to every guide, API reference, and code example in our documentation. ## Connect to Cursor Install RevRing MCP server in Cursor Click the button above to install the RevRing MCP server in Cursor. Or configure it manually: Add the following to `.cursor/mcp.json` in your project directory (or `~/.cursor/mcp.json` for global access): ```json theme={null} { "mcpServers": { "RevRing": { "url": "https://docs.revring.ai/mcp" } } } ``` To verify it's working, ask Cursor a question about RevRing — for example, *"How do I send an outbound call with the RevRing API?"* ## Connect to VS Code [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=RevRing\&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fdocs.revring.ai%2Fmcp%22%7D) — click to add the RevRing MCP server automatically. Or configure it manually: Create a `.vscode/mcp.json` file in your project root: ```json theme={null} { "servers": { "RevRing": { "type": "http", "url": "https://docs.revring.ai/mcp" } } } ``` Open GitHub Copilot chat and ask a question about RevRing. VS Code may prompt you to trust the MCP server on first use — select **Allow** to enable it. ## Connect to Claude Navigate to the [Connectors](https://claude.ai/settings/connectors) page in your Claude settings. Select **Add custom connector** and enter: * **Name**: `RevRing` * **URL**: `https://docs.revring.ai/mcp` When chatting with Claude, select the attachments button (the **+** icon) and choose the RevRing connector. Claude can now search RevRing docs to answer your questions. ## Connect to Claude Code Run the following command in your terminal: ```bash theme={null} claude mcp add --transport http RevRing https://docs.revring.ai/mcp ``` Verify the connection: ```bash theme={null} claude mcp list ``` ## Connect to ChatGPT In ChatGPT, navigate to your Connectors settings and create a new connector with the URL: ``` https://docs.revring.ai/mcp ``` Start a new chat, select the **+** menu, choose **More**, and select the RevRing connector. ## What's available The MCP server exposes a **search tool** that AI applications use to query all indexed RevRing documentation. This covers: * **Platform guides** — telephony setup, inbound/outbound calls, agent tools, knowledge bases, webhooks, and more * **API reference** — every endpoint, request/response schema, and error code * **Code examples** — integration patterns and configuration samples Most AI clients will call the search tool automatically when your question is relevant to RevRing. You can also explicitly ask your AI tool to search RevRing docs. ## Use cases * **Building API integrations** — ask your AI tool to write code that sends calls, manages agents, or processes webhooks, and it will pull the correct endpoints and schemas from RevRing docs * **Configuring agents** — get accurate instructions for setting up tools, knowledge bases, conversation flows, and advanced settings * **Debugging** — describe an issue and your AI tool can search for relevant error codes, troubleshooting steps, and configuration requirements * **Learning the platform** — explore RevRing capabilities through natural conversation with any connected AI tool # Alerts Source: https://docs.revring.ai/platform/alerts Get a webhook or Slack message when failed calls, tool errors, negative sentiment or call volume cross a threshold. Alerts watch your calls and tell you when something needs attention: more calls failing than usual, a tool your agent depends on returning errors, callers getting frustrated, or call volume dropping to nothing. Each alert sends one message when it fires and one when it resolves. ## Create an alert **Dashboard:** open **Alerts** in the sidebar and click **New alert**. Pick what to watch, which agent's calls (or all agents), the threshold and the time window, and where to send it. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/alert-rules \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Failed calls on the booking line", "agentId": "AGENT_ID", "metric": "call_failure_rate", "comparator": "above", "threshold": 20, "windowMinutes": 60, "minCalls": 10, "webhookUrl": "https://example.com/revring-alerts", "slackWebhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX" }' ``` | Field | Description | | - | - | | `name` | Shown in notifications and the dashboard. | | `metric` | What to watch (see below). | | `agentId` | Only this agent's calls. Leave it out for all agents. | | `comparator` | `above` (default) or `below`. | | `threshold` | A percent from 0 to 100 for rates, or a number of calls for `call_count`. | | `windowMinutes` | The period looked at: `15`, `60` (default), `240` or `1440`. | | `minCalls` | Rates are only judged with at least this many calls in the period (default 10), so one bad call in a quiet hour does not fire an alert. | | `webhookUrl` | An https endpoint for a JSON notification. | | `slackWebhookUrl` | A Slack [incoming webhook](https://api.slack.com/messaging/webhooks) URL. | | `enabled` | Turn the alert off without deleting it. | Each alert needs a webhook, a Slack webhook, or both. An organization can have up to 20 alerts. ## What you can watch | Metric | Value | | - | - | | `call_failure_rate` | Percent of finished calls that failed. Outbound calls that were not answered count as failed. | | `evaluation_fail_rate` | Percent of calls where at least one [success criterion](/platform/webhooks#post-call-evaluation) was judged `fail` (calls with no `pass` or `fail` verdict are not counted). | | `negative_sentiment_rate` | Percent of calls with negative [caller sentiment](/platform/webhooks#caller-sentiment). | | `tool_error_rate` | Percent of tool calls (custom tools and webhooks) that returned an error or timed out. | | `webhook_failure_rate` | Percent of post-call webhooks that could not be delivered after all retries. | | `call_count` | Number of calls. Use `below` to hear when calls stop coming in. | ## How alerts are checked Every alert is checked every 5 minutes over its time window. When the value crosses the threshold the alert **fires** and sends one notification. It sends nothing more while it stays over the threshold, then sends one more when it **resolves**. Changing an alert's condition takes effect at the next check: an alert that no longer meets its new condition resolves. To see an alert's current value without sending anything, call `GET /v1/alert-rules/{id}/value`. To check your endpoints, `POST /v1/alert-rules/{id}/test` (or the send icon in the dashboard) sends a test notification. `GET /v1/alert-rules/{id}/events` lists past notifications with the delivery result for each channel. ## Webhook payload ```json theme={null} { "event": "alert.triggered", "alert": { "id": "cmuw...", "name": "Failed calls on the booking line", "metric": "call_failure_rate", "comparator": "above", "threshold": 20, "windowMinutes": 60, "agentId": "AGENT_ID" }, "value": 27.5, "sample": 40, "summary": "Failed calls: 27.5% over the last 1 hour (40 in the sample); alert when above 20%.", "timestamp": "2026-10-06T14:05:00.000Z" } ``` `event` is `alert.triggered`, `alert.resolved` or `alert.test`. `sample` is the number of calls (or tool calls, or webhooks) the value was computed from. Alert webhooks are signed like your other webhooks when you have a [signing secret](/platform/webhooks#verify-webhook-sources). Slack messages carry the alert name and the same summary line. # Campaigns Source: https://docs.revring.ai/platform/campaigns Call a contact list with one agent: calling windows in each contact local time, automatic retries by outcome, pacing, a do-not-call list and a CSV of results. A campaign calls a list of contacts with one agent. RevRing dials inside the calling window you set, in each contact's own time zone when you provide one, retries the outcomes you choose (no answer, busy, voicemail, declined, failed), paces new calls so your carrier is not overwhelmed, never dials numbers on your do-not-call list, and gives you a CSV of every contact with the result of their last call. ## Create and start a campaign ```bash theme={null} curl -X POST https://api.revring.ai/v1/campaigns \ -H "x-api-key: $REVRING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "October renewals", "agentId": "cmagent123", "fromNumber": "+14155550100", "callingWindow": { "start": "09:00", "end": "18:00", "days": [1, 2, 3, 4, 5], "timezone": "America/New_York" }, "retryPolicy": { "maxAttempts": 3, "retryDelayMinutes": 120, "retryOn": ["no_answer", "busy"] }, "maxConcurrency": 5, "callsPerSecond": 1, "contacts": [ { "toNumber": "+14155550123", "variables": { "first_name": "Ana", "renewal_date": "Oct 31" } }, { "toNumber": "+13125550188", "variables": { "first_name": "Sam" }, "timezone": "America/Chicago" } ], "start": true }' ``` Each contact's `variables` are passed to the call exactly like `variables` on [Send Call](/api-reference/calls/send), merged over the campaign's `defaultVariables`. Use them in the prompt and opening line as `{{first_name}}`. Without `"start": true` the campaign is created as a draft; start it with `POST /campaigns/{id}/start`. Set `startAt` to hold dialing until a later time. | Setting | Meaning | Default | | - | - | - | | `fromNumber` | Caller ID. Must be a number on the agent's SIP trunk | Required | | `callingWindow` | `start` and `end` (24h `HH:MM`), `days` (0 = Sunday), and `timezone`. Each contact is called in its own local time: its `timezone` when you give one, otherwise the zone of its number's area code, otherwise the window's `timezone` | Any time | | `retryPolicy` | `maxAttempts` (1 to 10), `retryDelayMinutes`, and which outcomes to retry | One attempt, no retries | | `maxConcurrency` | Most calls this campaign runs at once | Your plan's concurrency | | `callsPerSecond` | New calls started per second (0.1 to 10). Many carriers allow 1 per second per trunk | 1 | | `voicemailStrategy` | `agent`: the agent's voicemail setting on every attempt. `last_attempt`: earlier attempts that reach voicemail hang up and are retried; the message is left only on the final attempt | `agent` | | `environment` | [Environment](/platform/versioning) the agent runs on | Production | | `startAt` | Don't dial before this time | Immediately | Campaign calls share your organization's concurrency with everything else. Calls you place with [Send Call](/api-reference/calls/send) that are waiting in the queue go first. ## Outcomes and retries When a call ends, the contact gets an `outcome`: | Outcome | When | | - | - | | `answered` | Someone picked up and the conversation happened (including transferred calls) | | `voicemail` | Voicemail was detected; the agent's voicemail setting applied | | `no_answer` | It rang out, or nobody spoke | | `busy` | The line was busy | | `declined` | The call was rejected | | `failed` | The call could not be placed | | `do_not_call` | The number is on your do-not-call list; it was not dialed | With `voicemailStrategy: "last_attempt"`, voicemail is always retried until the last attempt, which leaves the agent's voicemail message once. If the outcome is in `retryOn` and the contact has attempts left, it waits `retryDelayMinutes` and is called again, still only inside the calling window. Otherwise the contact is `completed` (answered, voicemail, or an outcome you don't retry) or `exhausted` (retries used up). ## Follow progress `GET /campaigns/{id}` returns `progress`: the number of contacts in each status and each outcome. `GET /campaigns/{id}/contacts` lists contacts with `status`, `outcome`, `attempts`, `nextAttemptAt` and `lastCallId`; filter with `status` or `outcome`. Every call a campaign places carries `campaignId` on the call record and in the [post-call webhook](/platform/webhooks), so your existing handler can tell campaign calls apart. The campaign completes on its own when every contact is settled. ## Export results `GET /campaigns/{id}/export` returns a CSV with one row per contact: number, status, outcome, attempts, the last call's ID, status, duration, summary and recording, every contact variable (`var.*`), every [extraction](/platform/webhooks#post-call-data-extraction) field (`extraction.*`) and every [evaluation](/platform/webhooks#post-call-evaluation) result (`evaluation.*`). ## Pause, resume, cancel * `POST /campaigns/{id}/pause` stops new dials. Calls in progress finish and their outcomes are recorded. * `POST /campaigns/{id}/start` resumes. * `POST /campaigns/{id}/cancel` stops the campaign for good. Contacts not yet called become `canceled`. Contacts can be added to a draft, running or paused campaign with `POST /campaigns/{id}/contacts` (up to 10,000 per request; numbers already in the campaign are skipped). ## Do-not-call list Numbers on your organization's do-not-call list are never dialed by a campaign. They are marked `skipped` with outcome `do_not_call`. ```bash theme={null} curl -X POST https://api.revring.ai/v1/do-not-call \ -H "x-api-key: $REVRING_API_KEY" -H "Content-Type: application/json" \ -d '{ "numbers": ["+14155550123"], "reason": "asked not to be called" }' ``` List entries with `GET /do-not-call` and remove one with `DELETE /do-not-call/{e164}`. ### Add callers who ask automatically Turn on `doNotCallDetection` on an agent (or **Detect do-not-call requests** on its Advanced tab) and, after each call, RevRing checks whether the caller asked not to be called again: to stop calling, to be taken off the list, or that they are on a do-not-call list. Declining an offer or asking to be called back later does not count. When they asked, the call shows `doNotCallRequested: true` (also in the post-call webhook) and their number is added to your do-not-call list with the reason `asked on call {id}`, so campaigns never dial it again. Agents imported from Retell AI with do-not-call detection on keep it. # Conversation Flows Source: https://docs.revring.ai/platform/conversation-flows Build structured conversation flows with the RevRing flow JSON format Conversation flows let you define structured, multi-step call scripts for your AI agents. Instead of relying on a single prompt, you can build a directed graph of nodes (steps) and edges (transitions) that guide the conversation through a precise sequence. Flows are defined as a JSON format that we call the **flow definition**. You can build flows visually in the RevRing dashboard, or generate them programmatically — including with AI tools like ChatGPT — and import them via the API. ## Overview A flow consists of: * **Begin** — which node starts the conversation and who speaks first * **Nodes** — individual steps (conversation, tool call, logic branch, transfer, etc.) * **Edges** — connections between nodes that define transitions and conditions ```json theme={null} { "schemaVersion": 1, "begin": { "startNodeId": "node_1", "whoSpeaksFirst": "agent" }, "nodes": [ ... ], "edges": [ ... ] } ``` ## Flow Definition Reference ### Top-Level Structure | Field | Type | Required | Description | | - | - | - | - | | `schemaVersion` | `1` | Yes | Must be `1` | | `begin` | object | Yes | Flow entry point configuration | | `nodes` | array | Yes | List of flow nodes (at least one) | | `edges` | array | Yes | List of edges connecting nodes | | `ui` | object | No | Visual editor state (viewport position/zoom) | ### Begin | Field | Type | Required | Description | | - | - | - | - | | `startNodeId` | string | Yes | ID of the first node to execute. Must match a node in `nodes`. | | `whoSpeaksFirst` | `"agent"` or `"user"` | Yes | Whether the agent speaks first or waits for the caller | ## Node Types Every node has the following base fields: | Field | Type | Required | Description | | - | - | - | - | | `id` | string | Yes | Unique identifier for the node | | `type` | string | Yes | One of the node types below | | `name` | string | Yes | Human-readable label | | `isGlobal` | boolean | No | If `true`, this node can be triggered from any point in the flow via a global edge | | `position` | `{ x, y }` | Yes | Visual position in the editor (use any values when generating programmatically) | | `data` | object | Yes | Type-specific configuration (see below) | ### Conversation The core node type. The agent speaks and/or listens based on an instruction. **Type**: `"conversation"` | Field | Type | Required | Description | | - | - | - | - | | `instructionType` | `"prompt"` or `"static"` | Yes | `"prompt"` = LLM interprets the instruction as context. `"static"` = agent speaks the text verbatim. | | `instruction` | string | Yes | The instruction text or static message. Supports `{{variables}}`. | | `skipResponse` | boolean | No | If `true`, the agent speaks the instruction and immediately moves to the next node without waiting for a user reply. Requires a `skip` edge. | | `blockInterruptions` | boolean | No | If `true`, the agent cannot be interrupted while speaking | **Example:** ```json theme={null} { "id": "greeting", "type": "conversation", "name": "Greeting", "position": { "x": 300, "y": 200 }, "data": { "instructionType": "prompt", "instruction": "Greet the caller warmly. Introduce yourself as {{agent_name}} from {{company_name}}. Ask how you can help them today." } } ``` ### Function (Tool Call) Invokes one of your agent's custom tools during the flow. **Type**: `"function"` | Field | Type | Required | Description | | - | - | - | - | | `toolName` | string | Yes | Name of the custom tool to invoke (must be configured on the agent) | | `speakDuringExecution` | boolean | No | If `true`, agent speaks while the tool runs | | `speakInstruction` | string | No | What to say while the tool executes | | `speakInstructionType` | `"prompt"` or `"static"` | No | How to interpret the speak instruction | | `blockInterruptions` | boolean | No | Block interruptions during execution | | `waitForResult` | boolean | No | Default `true`. Set `false` for fire-and-forget calls. | | `outputVariables` | array | No | Map tool output keys to flow variables for use in downstream conditions | Each entry in `outputVariables`: | Field | Type | Description | | - | - | - | | `outputKey` | string | Key in the tool's JSON response | | `variableName` | string | Flow variable name to store the value as | **Example:** ```json theme={null} { "id": "lookup_order", "type": "function", "name": "Look Up Order", "position": { "x": 300, "y": 400 }, "data": { "toolName": "lookup_order", "speakDuringExecution": true, "speakInstruction": "Let me look that up for you, one moment.", "speakInstructionType": "static", "outputVariables": [ { "outputKey": "status", "variableName": "order_status" }, { "outputKey": "eta", "variableName": "delivery_eta" } ] } } ``` ### Logic Split A branching node with no data of its own — all logic is defined by its outgoing edges (conditions and an else fallback). **Type**: `"logic_split"` | Field | Type | Required | Description | | - | - | - | - | | (none) | — | — | Data is an empty object `{}` | The logic split node must have: * One or more `condition` edges (evaluated in order) * Exactly one `else` edge (fallback if no conditions match) **Example:** ```json theme={null} { "id": "check_status", "type": "logic_split", "name": "Check Order Status", "position": { "x": 300, "y": 600 }, "data": {} } ``` ### Call Transfer Transfers the call to another phone number. **Type**: `"call_transfer"` | Field | Type | Required | Description | | - | - | - | - | | `transferTo` | string | Yes | Destination phone number in E.164 format (e.g. `"+14155551234"`) or a `{{variable}}` | | `transferMode` | `"cold"` or `"warm"` | No | Cold = immediate transfer. Warm = agent briefs the recipient first. | | `extension` | string | No | Touch-tone digits dialed after the call connects, to reach an extension behind a phone menu. Allowed: `0-9`, `*`, `#`, and `w` for a 0.5-second pause. Start with `wwww` (2 seconds) so the destination is ready for input, e.g. `"wwww1234"`. Warm transfers only. | | `speakDuringExecution` | boolean | No | Speak while the transfer is being set up | | `speakInstruction` | string | No | What to say during transfer | | `holdMessage` | string | No | Message spoken to the caller while on hold (warm transfer, max 500 chars) | | `holdMusicEnabled` | boolean | No | Play hold music (warm transfer) | | `summaryPrompt` | string | No | Prompt used to generate a brief for the recipient (warm transfer, max 2000 chars) | | `introMessage` | string | No | Message spoken to the recipient before connecting (warm transfer, max 500 chars) | | `continueRecordingAfterTransfer` | boolean | No | Keep recording after the handoff, so the recording also includes the recipient and caller conversation (warm transfer) | **Example:** ```json theme={null} { "id": "transfer_support", "type": "call_transfer", "name": "Transfer to Support", "position": { "x": 600, "y": 800 }, "data": { "transferTo": "+12025559999", "transferMode": "warm", "speakDuringExecution": true, "speakInstruction": "Let me connect you with our support team.", "holdMessage": "Please hold while I connect you.", "holdMusicEnabled": true, "continueRecordingAfterTransfer": false } } ``` ### End Call Terminates the call, optionally speaking a closing message. **Type**: `"end"` | Field | Type | Required | Description | | - | - | - | - | | `message` | string | No | Closing message to speak before hanging up | **Example:** ```json theme={null} { "id": "goodbye", "type": "end", "name": "End Call", "position": { "x": 300, "y": 1000 }, "data": { "message": "Thank you for calling. Have a great day!" } } ``` ### Press Digit (DTMF) Navigates an automated phone menu on the other end of the call. The agent stays silent, listens to the menu, and presses the digit your instruction calls for. Use it when your agent calls a business that answers with "press 1 for...". **Type**: `"press_digit"` | Field | Type | Required | Description | | - | - | - | - | | `instruction` | string | Yes | Which option to choose, in plain language | | `detectionDelaySeconds` | number | No | How long to let the menu finish before choosing (0 to 10 seconds, default 1) | **Example:** ```json theme={null} { "id": "menu", "type": "press_digit", "name": "Reach billing", "position": { "x": 300, "y": 200 }, "data": { "instruction": "Choose the option for the billing department. If the menu offers an operator, choose that instead.", "detectionDelaySeconds": 3 } } ``` Each time the menu speaks, the agent decides whether to press a digit; the node's transitions then move the flow on as usual. ### Extract Variable Uses the LLM to extract structured data from the conversation and store it in flow variables for downstream use. **Type**: `"extract_variable"` | Field | Type | Required | Description | | - | - | - | - | | `variables` | array | Yes | List of variables to extract (at least one) | Each entry in `variables`: | Field | Type | Required | Description | | - | - | - | - | | `variableName` | string | Yes | Name of the flow variable to store the extracted value | | `description` | string | Yes | Description of what to extract (used as context for the LLM) | | `variableType` | `"text"`, `"number"`, `"enum"`, `"boolean"` | Yes | Data type of the variable | | `enumOptions` | string\[] | No | Required when `variableType` is `"enum"` — the allowed values | **Example:** ```json theme={null} { "id": "get_intent", "type": "extract_variable", "name": "Detect Intent", "position": { "x": 300, "y": 400 }, "data": { "variables": [ { "variableName": "caller_intent", "description": "The primary reason the caller is calling", "variableType": "enum", "enumOptions": ["billing", "support", "sales", "other"] }, { "variableName": "caller_name", "description": "The caller's first name if mentioned", "variableType": "text" } ] } } ``` ## Edges Edges connect nodes and define how the flow transitions between steps. Condition edges can be attached to **any node type**, not just logic split nodes. For example, you can put condition edges directly on a conversation node to branch based on the caller's response — no logic split needed. ### Edge Fields | Field | Type | Required | Description | | - | - | - | - | | `id` | string | Yes | Unique identifier for the edge | | `source` | string | Yes | ID of the source node, or `"__global__"` for global edges | | `target` | string | Yes | ID of the target node | | `kind` | string | Yes | One of: `"default"`, `"condition"`, `"else"`, `"skip"` | | `order` | integer | Condition edges only | Evaluation order (0-based). Lower numbers are evaluated first. | | `condition` | object | Condition edges only | The condition to evaluate | ### Edge Kinds | Kind | Description | | - | - | | `default` | Unconditional transition — used when a node completes normally | | `condition` | Evaluated in order; first matching condition wins | | `else` | Fallback when no condition edges match (required on `logic_split` nodes) | | `skip` | Used with `skipResponse` conversation nodes — immediate transition without waiting for user input | ### Conditions on Any Node You can attach condition edges to any node type. This is particularly useful on conversation nodes — branch directly based on the caller's response without needing a separate logic split node. For example, a greeting node that routes callers to different paths: ```json theme={null} [ { "id": "e1", "source": "greeting", "target": "collect_info", "kind": "condition", "order": 0, "condition": { "type": "prompt", "promptText": "The caller wants help and didn't mention billing or tech support" } }, { "id": "e2", "source": "greeting", "target": "billing_confirm", "kind": "condition", "order": 1, "condition": { "type": "prompt", "promptText": "The caller needs billing help" } }, { "id": "e3", "source": "greeting", "target": "tech_confirm", "kind": "condition", "order": 2, "condition": { "type": "prompt", "promptText": "The caller needs technical help" } } ] ``` If no condition matches, the agent stays at the greeting node and continues the conversation until a condition is met. ### Conditions Conditions determine whether a `condition` edge is followed. There are two types: #### Prompt Conditions The LLM evaluates a natural language question against the conversation context. ```json theme={null} { "type": "prompt", "promptText": "Did the caller agree to schedule an appointment?" } ``` | Field | Type | Description | | - | - | - | | `type` | `"prompt"` | Prompt-based condition | | `promptText` | string | A yes/no question the LLM evaluates against the conversation | #### Equation Conditions Variable-based conditions that compare flow variables against values. No LLM call required. ```json theme={null} { "type": "equation", "match": "all", "equations": [ { "variable": "order_status", "operator": "==", "value": "shipped" }, { "variable": "delivery_eta", "operator": "exists" } ] } ``` | Field | Type | Description | | - | - | - | | `type` | `"equation"` | Equation-based condition | | `match` | `"any"` or `"all"` | Whether any or all equations must be true. Optional — defaults to `"all"`. | | `equations` | array | List of equations to evaluate | Each equation: | Field | Type | Description | | - | - | - | | `variable` | string | Flow variable name to compare | | `operator` | string | Comparison operator | | `value` | string | Value to compare against (not required for `exists`/`not_exists`) | **Available operators:** `==`, `!=`, `contains`, `not_contains`, `contained_in`, `not_contained_in`, `>`, `<`, `>=`, `<=`, `exists`, `not_exists` ## Global Nodes and Edges Global nodes can be triggered from **any point** in the conversation, not just from a specific predecessor node. This is useful for handling requests that can happen at any time, such as "transfer me to a human" or "I want to cancel." To make a node global: 1. Set `isGlobal: true` on the node 2. Add one or more edges with `source: "__global__"` targeting that node 3. Global edges must have a `condition` — typically a prompt condition ```json theme={null} { "id": "global_transfer_edge", "source": "__global__", "target": "transfer_human", "kind": "condition", "order": 0, "condition": { "type": "prompt", "promptText": "Is the caller asking to speak with a human or a real person?" } } ``` ## Transition Behavior After each user reply, the agent evaluates edges in this priority order: 1. **Global edges** — checked first, across all global nodes. If a global condition matches, the flow jumps to that global node regardless of where the conversation currently is. 2. **Condition edges** on the current node — evaluated in `order`, first match wins 3. **Else edge** — taken if no condition edges matched 4. **Default edge** — unconditional, taken if present 5. **No match** — the agent stays in the current node and continues the conversation This means a conversation node doesn't need a fallback edge. If no conditions match, the agent simply keeps talking at the current step — useful for nodes that need to collect information before moving on (e.g. "keep asking until the caller provides their phone number"). ## Validation Rules When submitting a flow via the API, the following rules are enforced: 1. `schemaVersion` must be `1` 2. Every node must have a unique `id` 3. `begin.startNodeId` must reference an existing node 4. All edge `source` and `target` values must reference existing nodes (except `"__global__"` as source) 5. `logic_split` nodes must have exactly one `else` edge 6. `skipResponse` conversation nodes must have exactly one `skip` edge and no other outgoing edges 7. Global nodes (`isGlobal: true`) must have at least one `__global__` edge targeting them 8. `__global__` edges must target nodes with `isGlobal: true` 9. `condition` edges must have an `order` value, unique per source node 10. Prompt conditions must have non-empty `promptText`; equation conditions must have at least one equation 11. Total flow size must not exceed 48 KB ## Complete Example Here is a complete flow for an order status hotline: ```json theme={null} { "schemaVersion": 1, "begin": { "startNodeId": "greeting", "whoSpeaksFirst": "agent" }, "nodes": [ { "id": "greeting", "type": "conversation", "name": "Greeting", "position": { "x": 300, "y": 100 }, "data": { "instructionType": "prompt", "instruction": "Greet the caller warmly. Ask for their order number so you can look up the status." } }, { "id": "extract_order", "type": "extract_variable", "name": "Get Order Number", "position": { "x": 300, "y": 300 }, "data": { "variables": [ { "variableName": "order_number", "description": "The customer's order number", "variableType": "text" } ] } }, { "id": "lookup", "type": "function", "name": "Look Up Order", "position": { "x": 300, "y": 500 }, "data": { "toolName": "lookup_order", "speakDuringExecution": true, "speakInstruction": "One moment while I look that up.", "speakInstructionType": "static", "outputVariables": [ { "outputKey": "status", "variableName": "order_status" } ] } }, { "id": "check_status", "type": "logic_split", "name": "Check Status", "position": { "x": 300, "y": 700 }, "data": {} }, { "id": "shipped_response", "type": "conversation", "name": "Order Shipped", "position": { "x": 100, "y": 900 }, "data": { "instructionType": "prompt", "instruction": "Tell the customer their order has been shipped and ask if there's anything else you can help with." } }, { "id": "pending_response", "type": "conversation", "name": "Order Pending", "position": { "x": 500, "y": 900 }, "data": { "instructionType": "prompt", "instruction": "Let the customer know their order is still being processed and should ship within 1-2 business days. Ask if they have any other questions." } }, { "id": "transfer_support", "type": "call_transfer", "name": "Transfer to Support", "isGlobal": true, "position": { "x": 700, "y": 500 }, "data": { "transferTo": "+12025559999", "transferMode": "cold", "speakDuringExecution": true, "speakInstruction": "Let me connect you with our support team right away." } }, { "id": "goodbye", "type": "end", "name": "End Call", "position": { "x": 300, "y": 1100 }, "data": { "message": "Thank you for calling! Goodbye." } } ], "edges": [ { "id": "e1", "source": "greeting", "target": "extract_order", "kind": "default" }, { "id": "e2", "source": "extract_order", "target": "lookup", "kind": "default" }, { "id": "e3", "source": "lookup", "target": "check_status", "kind": "default" }, { "id": "e4", "source": "check_status", "target": "shipped_response", "kind": "condition", "order": 0, "condition": { "type": "equation", "match": "all", "equations": [ { "variable": "order_status", "operator": "==", "value": "shipped" } ] } }, { "id": "e5", "source": "check_status", "target": "pending_response", "kind": "else" }, { "id": "e6", "source": "shipped_response", "target": "goodbye", "kind": "default" }, { "id": "e7", "source": "pending_response", "target": "goodbye", "kind": "default" }, { "id": "e8", "source": "__global__", "target": "transfer_support", "kind": "condition", "order": 0, "condition": { "type": "prompt", "promptText": "Is the caller asking to speak with a human representative?" } } ] } ``` This flow: 1. **Greets** the caller and asks for their order number 2. **Extracts** the order number from the conversation 3. **Calls a tool** to look up the order status 4. **Branches** based on whether the status is `"shipped"` or anything else 5. **Responds** with the appropriate message 6. **Ends** the call 7. At **any point**, if the caller asks for a human, the call is transferred (global node) ## Importing Flows via API Agent `mode` is set at creation time and cannot be changed afterwards. To use conversation flows, create an agent with `mode: "conversation_flow"`. ### Creating a flow agent Use the [Create Agent](/api-reference/agents/create) endpoint with `mode` set to `"conversation_flow"` and the `flowDefinition` field set to your flow JSON: ```bash theme={null} curl -X POST https://api.revring.ai/v1/agents \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Order Status Agent", "mode": "conversation_flow", "promptTemplate": "You are a helpful order status assistant.", "flowDefinition": { "schemaVersion": 1, "begin": { ... }, "nodes": [ ... ], "edges": [ ... ] } }' ``` ### Updating a flow To update the flow on an existing `conversation_flow` agent, use the [Update Agent](/api-reference/agents/update) endpoint with just the `flowDefinition` field: ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "flowDefinition": { "schemaVersion": 1, "begin": { ... }, "nodes": [ ... ], "edges": [ ... ] } }' ``` `mode` is immutable after creation. You cannot convert a `single_prompt` agent to `conversation_flow` — create a new agent instead. You can generate flow definition JSON with AI tools like ChatGPT. Share this reference page as context, describe the conversation flow you want, and paste the generated JSON into the API call or the dashboard's import feature. ## Using Variables in Flows Flow nodes support `{{variable}}` syntax in instruction text. Variables can come from: * **System variables** (`{{current_time}}`, `{{user_number}}`, etc.) — see [Prompting & Variables](/platform/prompting-variables) * **Default variables** configured on your agent * **Dynamic variables** passed via the API or pre-call webhook * **Extracted variables** from `extract_variable` nodes * **Tool output variables** mapped via `outputVariables` in function nodes All variables are available to every node in the flow once set. # Data Controls Source: https://docs.revring.ai/platform/data-controls Remove personal details from call transcripts and summaries before they are stored or sent to your webhook. Each agent decides what RevRing keeps from its calls. Turn on personal-detail removal and every call's transcript and summary are cleaned before they are stored, shown in the dashboard or sent to your post-call webhook. ## Remove personal details ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{id} \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "storageMode": "redact_pii" }' ``` After every call, each personal detail in the transcript and summary is replaced with a label naming what was removed: ```text theme={null} Caller: Sure, it's [person_name], [phone_number]. My date of birth is [date_of_birth]. Agent: Thanks [person_name], you're booked for Tuesday at 3pm. ``` What is removed (all of these by default): | Category | Examples | | - | - | | `person_name` | A caller's first, last or full name | | `phone_number` | Phone numbers, in digits or spoken ("four one five...") | | `email` | Email addresses, including spoken forms ("jane at example dot com") | | `address` | Street addresses, unit numbers, postal codes | | `date_of_birth` | Birth dates | | `government_id` | Social Security, passport, driver's license and tax ID numbers | | `payment_card` | Card numbers, expiry dates and security codes | | `bank_account` | Bank account, routing and IBAN numbers | | `account_number` | Customer, member, policy, order and claim numbers | | `password_or_pin` | Passwords, passcodes, PINs and security answers | | `medical_record_number` | Medical record, patient and health plan member IDs | To remove only some categories, list them in `piiCategories`: ```json theme={null} { "storageMode": "redact_pii", "piiCategories": ["payment_card", "government_id", "password_or_pin"] } ``` How it behaves: * The redacted transcript and summary are what is stored, shown in the dashboard and sent in the post-call webhook (in every [webhook format](/platform/webhooks)). Tool call arguments and results in the transcript are redacted too. * Your agent's own name, your business name, product names, prices and appointment times are kept. * Extracted data (`postCallExtraction`) and evaluation results are produced from the full conversation and are **not** redacted, since they hold exactly the fields you asked for. Call variables you send are kept as you sent them. * Recordings are not changed. * If redaction cannot run for a call, its transcript text is withheld rather than stored with personal details. * Calls redacted this way show `"piiRedacted": true`. ## In the dashboard Open the agent, go to the **Advanced** tab, and set **Call data storage** to **Remove personal details from transcripts and summaries**. # Import Agents Source: https://docs.revring.ai/platform/importing-agents Bring your voice agents over from Retell AI or ElevenLabs in minutes: prompt, opening, tools, transfers, voicemail, knowledge and post-call analysis, with a report of every setting. Moving an agent from another platform should not mean rebuilding it by hand. The importer reads your agents from **Retell AI** or **ElevenLabs Agents** with your API key for that platform, shows you exactly how each setting carries over, and creates the agents here when you are ready. ## How it works `POST /imports/agents` with the source platform and your API key for it returns the agents on that account. `POST /imports` with the agent IDs returns a report for each agent. Nothing is created yet. Send the same request with `"dryRun": false`. The agents are created with their custom tools and knowledge bases, and the report lists what to check before switching traffic. ```bash theme={null} curl -X POST https://api.revring.ai/v1/imports \ -H "x-api-key: $REVRING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source": "elevenlabs", "apiKey": "", "agentIds": ["agent_4201k6x9pq3rfe8tvb2mzk7d5wya"], "dryRun": true }' ``` Your source API key is used only for read-only requests to that platform during the import request. It is never stored, logged, or returned. A key with read access to agents, tools, knowledge base and voices is enough. ElevenLabs agents that live in a data-residency region are read from that region: pass `"region": "eu"`, `"in"`, `"sg"` or `"us"`. ## The report Every source setting that is set gets one line, so nothing is dropped silently: | Status | Meaning | | - | - | | `mapped` | Carries over with the same meaning | | `approximated` | Carried over to the closest equivalent. Worth a look before going live | | `unsupported` | No equivalent yet. Not imported | | `manual` | Needs a step from you, such as pasting a secret header value | Each agent also gets a `checklist` of the steps left to you, and a `status`: `ready` or `ready_with_gaps` on a dry run, `imported` or `imported_with_gaps` after importing, `blocked` when the agent cannot be imported (see below), or `failed`. ```json theme={null} { "data": { "platform": "elevenlabs", "dryRun": true, "agents": [ { "source": { "platform": "elevenlabs", "agentId": "agent_4201k6...", "name": "Acme Outbound SDR" }, "status": "ready_with_gaps", "voice": { "status": "mapped", "source": "21m00Tcm4TlvDq8ikWAM", "detail": "Studio voice \"Rachel\"" }, "customTools": [{ "name": "book_demo", "webhookUrl": "https://api.acme-crm.example.com/v1/demos" }], "knowledgeBases": [{ "name": "Acme Outbound SDR knowledge", "sources": 2 }], "counts": { "mapped": 31, "approximated": 6, "unsupported": 5, "manual": 3 }, "fields": [ { "field": "conversation_config.agent.first_message", "status": "mapped", "target": "firstMessage" }, { "field": "conversation_config.agent.prompt.llm", "status": "approximated", "target": "llmModel", "detail": "source model \"gpt-4.1\" runs on the Max tier" }, { "field": "tools.book_demo.request_headers.X-Api-Key", "status": "manual", "target": "customTools.headers", "detail": "secret header values cannot be exported; paste the value" } ], "checklist": [ "tools.book_demo.request_headers.X-Api-Key: secret header values cannot be exported; paste the value", "Phone numbers: connect your numbers or SIP trunk and assign them to this agent." ] } ], "summary": { "agents": 1, "ready": 1, "blocked": 0, "imported": 0, "failed": 0 } } } ``` ## What carries over | Area | Retell AI | ElevenLabs Agents | | - | - | - | | Prompt | Single prompt. Multi-prompt agents are combined into one prompt with a section per stage | System prompt | | Opening | Begin message, who speaks first, delay, wait-for-caller silence | First message (empty means the agent waits for the caller) | | Variables | Default dynamic variables. `{{current_calendar}}` becomes `{{current_time}}` | Dynamic variable placeholders. `{{system__caller_id}}` becomes `{{user_number}}`, `{{system__called_number}}` becomes `{{agent_number}}`, `{{system__time_utc}}` becomes `{{current_time}}` | | Model | Mapped to the Standard or Max tier, with temperature | Mapped to the Standard or Max tier, with temperature and reasoning effort | | Speech | Language, speed, volume, interruption sensitivity, boosted keywords, noise cancellation, time zone | Language, speed, stability, keywords, interruption ignore words, time zone | | Timing | Silence hang-up, maximum duration, reminder timeout | Turn timeout, silence hang-up, maximum duration | | Tools | End call, transfer (fixed numbers, cold or warm with hold music, whisper, intro, caller ID and timeouts), press digit, custom webhook tools | End call, transfer to number (phone destinations, extension digits), keypad tones, voicemail detection, webhook tools (body, query and fixed values) | | Voicemail | Hang up or leave a message | Leave a message or hang up | | Analysis | Custom analysis fields become [extraction](/platform/webhooks) fields; call successful becomes an evaluation criterion; user sentiment becomes an extraction field | Data collection becomes extraction fields; evaluation criteria carry over | | Knowledge | Knowledge bases with URL, text and document sources, plus retrieval settings | URL, text and file documents, plus retrieval settings | | Webhook | Post-call webhook URL | Post-call webhook URL (the agent's own workspace webhook, or the workspace default); conversation initiation webhook becomes the pre-call webhook | **Voices.** The importer picks the matching Studio voice when one is available, and otherwise the closest voice by language and gender. Preview the voice before going live. No platform lets you export a cloned voice, so clone it again from a recording you own. **Webhook payloads.** An imported Retell webhook URL keeps Retell's `call_ended` / `call_analyzed` payload shape, and an ElevenLabs agent's post-call webhook keeps the `post_call_transcription` shape, so your existing handler keeps working. A webhook that is turned off on ElevenLabs is not set. Only the signature check changes: verify `X-RevRing-Signature`. See [compatibility formats](/platform/webhooks#compatibility-formats). **Custom tool requests.** ElevenLabs webhook tools are imported with **Send Raw Request Body** on, so your endpoint receives the same body shape. Retell tools send the arguments under `arguments` instead of `args`; update the handler or turn on Send Raw Request Body. ## Not imported (yet) * **Blocked agents:** agents that run on your own language model server (custom LLM), and Retell Conversation Flow agents. ElevenLabs workflows import the main agent prompt only. * **Secrets:** secret header values and OAuth connections cannot be exported from the source. The tool is created with the header name and an empty value for you to fill in. * **Phone numbers:** connect your carrier numbers or SIP trunk after importing and assign them to the agent. See [Telephony](/platform/telephony). * Other unsupported settings, such as pronunciation dictionaries or ambient sound, are listed in the report with the reason. ## Importing again Importing an agent you already imported **updates** the agent it created instead of making a copy: settings are re-applied, custom tools with the same name are updated, and knowledge bases already linked are left in place. Use this to pick up source changes while you test side by side. ## Limits * Up to 25 agents per dry run, and up to 10 per import request. * Knowledge bases hold up to 50 sources each; larger sets are split across several knowledge bases linked to the agent. * Custom tool calls time out after 8 seconds. Longer source timeouts are reported. See [Import Agents](/api-reference/imports/import) and [List Source Agents](/api-reference/imports/list-agents) in the API reference. # Inbound Calls Source: https://docs.revring.ai/platform/inbound Configure agents to handle incoming phone calls Configure your AI agents to answer and handle inbound phone calls on your business phone numbers. ## How Inbound Calls Work When someone dials one of your phone numbers: 1. The call routes through your SIP trunk to RevRing 2. RevRing matches the dialed number to an agent based on phone number assignments 3. If a pre-call webhook is configured, RevRing calls it to get dynamic variables 4. The agent answers and begins the conversation using its configured prompt and first message 5. After the call ends, RevRing sends call details to your post-call webhook (if configured) ## Assigning Phone Numbers to Agents Before an agent can receive inbound calls, you must assign at least one phone number to it. ### Step 1: Open Agent Settings 1. Navigate to [Agents](https://dashboard.revring.ai/app/agents) 2. Click on the agent you want to configure 3. Select the **Numbers** tab ### Step 2: Add an Inbound Number 1. In the **Add an Inbound Number** card, enter the phone number in E.164 format: ``` +12025551234 ``` 2. Click **Add Number** The number will appear in the **Assigned Inbound Numbers** list above. The phone number must already be assigned to your SIP trunk in both your telephony provider (Twilio, Telnyx, etc.) and in your RevRing SIP trunk configuration. ### Step 3: Verify Assignment Call the phone number from your mobile device to verify the agent answers correctly. You should hear the agent's first message (if configured) or the agent will wait for you to speak first. ## Configuring Agent Behavior ### First Message The first message is what the agent says immediately upon answering the call. Configure it in the **Overview** tab: 1. Navigate to the agent's **Overview** tab 2. In the **First Message** field, enter your greeting: ``` Thank you for calling Acme Support. How can I help you today? ``` 3. Click **Save** Leave the first message empty if you want the agent to wait for the caller to speak first. This is useful for support lines where callers typically explain their issue right away. ### Dynamic First Messages with Variables You can personalize the first message using variables from the pre-call webhook: ``` Hello {{customer_name}}, thanks for calling back about {{ticket_subject}}. How can I help? ``` See the [Webhooks guide](/platform/webhooks) for details on implementing pre-call webhooks. If the same agent also places outbound calls and needs a different opening for them, set the **Outbound First Message** field (`firstMessageOutbound`). See [Separate Inbound and Outbound Openings](/platform/outbound#separate-inbound-and-outbound-openings). ### Agent Prompt The prompt defines how your agent behaves throughout the entire conversation. Navigate to the **Overview** tab and configure the **Prompt** field: ``` You are a customer support agent for Acme Corp. Your responsibilities: - Listen carefully to the customer's issue - Ask clarifying questions if needed - Provide helpful solutions from our knowledge base - Offer to transfer to a specialist if you cannot resolve the issue - Always be professional, patient, and empathetic Keep responses concise (under 30 seconds). If the customer seems frustrated, acknowledge their feelings and focus on solutions. ``` The prompt is the most critical configuration. Be specific about tone, responsibilities, and constraints. Test thoroughly and iterate based on real call transcripts. ## Advanced Inbound Features ### Pre-Call Webhooks Pre-call webhooks allow you to customize each call based on caller information. Before answering, RevRing sends caller details to your webhook and uses the response to personalize the conversation. Configure in the **Advanced** tab: 1. Set **Pre-call Webhook URL** to your endpoint: ``` https://api.yourcompany.com/webhooks/pre-call ``` 2. Click **Save** RevRing will POST the caller's number and other context. Your webhook must respond within 3 seconds with variables: ```json theme={null} { "variables": { "customer_name": "John Smith", "account_status": "premium", "last_order": "Order #12345" } } ``` These variables can then be used in your prompt and first message with `{{variable_name}}` syntax. See the [Webhooks guide](/platform/webhooks) for complete implementation details. ### Error Handling If your webhook fails, times out, or returns invalid JSON: * RevRing will proceed with the call using default variables (if configured) * The call will not be rejected - the agent will answer without personalization * Check the call's `preCallWebhookStatus` field in call logs to see webhook delivery status ### Default Variables You can set default variable values in the **Overview** tab that apply to all calls unless overridden by the pre-call webhook. This is useful for agent-wide settings. While editing your prompt, any variables you reference with `{{variable_name}}` syntax will be detected. You can set defaults in the **Default Variables** section (if visible in your dashboard configuration). ### Voicemail Detection Enable voicemail detection to automatically handle scenarios where the agent reaches an answering machine or voicemail: 1. Navigate to the **Tools** tab 2. Locate **Voicemail Detection** under System Tools 3. Click **Configure** 4. Choose an action: * **Hangup**: End the call immediately when voicemail is detected * **Leave Message**: Speak a pre-recorded message and then hang up 5. If you selected "Leave Message", enter the message: ``` Hi, this is an automated message from Acme Corp. We'll try calling you back later. For immediate assistance, please call us at 1-800-555-0123. ``` 6. Click **Save** Voicemail detection is most useful for outbound calling scenarios. For inbound calls, it's typically not needed since customers are calling you. ### Call Timeouts Configure timeout behavior in the **Advanced** tab: * **Turn Timeout**: Maximum silence before the agent prompts the caller ``` 7 seconds (recommended for natural conversation flow) ``` Set to `-1` to disable turn-level prompting. * **Silence End Call Timeout**: Maximum total silence before ending the call ``` -1 (disabled - let caller hang up when done) ``` Or set a value like `30` to end after 30 seconds of silence. * **Max Conversation Duration**: Hard limit on total call length ``` 600 seconds (10 minutes) ``` When reached, the agent will politely end the call. ## Testing Inbound Calls ### Using the Test Panel The test panel on the right side of the agent page lets you call your agent directly from the browser via WebRTC — no phone needed. 1. Open your agent and click the **call button** (microphone icon) in the test panel 2. Speak through your microphone to talk to the agent 3. Watch the **Transcript** update in real time 4. Click the button again to end the call Set **Test Variables** at the bottom of the panel to simulate dynamic data your pre-call webhook would normally provide. ### Calling from a Real Phone To test the full telephony path (SIP trunk, phone numbers, pre-call webhook): 1. Go to the **Numbers** tab and note your assigned phone numbers 2. Call one of those numbers from your mobile phone 3. Review the call in [Call Logs](https://dashboard.revring.ai/app/calls) afterwards ### Best Practices for Testing * **Test different scenarios**: Happy path, error cases, edge cases * **Review transcripts**: Check for misunderstandings or unexpected responses * **Refine your prompt**: Adjust based on actual conversation flow * **Test interruptions**: See how the agent handles being interrupted mid-sentence * **Verify tools**: Test transfer, end call, and custom tool invocations ## Monitoring Inbound Calls ### Call Logs View all inbound calls in the [Call Logs](https://dashboard.revring.ai/app/calls) page: 1. Filter by **Direction**: Select "Inbound" 2. Filter by **Agent**: Select your agent 3. Filter by **Status**: View completed, failed, or ongoing calls 4. Click any call to view full details, transcript, and recording ### Overview Dashboard Monitor real-time metrics on the [Overview](https://dashboard.revring.ai/app) page: * **Active Calls**: See how many inbound calls are currently ongoing * **Total Calls**: Track daily, weekly, and monthly inbound call volume * **Average Duration**: Monitor typical call length * **Total Cost**: Track usage and costs ## Common Patterns ### Business Hours Handling Use a pre-call webhook to check business hours and return different variables: ```json theme={null} { "greeting": "Thank you for calling during business hours", "transfer_available": "true" } ``` Or during off-hours: ```json theme={null} { "greeting": "Thank you for calling. Our office is currently closed", "transfer_available": "false" } ``` Reference these in your prompt to adjust behavior. ### IVR-Style Routing Configure multiple agents for different departments, each with its own phone number: * Sales: `+12025551111` → Sales Agent * Support: `+12025552222` → Support Agent * Billing: `+12025553333` → Billing Agent ### Escalation to Human Enable the transfer tool to allow the agent to escalate to a human: 1. Navigate to the **Tools** tab 2. Click **Configure** on the **Transfer** tool 3. Add transfer routes: * **To number**: `+12025559999` (your support team) * **Condition**: `When the customer requests to speak with a human or when the issue is complex` 4. Click **Save** The agent will automatically offer to transfer when appropriate. ## Troubleshooting * Verify the phone number is assigned to the agent in the Phone Numbers tab * Check that the agent status is set to Active in the Overview tab * Confirm the number is properly configured in your SIP trunk * Review your telephony provider's routing configuration * Check for errors in your pre-call webhook (if configured) * Review the call transcript in Call Logs for error messages * Verify your prompt is not empty or malformed * Check Advanced tab timeout settings * Ensure the First Message field is filled in and saved * Check that the message doesn't exceed 4000 characters * Verify there are no syntax errors in variable references * Review the call recording to confirm what the agent actually said * Verify variable syntax uses double curly braces: `{{variable_name}}` * Check that your pre-call webhook is returning the correct variable names * Ensure the webhook response is valid JSON * Review webhook logs for errors or timeouts ## Next Steps Configure call transfer, end call, and custom tools Implement pre-call and post-call webhook integrations Set up outbound calling capabilities Manage phone numbers programmatically via API # Knowledge Bases Source: https://docs.revring.ai/platform/knowledge-bases Give your agents access to your documents, URLs, and text content for grounded, accurate answers Knowledge bases allow your AI agents to retrieve information from your own documents during phone calls. Instead of relying solely on the system prompt, agents can search through uploaded files, web pages, and text content to provide accurate, grounded answers. ## Overview A knowledge base is a collection of **sources** — files, URLs, or text snippets — that your agent can search during a conversation. When a caller asks a question, the agent retrieves the most relevant passages from the knowledge base and uses them to formulate a response. This is especially useful for: * **Product catalogs** — answer questions about pricing, features, and availability * **FAQ documents** — handle common customer inquiries accurately * **Policy documents** — reference specific terms, conditions, or procedures * **Internal wikis** — provide support agents with up-to-date information * **Training materials** — ensure consistent, accurate responses across all calls ## Creating a Knowledge Base ### From the Dashboard 1. Navigate to **Knowledge Bases** in the sidebar 2. Click **Create Knowledge Base** 3. Enter a **Name** and optional **Description** 4. Click **Create** ### Via API ```bash theme={null} curl -X POST https://api.revring.ai/v1/knowledge-bases \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Product FAQ", "description": "Frequently asked questions about our products" }' ``` ## Adding Sources Each knowledge base can contain up to **50 sources**. Sources are processed asynchronously — after adding a source, it moves through the following statuses: | Status | Description | | - | - | | `queued` | Source is queued for processing | | `processing` | Source is being indexed and will be available shortly | | `ready` | Source is indexed and available for retrieval | | `failed` | Processing failed (check `errorMessage` for details) | ### Text Sources Add text content directly. Useful for FAQ lists, policy excerpts, or any structured text. **Dashboard:** Click **Add Source** → **Text**, enter a name and content, then click **Add Text Source**. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/knowledge-bases/{id}/sources \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sourceType": "text", "displayName": "Return Policy", "content": "Our return policy allows returns within 30 days of purchase. Items must be in original condition with receipt. Refunds are processed within 5-7 business days." }' ``` Text sources can be up to 100,000 characters. ### URL Sources Add a web page URL. RevRing fetches and indexes the page content automatically. **Dashboard:** Click **Add Source** → **URL**, enter a **Name** and the **URL**, then click **Add URL Source**. Turn on **Read linked pages too** to read a whole site, and pick a schedule under **Keep it current**. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/knowledge-bases/{id}/sources \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sourceType": "url", "originalUrl": "https://www.example.com/faq", "displayName": "FAQ Page" }' ``` Only `http://` and `https://` URLs are supported. Without `crawl` settings, the single page is read once, when the source is added. #### Whole websites Add `crawl` to read the pages a URL links to as well, so one source covers a help center or documentation site. RevRing starts at `originalUrl` and follows links on the same site under one path, up to `maxPages` pages, and combines them into one source with each page's address. Linked files (images, PDFs, scripts, feeds, text files) are not followed; add those as file sources. ```bash theme={null} curl -X POST https://api.revring.ai/v1/knowledge-bases/{id}/sources \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sourceType": "url", "originalUrl": "https://help.example.com/", "displayName": "Help center", "crawl": { "maxPages": 25, "includePath": "/articles/", "refreshDays": 7 } }' ``` | Field | Description | | - | - | | `maxPages` | Pages to read, 1 to 50 (default 1: only `originalUrl`). | | `includePath` | Only follow links whose path starts with this, for example `/help/`. Defaults to the folder of `originalUrl`, so `https://example.com/help/start` stays under `/help/`. | | `refreshDays` | Read the pages again every 1 to 90 days. Leave it out to read them once. | Reading a site takes up to a few minutes. When it finishes, the source's `crawl` shows how many `pages` were read and when (`lastFetchedAt`). The content of all pages counts toward one source. #### Keeping URL sources current With `refreshDays` set, RevRing reads the pages again on that schedule. To read a URL source again right away (after you publish a change, for example), hover the source in the dashboard and click the refresh icon, or call: ```bash theme={null} curl -X POST https://api.revring.ai/v1/knowledge-bases/{id}/sources/{sourceId}/refresh \ -H "x-api-key: YOUR_API_KEY" ``` The refresh runs in the background (`202`). Your agents keep using the current content the whole time: new content replaces it only once it is fully indexed, and when nothing changed, nothing is replaced. If a refresh cannot read the site, the current content stays. A source can be refreshed once a minute (`429` otherwise), not while its first read is still running (`409`), and only URL sources can be refreshed (`400`). ### File Sources Upload documents directly. Supported formats: **PDF, TXT, DOCX, MD, CSV, JSON**. **Dashboard:** Click **Add Source** → **File**, then select a file from your computer. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/knowledge-bases/{id}/sources \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@/path/to/document.pdf" ``` Maximum file size is **20 MB**. ### Checking Source Status After adding a source, you can check its processing status: ```bash theme={null} curl https://api.revring.ai/v1/knowledge-bases/{id}/sources/{sourceId} \ -H "x-api-key: YOUR_API_KEY" ``` The response includes a `status` field (`queued`, `processing`, `ready`, or `failed`) and an `errorMessage` if processing failed. ## Linking Knowledge Bases to Agents Once your knowledge base has sources in the `ready` state, link it to one or more agents. ### From the Dashboard 1. Open your agent's settings 2. Go to the **Knowledge** tab 3. Click **Link Knowledge Base** 4. Select the knowledge base you want to link — it's linked immediately ### Via API ```bash theme={null} curl -X POST https://api.revring.ai/v1/agents/{agentId}/knowledge-bases \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "knowledgeBaseId": "kb_abc123" }' ``` To unlink: ```bash theme={null} curl -X DELETE https://api.revring.ai/v1/agents/{agentId}/knowledge-bases/{knowledgeBaseId} \ -H "x-api-key: YOUR_API_KEY" ``` ## Retrieval Settings When a knowledge base is linked to an agent, you can configure how the agent uses it. These settings are on the agent, not the knowledge base — so different agents can use the same knowledge base with different retrieval behaviors. ### Retrieval Mode | Mode | Description | | - | - | | `automatic` | Labeled **Automatic (RAG)** in the dashboard. The agent automatically searches the knowledge base before every response. This is the default and recommended mode. | | `query_tool` | Labeled **Query Tool** in the dashboard. The agent uses a dedicated tool to search the knowledge base. This gives you more control over when retrieval happens — useful if you want the agent to only search when explicitly asked. | **Small knowledge bases are used in full.** In automatic mode, when the combined text of an agent's knowledge bases is small enough, the agent is simply given all of it — no search step, no chance of missing a passage, and no added latency. Larger knowledge bases (and ones built from uploaded files) use retrieval as described below. This applies across calls, web chat, and SMS. ### Chunks to Retrieve The number of text passages to retrieve per query (1–20, default 3). Higher values provide more context but use more tokens. Start with the default and increase if the agent is missing relevant information. ### Similarity Threshold Minimum relevance score for retrieved passages (0.0–1.0, default 0). Set this higher (e.g., 0.3–0.5) to filter out loosely related content. A value of 0 returns the top results regardless of relevance score. ### Configuring via API ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "knowledgeBaseRetrievalMode": "automatic", "knowledgeBaseChunks": 5, "knowledgeBaseSimilarityThreshold": 0.3 }' ``` ## Managing Sources ### Deleting a Source To remove a source from a knowledge base: **Dashboard:** Hover over the source row and click the **trash icon**, then confirm deletion. **API:** ```bash theme={null} curl -X DELETE https://api.revring.ai/v1/knowledge-bases/{id}/sources/{sourceId} \ -H "x-api-key: YOUR_API_KEY" ``` ### Deleting a Knowledge Base Deleting a knowledge base removes all its sources and unlinks it from any agents. **Dashboard:** Open the knowledge base, click the **⋮ menu**, then select **Delete Knowledge Base**. **API:** ```bash theme={null} curl -X DELETE https://api.revring.ai/v1/knowledge-bases/{id} \ -H "x-api-key: YOUR_API_KEY" ``` ## Best Practices ### Content Quality * **Be specific**: Focused, well-structured content retrieves better than long, rambling documents * **Use clear headings**: Documents with clear sections help the retrieval system find relevant passages * **Keep content current**: Set `refreshDays` on URL sources, or refresh them after your site changes; replace file and text sources when the underlying content changes * **Avoid duplication**: Don't add the same content multiple times across different sources ### Prompt Integration Reference the knowledge base in your agent's prompt to guide how it uses retrieved information: ``` You are a customer support agent for Acme Corp. When customers ask about our products, policies, or procedures, use the information from your knowledge base to provide accurate answers. If you can't find the answer in your knowledge base, let the customer know and offer to transfer them to a specialist. Do not make up information — only share what you can verify from your knowledge base. ``` ### Source Organization * Create separate knowledge bases for different topics (e.g., "Product Catalog", "Support Policies", "Pricing") * Link only the relevant knowledge bases to each agent * Keep source count manageable — a few high-quality sources outperform many low-quality ones ## Troubleshooting Most sources process within a few seconds to a minute; a URL source that reads a whole site can take a few minutes. If a source stays in `processing` for more than 15 minutes, it may have encountered an issue. Try deleting and re-adding the source. For file sources, ensure the file is not corrupted and is in a supported format. Check the `errorMessage` field on the source for specifics. Common causes include: * URL is unreachable or returns an error * File format is not supported * File is corrupted or empty * Text content is empty * Verify the knowledge base is linked to the agent (check the Knowledge tab) * Ensure at least one source is in `ready` status * Check that retrieval mode is set to `automatic` (or `query_tool` if you prefer manual control) * Add guidance in your prompt telling the agent to use its knowledge base * Try increasing the `knowledgeBaseChunks` value if relevant content is being missed * Review the source content for accuracy * Increase `knowledgeBaseSimilarityThreshold` to filter out loosely related passages * Decrease `knowledgeBaseChunks` to reduce noise from less relevant results * Add instructions in your prompt to only use verified knowledge base content ## Next Steps Configure system and custom tools Manage knowledge bases programmatically Customize agent behavior with variables Build structured conversation flows # Messaging Source: https://docs.revring.ai/platform/messaging Let your agents send text messages (SMS), from the same number they call from Connect a messaging provider so your agents can send text messages. Your agents can send an SMS **during a live call** (for example, texting the caller a booking or payment link), using the same number they call from. They can also hold [two-way text conversations](/platform/two-way-messaging) and send texts outside of a call. ## How it works 1. You connect your own messaging provider once (Twilio today). 2. You enable **Send SMS on call** for an agent and choose that connection. 3. During a call, when it's useful, the agent texts the person on the call. For example, the caller says "can you text me the link?" and the agent sends it while staying on the call. 4. The message is sent through your connected provider account. You bring your own provider account, so messages are sent from your numbers and billed to you by your provider, at your provider's rates. ## Prerequisites Before you begin: * An active RevRing account with at least one configured agent * Your own Twilio account (this can be the same account you use for voice) * A Twilio **Messaging Service** with at least one SMS-capable number in its sender pool (recommended). Twilio's Messaging Service picks the sender for each message and handles compliance features for you. To send texts from the **same number** your agent calls from, add that number to your Twilio Messaging Service's sender pool, or set it as the agent's send-from number in the next steps. ## Setup ### Step 1: Connect your provider 1. In the RevRing dashboard, go to [Connect → Messaging](https://dashboard.revring.ai/app/messaging). 2. Click **Connect provider** and choose **Twilio**. 3. Enter the values from your [Twilio Console](https://console.twilio.com): * **Account SID** (starts with `AC`) * **Auth Token** * **Messaging Service SID** (starts with `MG`) — recommended 4. Click **Connect**. We verify the credentials with your provider before saving them, so you'll know right away if something is off. Your Auth Token is stored securely so we can send messages on your behalf, and it is never shown again or returned by the API. You can rotate it any time by editing the connection. ### Step 2: Enable Send SMS on an agent 1. Go to [Agents](https://dashboard.revring.ai/app/agents) and open the agent. 2. Open the **Tools** tab and find **Send SMS on call**. 3. Turn it on, then: * **Messaging connection** — choose the connection from Step 1. * **Send-from number** (optional) — the number texts should come from. Leave blank to let your Messaging Service choose the sender. Set a number to always send from it (it must be SMS-capable on your provider account). * **Custom instructions** (optional) — override when and how the agent should text the caller. 4. Click **Save**. That's it. On the agent's next call, it can text the caller when appropriate. ## Guiding when the agent texts The agent decides when to send a text based on the conversation and its prompt. To make it reliable, mention it in your agent's prompt, for example: ```text theme={null} If the caller asks for a link, address, or confirmation, offer to text it to them and then send it. ``` You can also use the **Custom instructions** field on the Send SMS tool to tune this behavior without touching your main prompt. The agent can only text the person it is currently on the call with. It writes the message itself, in your agent's voice, based on what the caller asked for. ## Using the same number for calls and texts A common goal is to have a contact see calls and texts from one number. Because your provider account owns the numbers, you control this: * Make sure the number your agent calls from is also SMS-capable in your provider account. * Add it to your Twilio Messaging Service sender pool, or set it as the agent's **Send-from number**. ## Manage connections via API If you provision many agents or brands programmatically, you can manage messaging connections through the API instead of the dashboard. See [Messaging Connections](/api-reference/messaging-connections/list). ## Troubleshooting ### "We couldn't verify those credentials" **Cause:** The Account SID or Auth Token is incorrect. **Solution:** Re-copy both from your Twilio Console and try again. The Account SID starts with `AC`. ### "That Messaging Service SID wasn't found on the account" **Cause:** The Messaging Service SID doesn't belong to that account. **Solution:** Confirm the SID (starts with `MG`) under Messaging in your Twilio Console, and that it's on the same account as the Account SID you entered. ### The agent isn't texting **Solution:** 1. Confirm **Send SMS on call** is enabled on the agent and a connection is selected. 2. Make sure the connection has a Messaging Service, or set a **Send-from number** on the agent. 3. Confirm the send-from number is SMS-capable in your provider account. 4. Encourage the behavior in your agent's prompt or the tool's custom instructions. ## Next steps Hold text conversations and send SMS outside a call Tell your agent when to text the caller See all built-in and custom agent tools Manage connections programmatically Open the dashboard # Multilingual Source: https://docs.revring.ai/platform/multilingual Build AI voice agents that speak multiple languages RevRing agents speak and understand 20 languages, plus a multilingual mode for callers who switch languages, with 300+ voices. ## Supported Languages The agent's `language` setting chooses how the agent listens and speaks: | Language | Code | Language | Code | | - | - | - | - | | English | `en` | Arabic | `ar` | | Spanish | `es` | Russian | `ru` | | French | `fr` | Polish | `pl` | | Chinese | `zh` | Dutch | `nl` | | Korean | `ko` | Hindi | `hi-IN` | | Japanese | `ja` | Bengali | `bn-IN` | | German | `de` | Tamil | `ta-IN` | | Italian | `it` | Telugu | `te-IN` | | Portuguese | `pt` | Gujarati | `gu-IN` | | Multilingual | `multi` | Kannada, Marathi | `kn-IN`, `mr-IN` | ### Multilingual mode With `"language": "multi"` the agent understands callers speaking English, Spanish, French, German, Italian, Portuguese, Dutch, Russian, Hindi or Japanese, including callers who switch mid-call, and replies in the language the caller speaks. Tell the agent how to choose in the prompt, for example: *"Reply in the language the caller uses. If they switch to Spanish, continue in Spanish."* Pick a voice that sounds natural in every language you expect. Voices are also available in Turkish, Greek, Ukrainian, Danish, Estonian, Finnish, Norwegian, Swedish and Vietnamese; preview them in the [Voice Library](https://dashboard.revring.ai/app/agents/voices). **Voice Flexibility:** Any voice can be used with any language setting. For example, you can use a Spanish-labeled voice on an English-language agent to achieve a Spanish accent, or mix and match voices and languages to create the exact speaking style you need. ## Setting Agent Language ### Via Dashboard 1. Navigate to [Agents](https://dashboard.revring.ai/app/agents) and select your agent 2. Go to the **Overview** tab 3. Select your desired **Language** from the dropdown and click **Save** 4. Go to the **Voice** tab 5. Choose a **Voice** that matches your selected language 6. Click **Save Voice** You can preview all available voices in the [Voice Library](https://dashboard.revring.ai/app/agents/voices). ### Via API Set the language when creating or updating an agent: ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/agent_123abc \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "language": "es", "voiceId": "revring-rafael" }' ``` **Supported language codes:** * `en` (English) * `es` (Spanish) * `pt` (Portuguese) * `de` (German) * `it` (Italian) * `nl` (Dutch) * `zh` (Chinese) * `ja` (Japanese) * `hi` (Hindi) * `tr` (Turkish) * `el` (Greek) * `uk` (Ukrainian) * `ar` (Arabic) * `da` (Danish) * `et` (Estonian) * `fi` (Finnish) * `nb` (Norwegian Bokmål) * `no` (Norwegian) * `sv` (Swedish) * `vi` (Vietnamese) ## One agent, many languages Use one agent for callers in different languages: set the language per call, and give the agent a voice and opening line for each language. ### Language per call When you know the caller's language before the call (from your CRM, for example), pass it when you place the call. The agent listens and speaks in that language for this call only: ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "AGENT_ID", "toNumber": "+15551234567", "fromNumber": "+15559876543", "language": "es" }' ``` For inbound calls, your [pre-call webhook](/platform/webhooks) can answer with `"overrides": { "language": "es" }` to run that call in Spanish. The `{{language}}` variable carries the call's language. ### Language presets `languagePresets` gives the agent a voice and an opening line per language. When a call runs in a language that has a preset, the agent uses that preset's voice and opening line instead of its own: ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "languagePresets": { "es": { "voiceId": "SPANISH_VOICE_ID", "firstMessage": "Hola {{first_name}}, le habla Maya de Northside." }, "fr": { "voiceId": "FRENCH_VOICE_ID", "firstMessage": "Bonjour, ici Maya de Northside." } } }' ``` * A preset can set the voice, the opening line, or both. Whatever it leaves out comes from the agent. * A regional code uses its base language's preset when it has none of its own: a call in `pt-BR` uses the `pt` preset. * A voice or opening line given explicitly for one call (per-call `voiceId`, or the pre-call webhook's `voiceId` or `firstMessage`) wins over the preset. * Preset voices come from the [Voice Library](https://dashboard.revring.ai/app/agents/voices) (`GET /v1/voices`); unknown voices are refused. * Agents imported from ElevenLabs bring their per-language opening lines. ## Writing Prompts for Multilingual Agents ### Best Practice: Match Prompt Language For optimal performance, write your agent's prompt in the same language as the agent's configured language setting: **Spanish Agent Example:** ``` Eres un asistente de servicio al cliente profesional y amigable. INSTRUCCIONES: - Saluda a los clientes calurosamente - Responde preguntas sobre nuestros productos - Mantén las respuestas concisas y útiles Siempre sé cortés y profesional. ``` ### Cross-Language Prompts While not required, agents can follow prompts written in a different language than their speaking language. The AI is intelligent enough to understand instructions in one language while conversing in another: **English Prompt for Spanish Agent:** ``` You are a customer service agent speaking Spanish. INSTRUCTIONS: - Greet customers warmly in Spanish - Answer questions about our products - Keep responses under 30 seconds Always be polite and professional. ``` However, for best results and consistency, we recommend writing prompts in the agent's configured language. ## Language System Variable The `{{language}}` system variable is automatically available in all prompts and contains the agent's language code: ``` You are speaking in {{language}}. Current language: {{language}} Timezone: {{timezone}} Current time: {{current_time}} ``` This can be useful for building flexible prompts or logging purposes. ## Voice Selection Each language has multiple voices to choose from. Visit the [Voice Library](https://dashboard.revring.ai/app/agents/voices) to preview all available voices in your selected language. ### Backup Voice Every agent can set a **Backup Voice** (in the dashboard Voice tab, or `fallbackVoiceId` via the API) for seamless voice continuity. In the rare case a voice is temporarily unavailable, the agent switches to its backup voice instantly and the conversation continues as normal. Leave it on **Automatic** to let the platform manage this for you. For multilingual agents we recommend choosing a backup voice in the agent's own language. ### English Voice Categories English offers two categories of voices: * **English Ultra-Realistic (Beta)**: Cutting-edge voices with exceptional naturalness and expressiveness * **English Stable**: Reliable, production-ready voices with consistent performance If you experience instability with Ultra-Realistic voices, try switching to an **English Stable** voice for better reliability. ## Troubleshooting ### Voice Instability If you notice inconsistent voice quality or unexpected behavior: 1. **Switch Voice Category**: For English agents, try switching from Ultra-Realistic to Stable voices 2. **Verify Language Match**: Ensure the selected voice matches your agent's language setting ### Need Help? For assistance with multilingual setup, voice selection, or configuration, contact support. Our team can help you: * Choose the right voices for your use case * Optimize prompts for specific languages * Configure multilingual routing strategies * Troubleshoot voice quality issues ## Complete Example Here's a complete Spanish agent configuration: **Via API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/agents \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sipTrunkId": "trunk_abc123", "name": "Asistente en Español", "language": "es", "voiceId": "revring-rafael", "promptTemplate": "Eres un asistente de servicio al cliente. Ayuda al cliente con sus preguntas.", "firstMessage": "Hola, bienvenido. ¿Cómo puedo ayudarte hoy?" }' ``` **Via Dashboard:** 1. Create agent with name "Asistente en Español" 2. In **Overview** tab: * Language: Spanish * First Message: "Hola, bienvenido. ¿Cómo puedo ayudarte hoy?" * Prompt: "Eres un asistente de servicio al cliente. Ayuda al cliente con sus preguntas." 3. In **Voice** tab: * Voice: Rafael For callers who switch languages mid-call, use multilingual mode. For a market that speaks one language, a dedicated agent in that language with a native voice gives the best quality. # Outbound Calls Source: https://docs.revring.ai/platform/outbound Send outbound calls through your AI agents Configure your AI agents to initiate outbound phone calls for sales, surveys, reminders, and more. ## How Outbound Calls Work When you send an outbound call through RevRing: 1. You specify the agent, from number (caller ID), to number (recipient), and optional variables 2. RevRing initiates the call through your SIP trunk 3. When the recipient answers, the agent begins speaking based on its first message and prompt 4. The conversation proceeds according to your agent's configuration 5. After the call ends, RevRing sends complete call details to your post-call webhook (if configured) ## Sending Test Calls via Dashboard ### Using the Send Call Page The [Send Call](https://dashboard.revring.ai/app/testing/send) page is the primary way to test outbound calls from the dashboard. 1. Navigate to [Send Call](https://dashboard.revring.ai/app/testing/send) 2. Select your **Agent** from the dropdown 3. Enter the **From Number** (must be a number on your SIP trunk, E.164 format) 4. Enter the **To Number** (the recipient's phone number, E.164 format) 5. Optionally set a **Caller ID Name** (up to 15 characters) 6. Add **Variables** if your prompt uses dynamic content 7. Click **Send Test Call** The **Live Transcript** card on the right updates in real time as the conversation progresses. After the call ends, click **View full call details** to see the recording, summary, and metrics. Save frequently used configurations by entering a name and clicking **Save Config**. Load saved configs from the dropdown to quickly re-run tests with different scenarios. ### Using the Test Panel The test panel on the right side of the agent page lets you call your agent directly from the browser via WebRTC. This is useful for quick iteration — you can tweak the prompt and immediately test without needing a phone. Use **Test Variables** to set `direction`, `customer_name`, or any other variables your prompt expects. To test outbound calling over the phone network (with real caller ID and SIP routing), use the **Send Call** page instead. ## Sending Calls via API For production outbound calling at scale, use the RevRing API. ### Basic Outbound Call ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_id_here", "fromNumber": "+12025551234", "toNumber": "+14155552671" }' ``` ### With Caller ID Name You can set a custom caller ID name (up to 15 characters) that is displayed to the recipient when the call arrives: ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_id_here", "fromNumber": "+12025551234", "toNumber": "+14155552671", "callerIdName": "Acme Support" }' ``` Caller ID name support depends on your SIP trunk provider and the recipient's carrier. Not all carriers display the caller ID name. ### With Custom Variables ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_id_here", "fromNumber": "+12025551234", "toNumber": "+14155552671", "variables": { "customer_name": "John Smith", "appointment_time": "2:00 PM today", "confirmation_code": "ABC123" } }' ``` The API returns a call object with an `id` that you can use to track the call's status. See the [API Reference](/api-reference/calls/send) for complete documentation. ## Configuring Outbound Agent Behavior ### First Message For outbound calls, the first message is critical since you're initiating contact. Configure it in the **Overview** tab: ``` Hi {{customer_name}}, this is Alex calling from Acme Corp regarding your appointment scheduled for {{appointment_time}}. Is now a good time to talk? ``` Unlike inbound calls where leaving first message empty makes sense, outbound calls should almost always include a first message to properly introduce the purpose of the call. ### When the Phone Is Still Ringing Some carrier routes report a call as answered before the person has actually picked up, occasionally by 15 seconds or more. Anything the agent says in that window is spoken into a ringing phone, and the person hears nothing or only the tail of the opening. Two settings control this, and both default to current behavior so existing agents are unchanged. **Pause before speaking.** Set `firstMessageDelayMs` to wait that many milliseconds after the call is reported answered before the opening is spoken. Tune it to the route: start around 2000 to 3000 and adjust. If the person speaks during the pause, the opening is skipped and the agent simply replies to them, so the two never talk over each other. ```json theme={null} { "firstMessageDelayMs": 2500 } ``` **Let the caller speak first.** Set `startSpeaker` to `"user"` and the agent stays silent until the person says something, then opens the conversation from its prompt. Nothing is spoken during ringing at all. The re-engagement prompt is held as well, so the silence is genuine rather than a short delay. ```json theme={null} { "startSpeaker": "user" } ``` **If the person never speaks.** When the caller speaks first, set `beginAfterUserSilenceMs` and the agent opens the call itself once the person has said nothing for that many milliseconds (500 to 60000). If they speak first, the agent replies to them as usual. With a static first message the agent speaks it; otherwise it generates its opening from the prompt. Leave it unset to keep waiting. Keep it shorter than the silence end call timeout, otherwise a silent call ends before the agent speaks. ```json theme={null} { "startSpeaker": "user", "beginAfterUserSilenceMs": 4000 } ``` Use the pause when you want the agent to open the call but slightly later, and `startSpeaker` when you would rather the person open it. A third option needs no settings at all: keep the opening to a short line such as "Hi, is this Alex?" so that little is lost if part of it is missed. ### Different Opening Settings for Outbound Calls An agent that handles both directions can open each one differently: greet inbound callers straight away, but wait for the person to say "Hello?" on calls it places. `startSpeakerOutbound` and `firstMessageDelayMsOutbound` apply to outbound calls only. Left unset, outbound calls use `startSpeaker` and `firstMessageDelayMs` like every other call, so existing agents are unchanged. In the dashboard, tick **Use different opening settings for outbound calls** on the **Overview** tab. ```json theme={null} { "startSpeaker": "agent", "startSpeakerOutbound": "user", "beginAfterUserSilenceMs": 4000 } ``` ### Per-Call Opening Overrides `POST /v1/calls` accepts `startSpeaker`, `firstMessageDelayMs` and `beginAfterUserSilenceMs` to change how a single call opens, for example a longer pause for a list known to answer late, without editing the agent. For each setting, a value sent with the call takes precedence over the agent's outbound setting, which takes precedence over its general setting. The agent's saved settings are unchanged. ```json theme={null} { "agentId": "agent_123", "toNumber": "+15551234567", "fromNumber": "+15557654321", "startSpeaker": "user", "beginAfterUserSilenceMs": 3000 } ``` ### Separate Inbound and Outbound Openings Agents that handle both directions often need different openings: "Thank you for calling..." inbound versus "Hi, this is ... calling from ..." outbound. Set the **Outbound First Message** field (API: `firstMessageOutbound`) with the outbound text, and keep **First Message** as the inbound text. On outbound calls the agent speaks the outbound text; on inbound calls (or when the outbound field is empty) it speaks First Message, so existing agents are unaffected until you set it. Both fields support `{{variables}}` with the same substitution as the prompt, so per-call personalization works in either direction. This applies to static first messages; in dynamic mode the opening is generated from the prompt, which can branch on the `{{direction}}` variable instead. ### Outbound-Specific Prompts Tailor your prompt for outbound scenarios: ``` You are making an outbound call to confirm a customer's appointment. Your goals: 1. Verify the customer is available to talk (ask permission) 2. Confirm their appointment for {{appointment_time}} 3. Answer any questions about the appointment 4. Offer to reschedule if needed 5. Thank them and end the call politely If they ask to be removed from future calls, acknowledge and confirm you'll update their preferences. Keep the call brief (under 2 minutes) and respectful of their time. ``` ### Using Variables for Personalization Variables allow you to customize each call without changing the agent configuration: 1. Reference variables in your prompt and first message using `{{variable_name}}` syntax 2. Pass variable values when sending the call via API or dashboard 3. The agent will replace placeholders with the actual values during the conversation Common variable use cases: * Customer name, account number, order ID * Appointment dates and times * Custom messages or offers * Callback reasons or context ## Voicemail Detection Voicemail detection is particularly useful for outbound calling to avoid wasting resources on unanswered calls. ### Enable Voicemail Detection 1. Navigate to the **Tools** tab 2. Locate **Voicemail Detection** under System Tools 3. Click **Configure** 4. Choose your action: * **Hangup**: End the call immediately when voicemail is detected (saves costs) * **Leave Message**: Speak a pre-recorded message before hanging up 5. If leaving a message, configure the voicemail message: ``` Hi {{first_name}}, this is Acme Corp calling to confirm your appointment. Please call us back at 1-800-555-0123 at your earliest convenience. Thank you. ``` The voicemail message supports `{{variables}}`, just like your prompt and first message. Variables are filled in from the call's variables (for example, `first_name`). If a variable has no value, the placeholder is left as-is. 6. Click **Save** For high-volume outbound campaigns, choose "Hangup" to minimize costs and focus agent time on live conversations. ### When Nobody Speaks Some voicemail systems answer without any greeting the agent can hear, so there is nothing for voicemail detection to key on. The platform closes these calls automatically: if nobody has spoken by the third re-engagement attempt (and the call is at least 20 seconds old), the agent says a short sign-off and ends the call with hangup cause `SILENCE_TIMEOUT`. This applies only while the other side has said nothing at all. Once a person has spoken, the agent stays patient through quiet stretches and keeps re-engaging normally. ## Call Pacing and Concurrency ### Understanding Concurrency Limits RevRing enforces a concurrency limit on the maximum number of simultaneous calls per organization. You can view your current limit and usage on the [Billing page](https://dashboard.revring.ai/app/billing). ### Call Queuing When you send an outbound call and all concurrent call slots are in use, RevRing automatically **queues** the call. Queued calls are dispatched in approximate order as slots become available. While a call is waiting, its status is `QUEUED`. Once a slot opens and the call is dispatched, the status transitions to `INITIATED` and then `ONGOING` when the recipient answers. Queued calls have a maximum wait time of approximately 4 hours. If a slot does not open within that window, or if the call fails to dispatch for another reason, the call transitions to `FAILED` rather than remaining `QUEUED` indefinitely. Inspect the `errorMessage` field to see why. You can check a call's status at any time: ```bash theme={null} curl https://api.revring.ai/v1/calls/{callId} \ -H "x-api-key: YOUR_API_KEY" ``` To cancel a queued call before it is dispatched: ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls/{callId}/cancel \ -H "x-api-key: YOUR_API_KEY" ``` Only calls with status `QUEUED` can be cancelled. Once a call moves to `INITIATED` or `ONGOING`, it cannot be cancelled through this endpoint. ### Opting Out of Queuing If you prefer to handle queuing in your own system, pass `skipQueue: true` when creating the call. This tells RevRing to return an immediate `429` error instead of queuing when the concurrency limit is reached: ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_id_here", "fromNumber": "+12025551234", "toNumber": "+14155552671", "skipQueue": true }' ``` **Response when at capacity (429)**: ```json theme={null} { "error": "concurrency_limit" } ``` ### Best Practices for High Volume * **Leverage built-in queuing**: Let RevRing queue calls automatically rather than managing pacing yourself * **Monitor active calls**: Track concurrent calls via the [Analytics API](/api-reference/analytics/overview) * **Cancel unnecessary queued calls**: If priorities change, cancel queued calls that are no longer needed * **Handle errors gracefully**: Implement retry logic with exponential backoff for non-queued calls * **Respect time zones**: Schedule calls during appropriate hours for the recipient's location ## Monitoring Outbound Calls ### Real-Time Monitoring View active outbound calls on the [Overview](https://dashboard.revring.ai/app) page: * **Active Calls**: See current ongoing outbound calls * Filter by agent to monitor specific campaigns ### Call Logs Track all outbound calls in [Call Logs](https://dashboard.revring.ai/app/calls): 1. Filter by **Direction**: Select "Outbound" 2. Filter by **Status**: View completed, failed, ongoing, or canceled calls 3. Filter by **Agent**: Focus on specific agents or campaigns 4. Click any call to view: * Complete transcript * Call recording * Duration and timestamps * Variables used * Hangup cause and error messages (if applicable) ### Success Metrics Monitor key metrics to optimize your outbound campaigns: * **Connection rate**: Percentage of calls that connect vs. no answer/busy * **Average duration**: Typical call length for successful connections * **Completion rate**: Calls that reach the intended outcome * **Cost per call**: Track expenses across campaigns ## Advanced Features ### Post-Call Webhooks Receive call results immediately after each call ends: 1. Configure **Post-call Webhook URL** in the **Advanced** tab: ``` https://api.yourcompany.com/webhooks/post-call ``` 2. RevRing will POST complete call details including: * Transcript * Recording URL * Call duration and timestamps * Variables used * AI-generated summary Use this to: * Update your CRM with call outcomes * Trigger follow-up actions * Track campaign performance * Schedule callbacks for unsuccessful attempts See the [Webhooks guide](/platform/webhooks) for implementation details. ### Call Transfer Enable transfers to allow the agent to escalate to a human representative: 1. Navigate to the **Tools** tab 2. Click **Configure** on the **Transfer** tool 3. Add transfer routes: * **To number**: `+12025559999` (your sales team) * **Condition**: `When the customer wants to speak with a human or has complex questions` 4. Click **Save** The agent will offer to transfer when the condition is met. ### End Call Control Configure when the agent should end calls: 1. Navigate to the **Tools** tab 2. Click **Configure** on the **End Call** tool 3. Set custom end call rules: ``` End the call when: - The customer confirms their appointment - The customer declines and asks not to be called again - The customer is not available and asks to call back later - You've been speaking for more than 3 minutes ``` 4. Click **Save** ## Compliance and Best Practices ### Legal Requirements When making outbound calls, ensure compliance with: * **TCPA** (US): Obtain prior express written consent for marketing calls * **TSR** (US): Maintain do-not-call lists and honor opt-out requests (RevRing can [add callers who ask automatically](/platform/campaigns#add-callers-who-ask-automatically)) * **GDPR** (EU): Respect data protection and privacy regulations * **Local regulations**: Follow country-specific telemarketing laws You are responsible for ensuring compliance with all applicable laws and regulations. RevRing provides the technical platform but does not provide legal advice. ### Best Practices * **Respect time zones**: Call during appropriate hours (typically 9 AM - 9 PM local time) * **Honor opt-outs**: Immediately stop calling anyone who requests removal * **Clear identification**: Always identify your company and purpose clearly * **Keep it brief**: Respect the recipient's time with concise, focused calls * **Professional tone**: Use appropriate language and maintain a professional demeanor * **Quality over quantity**: Focus on successful conversations, not just call volume ## Common Use Cases ### Appointment Reminders ``` Hi {{customer_name}}, this is Acme Dental calling to remind you about your appointment tomorrow at {{appointment_time}} with Dr. {{doctor_name}}. Please respond with "confirm" if you'll be able to make it, or let me know if you need to reschedule. ``` ### Sales Follow-Up ``` Hi {{lead_name}}, this is {{agent_name}} from {{company_name}}. You recently expressed interest in {{product_name}} on our website. I'm calling to see if you have any questions and to share how we can help {{solve_problem}}. ``` ### Survey and Feedback ``` Hi {{customer_name}}, this is a brief call from Acme Corp. We recently helped you with {{service_type}} and would love to get your feedback. On a scale of 1 to 10, how satisfied were you with our service? ``` ### Payment Reminders ``` Hi {{customer_name}}, this is Acme Billing. We're calling about invoice {{invoice_number}} with a balance of {{amount}} due on {{due_date}}. Are you able to make a payment today, or would you like to discuss payment options? ``` ## Troubleshooting * Verify the from number is assigned to your SIP trunk * Ensure the to number is in valid E.164 format * Check your SIP trunk has sufficient capacity and balance * Review your telephony provider's outbound restrictions * Confirm the destination country is allowed by your provider * Ensure voicemail detection is enabled in the Tools tab * Verify the action (hangup or leave message) is configured * Check the call recording to confirm voicemail was actually reached * Some voicemail systems may not be detectable - this is a known limitation * Verify variable names match exactly between API call and prompt (case-sensitive) * Check that variables are passed in the API request body * Ensure variable syntax in prompt uses double curly braces: `{{variable}}` * Review the call transcript to see what was actually spoken * Check if you're calling valid, reachable numbers * Verify caller ID (from number) is not flagged as spam * Consider time of day - avoid early morning or late evening * Review hangup causes in call logs to identify patterns * Ensure your first message clearly identifies the caller * Implement exponential backoff and retry logic * Reduce call pacing or batch size * Check your current limit and usage at [Billing](https://dashboard.revring.ai/app/billing) * Monitor active calls before sending new batches * Contact support to discuss increasing your organization's limit ## Next Steps Configure transfer, voicemail, and custom tools Implement post-call webhooks to capture results View complete API documentation for sending calls Query and analyze call history via API # Prompting & Variables Source: https://docs.revring.ai/platform/prompting-variables Personalize agent conversations with dynamic variables Variables enable you to personalize agent prompts and messages with dynamic data. RevRing provides system variables automatically and supports custom variables that you can set through the API or webhooks. ## Variable Types RevRing supports three types of variables: * **System Variables**: Automatically provided for every call with contextual information * **Default Variables**: Static variables you configure on the agent that apply to all calls * **Dynamic Variables**: Call-specific variables set per outbound call or via pre-call webhook for inbound calls Variables merge with the following priority: system variables are set first, then default variables are applied, and finally dynamic variables can override both system and default variables. ## System Variables System variables are automatically available in every call and provide contextual information about the call itself. | Variable | Description | Example | | - | - | - | | `{{direction}}` | Whether the call is inbound or outbound | `inbound` or `outbound` | | `{{user_number}}` | The customer's phone number | `+14155551234` | | `{{agent_number}}` | Your agent's phone number (caller ID for outbound) | `+12025559999` | | `{{current_time}}` | Current timestamp when the call starts, formatted in the agent's timezone | `Tuesday, November 26, 2025 at 2:30:00 PM EST` | | `{{timezone}}` | The agent's IANA timezone identifier | `America/New_York` | | `{{language}}` | The agent's language code | `en` | System variables are always available for you to use in your agent's prompt and first message. ### Example Usage ``` You are a customer service agent. CALL INFORMATION: - Call direction: {{direction}} - Customer phone: {{user_number}} - Our number: {{agent_number}} - Current time: {{current_time}} Greet the customer and provide assistance based on the {{direction}} call context. ``` ## Default Variables Default variables are static values you configure on your agent that apply to every call. These are useful for consistent information like business name, hours, or policies. ### Setting Default Variables Configure default variables via the [Update Agent API](/api-reference/agents/update): ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/agent_123abc \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "defaultVariables": { "business_name": "Acme Corp", "business_hours": "Monday-Friday 9am-5pm EST", "support_email": "support@acme.com" } }' ``` These variables will be available in your prompt as `{{business_name}}`, `{{business_hours}}`, and `{{support_email}}`. ### Example Prompt with Default Variables ``` You are the receptionist for {{business_name}}. INFORMATION: - Business hours: {{business_hours}} - Support email: {{support_email}} Answer customer questions about our services and schedule appointments during business hours. ``` ## Dynamic Variables Dynamic variables are set on a per-call basis and override default variables. The method for setting them differs between outbound and inbound calls. ### For Outbound Calls Set dynamic variables when sending an outbound call via the [Send Call API](/api-reference/calls/send): ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_123abc", "fromNumber": "+12025559999", "toNumber": "+14155551234", "variables": { "customer_name": "John Smith", "appointment_time": "2:00 PM today", "confirmation_code": "ABC123" } }' ``` ### For Inbound Calls Set dynamic variables by returning them from your pre-call webhook. See the [Webhooks guide](/platform/webhooks#pre-call-webhooks) for implementation details. Your webhook receives caller information and returns variables: ```json theme={null} { "variables": { "customer_name": "Sarah Johnson", "account_status": "premium", "last_order_date": "March 10, 2024" } } ``` ## Complete Example Here's a comprehensive example showing all variable types in action: ### Agent Configuration **Default Variables** (via API): ```json theme={null} { "business_name": "Dr. Smith's Dental Office", "office_hours": "Monday-Friday 8am-6pm" } ``` **Prompt**: ``` You are an appointment booking assistant for {{business_name}}. CALL INFORMATION: - Call direction: {{direction}} - Customer phone: {{user_number}} - Office phone: {{agent_number}} - Current time: {{current_time}} OFFICE DETAILS: - Hours: {{office_hours}} YOUR TASK: Help {{customer_name}} schedule a dental appointment. GREETING: Start by greeting {{customer_name}} warmly and confirming you're speaking with them at {{user_number}}. CONVERSATION FLOW: 1. Greet {{customer_name}} and mention you see they're calling from {{user_number}} 2. Ask what type of appointment they need (cleaning, checkup, emergency, etc.) 3. Check their preferred date and time 4. Confirm the appointment details including: - Patient name: {{customer_name}} - Callback number: {{user_number}} - Appointment date and time - Type of appointment 5. Let them know they'll receive a confirmation text at {{user_number}} Keep responses concise and professional. The current time is {{current_time}}, so suggest appropriate appointment slots during {{office_hours}}. ``` **First Message**: ``` Hi {{customer_name}}, this is Alex from {{business_name}}. I see you're calling from {{user_number}}. How can I help you today? ``` ### Setting Dynamic Variables **For outbound calls:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/calls \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "agent_123abc", "fromNumber": "+12025559999", "toNumber": "+14155551234", "variables": { "customer_name": "John Smith" } }' ``` **For inbound calls** (pre-call webhook response): ```json theme={null} { "variables": { "customer_name": "Sarah Johnson" } } ``` ### Where Variables Are Substituted `{{variable}}` placeholders are replaced in all of the following fields: * Agent prompt * First message * Voicemail message * Warm transfer fields: hold message, summary instructions (the briefing spoken to the person you transfer to), and intro message Substitution uses the variables available on that specific call: system variables, the agent's default variables, and the per-call variables you send. A placeholder whose variable is not set on the call is left exactly as typed (for example `{{external_id}}` stays visible), so missing data is easy to spot rather than silently disappearing. Make sure every variable referenced in these fields is supplied on each call. ## First Message The first message is the line your agent speaks when the call connects. It has two modes, controlled by the `firstMessageMode` field on the agent. ### Static (default) The agent speaks the `firstMessage` text exactly as written. It supports `{{variables}}`, just like the prompt: ``` Hi {{customer_name}}, this is Alex from {{business_name}}. How can I help you today? ``` Leave `firstMessage` empty to have the agent wait for the caller to speak first. ### Dynamic Set `firstMessageMode` to `dynamic` and the agent generates its opening line from the prompt instead of speaking a fixed string. This is useful when a single agent handles both inbound and outbound calls and needs a different greeting for each, since the `{{direction}}` system variable is available in the prompt: ``` You are a scheduling assistant for {{business_name}}. When the call begins, greet the caller: - If {{direction}} is inbound, thank them for calling {{business_name}} and ask how you can help. - If {{direction}} is outbound, introduce yourself and explain you are calling to confirm their appointment. ``` Enable it via the API: ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/agent_123abc \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "firstMessageMode": "dynamic" }' ``` In dynamic mode the `firstMessage` text is ignored. To return to a fixed greeting, set `firstMessageMode` back to `static` (or `null`). ## Working with Timezones RevRing automatically handles timezone conversion for you. The `{{current_time}}` system variable is formatted in your agent's configured timezone, and the `{{timezone}}` variable contains the IANA timezone identifier. ### Setting Agent Timezone Configure your agent's timezone via the API when creating or updating an agent: ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/agent_123abc \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "timezone": "America/New_York" }' ``` Or set it through the RevRing dashboard in your agent's configuration. ### Using Timezone in Prompts Both `{{current_time}}` and `{{timezone}}` are automatically available as system variables: ``` You are an appointment scheduling assistant for {{business_name}}. CURRENT TIME: {{current_time}} Your timezone is {{timezone}}. All times you communicate should be in this timezone. When booking appointments: 1. Use the current time above as your reference 2. Suggest available slots in {{timezone}} 3. Always mention the timezone when confirming (e.g., "2 PM Eastern Time") ``` ### Timezone Format Use IANA timezone identifiers (e.g., `America/New_York`, `Europe/London`, `Asia/Tokyo`) as they handle daylight saving time automatically. Common timezone identifiers: * **US Eastern**: `America/New_York` * **US Central**: `America/Chicago` * **US Mountain**: `America/Denver` * **US Pacific**: `America/Los_Angeles` * **UK**: `Europe/London` * **Central Europe**: `Europe/Paris` * **India**: `Asia/Kolkata` * **Australia (Sydney)**: `Australia/Sydney` ### Complete Example ``` You are scheduling appointments for {{business_name}}. CURRENT TIME & TIMEZONE: The current time is: {{current_time}} Your timezone: {{timezone}} Business hours: {{business_hours}} INSTRUCTIONS: 1. Reference {{current_time}} when discussing availability 2. All appointment times should be stated in {{timezone}} 3. When confirming appointments, explicitly mention the timezone 4. If the customer mentions a different timezone, acknowledge it but confirm appointments in {{timezone}} Example: Agent: "I have availability tomorrow at 2 PM, 3:30 PM, and 5 PM Eastern Time. Which works best for you?" Customer: "2 PM works" Agent: "Perfect! I've scheduled you for 2 PM Eastern Time on [date]. You'll receive a confirmation." ``` Set your agent's timezone to match your business location or primary customer base. For businesses serving multiple timezones, you can create separate agents for different regions, or override the system variables on a per-call basis when sending or receiving a call. The `{{current_time}}` variable is formatted in a human-readable format like "Wednesday, November 26, 2025 at 8:30:00 PM EST" to make it easy for the AI to understand and communicate naturally. ## Boosting Recognition of Names and Custom Terms Speech recognition can struggle with uncommon personal names, brand names, and industry-specific terms. You can boost recognition accuracy for terms that matter on a call in two ways: ### Automatic name boosting Any call variable whose key contains `name` (for example `first_name`, `last_name`, `company_name`, `attorney_name`) is automatically used to improve recognition when the caller says that name. No setup is needed beyond passing the variables you already use. ### The agent `sttKeyterms` setting Set boosted terms once on the agent and they apply to every call: * In the dashboard: the "Boosted Terms" field in the agent's settings. * In the API: the `sttKeyterms` array on create or update agent requests. ```json theme={null} { "sttKeyterms": ["Xylera", "Acme Corporation", "Krzysztof Wozniak"] } ``` ### The `stt_keyterms` variable For full control, set the reserved variable `stt_keyterms` to a comma-separated list of terms to boost: ```json theme={null} { "variables": { "first_name": "Theresa", "last_name": "O'Neill", "stt_keyterms": "Zantac, Krause and Kinsman, AFFF exposure" } } ``` You can set it anywhere variables are set: agent default variables, the create-call request, or your pre-call webhook response, so the boost list can be personalized per call. Guidelines: * Keep terms short: single words or short phrases up to about 4 words work best. * Boost only terms that are likely to be spoken and are hard to recognize (unusual names, product names, case types). Avoid boosting common words. * Up to 50 terms are applied per call. The agent `sttKeyterms` setting, the `stt_keyterms` variable, and automatic name variables are combined and de-duplicated. * Like any variable, `stt_keyterms` is also visible to the agent as context, which helps it spell and repeat the terms correctly. If callers report the agent mishearing a specific name or product, add it to `stt_keyterms` on the agent's default variables and it will apply to every call for that agent. ## Best Practices Use descriptive variable names like `customer_name` instead of `name` to avoid confusion when debugging transcripts. System variables like `{{direction}}` and `{{current_time}}` are particularly useful for context-aware prompts that behave differently for inbound vs outbound calls. Dynamic variables override default variables. If you set a default variable and also pass it dynamically, the dynamic value will be used for that call. # Telephony Setup Source: https://docs.revring.ai/platform/telephony Connect SIP trunks from Twilio, Telnyx, and other providers SIP trunks are the foundation of your RevRing phone system. They connect your AI agents to the public telephone network, enabling both inbound and outbound calling. ## Understanding SIP Trunks A SIP (Session Initiation Protocol) trunk is a virtual phone line that connects your RevRing agents to the phone network through a telephony provider like Twilio or Telnyx. Each trunk can handle multiple phone numbers and concurrent calls. ### Key Concepts * **Proxy Address**: The SIP server endpoint provided by your telephony provider * **Authentication**: Optional username and password credentials for SIP registration * **Phone Numbers**: The phone numbers routed through this trunk (must be in E.164 format) * **Cloud vs. On-Premise**: RevRing supports cloud-based SIP trunks from providers like Twilio and Telnyx ## RevRing SIP Domain When configuring your telephony provider to send inbound calls to RevRing, use the following SIP URI: ``` sip:sip.revring.ai ``` **SIP Domain:** `sip.revring.ai` This URI must be configured as the Origination destination in your telephony provider's trunk settings. ## Twilio Elastic SIP Trunking Twilio Elastic SIP Trunking is recommended for most users due to its reliability, global coverage, and pay-as-you-go pricing. ### Connect Twilio in one step The fastest way to bring Twilio numbers to RevRing. You give your Account SID and Auth Token, pick the numbers, and RevRing does every step below for you: it creates the Elastic SIP trunk in your Twilio account, points origination at `sip.revring.ai`, creates the termination credential, moves the numbers onto the trunk, and adds the matching trunk here. 1. Open [SIP Trunks](https://dashboard.revring.ai/app/sip-trunks) and select **Connect Twilio** 2. Enter your **Account SID** and **Auth Token** (Twilio Console home page, Account Info) and select **Find my numbers** 3. Pick the numbers to connect. Each one shows where its calls go today, and numbers already on RevRing cannot be picked again 4. Optionally choose the agent that answers these numbers, then select **Connect** Calls to the connected numbers come to RevRing right away, and your agents can call out from them. Your Twilio credentials are used for this setup only and are not stored. If any step fails, everything created in your Twilio account is removed and your numbers keep their current routing. Connecting a number moves its calls to RevRing. If the number answers calls somewhere else today (a voice webhook, a TwiML app or another SIP trunk), that setup stops receiving them. From the API: ```bash theme={null} # 1. See the account's numbers and where each routes today curl -X POST https://api.revring.ai/v1/sip-trunks/connect/twilio/numbers \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "twilio": { "accountSid": "AC...", "authToken": "..." } }' # 2. Connect the ones you want, answered by an agent curl -X POST https://api.revring.ai/v1/sip-trunks/connect/twilio \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "twilio": { "accountSid": "AC...", "authToken": "..." }, "numbers": ["+14155550123"], "agentId": "cm..." }' ``` A Twilio API key (`apiKeySid` and `apiKeySecret`) works in place of the Auth Token. To disconnect later, delete the trunk here, then delete the trunk named **RevRing** under Elastic SIP Trunking in the Twilio Console; its numbers go back to the voice settings configured on each number. To set up the trunk yourself instead, follow the steps below. ### Prerequisites * Active Twilio account * At least one phone number purchased from Twilio * Elastic SIP Trunking enabled in your Twilio Console ### Step 1: Configure Twilio 1. Log into the [Twilio Console](https://console.twilio.com) 2. Navigate to **Elastic SIP Trunking** → **Trunks** 3. Click **Create new SIP Trunk** or select an existing trunk 4. Note your **SIP Domain** (e.g., `yourtrunk.pstn.twilio.com`) ### Step 2: Create Credential List 1. In the Twilio Console, go to **Elastic SIP Trunking** → **Credential Lists** 2. Click **Create new Credential List** 3. Give it a name (e.g., `RevRing Credentials`) 4. Add a credential with a **Username** and **Password** of your choice 5. Save the Credential List ### Step 3: Assign Phone Numbers in Twilio 1. In your trunk settings, go to the **Numbers** tab 2. Click **Add Existing Number** 3. Select the phone numbers you want to use with RevRing 4. Click **Add** Phone numbers must be assigned to the trunk in Twilio before they can be used in RevRing. ### Step 4: Configure Origination (Inbound) For inbound calls to reach RevRing: 1. In your trunk settings, go to **Origination** 2. Add an **Origination SIP URI**: ``` sip:sip.revring.ai ``` 3. Set **Priority** to `1` and **Weight** to `1` ### Step 5: Configure Termination (Outbound) 1. In your trunk settings, go to **Termination** 2. Note your **Termination SIP URI** (e.g., `yourtrunk.pstn.twilio.com`) - you'll use this as the Proxy in RevRing 3. Under **Authentication** → **Credential Lists**, select the Credential List you created in Step 2 4. Optionally enable **PSTN Transfer** if you want your agents to use the Transfer To Number tool to transfer calls to external phone numbers ### Step 6: Add Trunk to RevRing 1. Navigate to [SIP Trunks](https://dashboard.revring.ai/app/sip-trunks) 2. Click **Add SIP Trunk** 3. Fill in the configuration: * **Name**: `Twilio Elastic` (or any descriptive name) * **Proxy**: Your Twilio Termination SIP URI from Step 5 (e.g., `yourtrunk.pstn.twilio.com`) - enter just the domain without the `sip:` prefix * **Username**: Enter the username from your Credential List (created in Step 2) * **Password**: Enter the password from your Credential List (created in Step 2) * **Phone Numbers**: Enter your phone numbers in E.164 format, comma-separated ``` +12025551234, +14155552671, +13105558899 ``` 4. Click **Add Trunk** You can paste a list of phone numbers separated by commas, spaces, or newlines - RevRing will automatically parse them. **Important**: Phone numbers must be assigned in both places: * In your Twilio Console (assigned to the trunk) * In RevRing (entered in the Phone Numbers field above) For **inbound calls** to a specific agent, you must also assign the phone number to that agent in the RevRing dashboard under **Agent → Phone Numbers**. For **outbound calls**, agents can use any phone number from their assigned SIP trunk. **Processing PHI?** If you're handling protected health information, you must: 1. Enable **Secure Trunking** (TLS/SRTP) in your Twilio trunk settings 2. Contact RevRing support to enable TLS and SRTP on your SIP connection ## Telnyx SIP Trunking Telnyx offers competitive pricing and global coverage with a developer-friendly API. ### Prerequisites * Active Telnyx account * At least one phone number purchased from Telnyx * SIP trunk created in Telnyx Portal ### Step 1: Configure Telnyx 1. Log into the [Telnyx Portal](https://portal.telnyx.com) 2. Navigate to **Voice** → **SIP Trunks** 3. Click **Create SIP Trunk** or select an existing trunk 4. Note your **SIP Trunk URI** (e.g., `sip.telnyx.com`) ### Step 2: Configure Outbound Settings 1. In your trunk settings, go to **Outbound** 2. Set **Tech Prefix**: Leave empty or as configured 3. Enable **ANI Override** if you need to set custom caller ID ### Step 3: Configure Inbound Settings 1. In your trunk settings, go to **Inbound** 2. Add a **Connection** pointing to RevRing's SIP URI: `sip:sip.revring.ai` 3. Set **Routing** to forward calls to the connection Optionally enable **PSTN Transfer** in your trunk settings if you want your agents to use the Transfer To Number tool to transfer calls to external phone numbers. ### Step 4: Assign Phone Numbers 1. Navigate to **Phone Numbers** in Telnyx Portal 2. Select the numbers you want to use 3. Set **Connection** to your SIP trunk 4. Save changes ### Step 5: Add Trunk to RevRing 1. Navigate to [SIP Trunks](https://dashboard.revring.ai/app/sip-trunks) 2. Click **Add SIP Trunk** 3. Fill in the configuration: * **Name**: `Telnyx` (or any descriptive name) * **Proxy**: `sip.telnyx.com` (or your custom domain) - enter just the domain without the `sip:` prefix * **Username**: Your Telnyx SIP username (if authentication is enabled) * **Password**: Your Telnyx SIP password (if authentication is enabled) * **Phone Numbers**: Enter your phone numbers in E.164 format 4. Click **Add Trunk** **Important**: Phone numbers must be assigned in both places: * In your Telnyx Portal (assigned to the trunk) * In RevRing (entered in the Phone Numbers field above) For **inbound calls** to a specific agent, you must also assign the phone number to that agent in the RevRing dashboard under **Agent → Phone Numbers**. For **outbound calls**, agents can use any phone number from their assigned SIP trunk. **Processing PHI?** If you're handling protected health information, you must: 1. Enable **Secure Trunking** (TLS/SRTP) in your Telnyx trunk settings 2. Contact RevRing support to enable TLS and SRTP on your SIP connection ## Managing SIP Trunks ### Editing Phone Numbers To add or remove phone numbers from an existing trunk: 1. Navigate to [SIP Trunks](https://dashboard.revring.ai/app/sip-trunks) 2. Click the menu (three dots) on the trunk row 3. Select **Edit Numbers** 4. Add or remove phone numbers as needed 5. Click **Save Numbers** Phone numbers must exist and be properly configured in your telephony provider before adding them to RevRing. Remember: To enable inbound calls for a specific agent, assign the phone number to that agent under **Agent → Phone Numbers** in the RevRing dashboard. ### Deleting a Trunk To delete a SIP trunk: 1. Navigate to [SIP Trunks](https://dashboard.revring.ai/app/sip-trunks) 2. Click the menu (three dots) on the trunk row 3. Select **Delete** 4. Confirm the deletion You cannot delete a SIP trunk that is assigned to active agents. Remove the trunk from all agents first. ## Phone Number Format All phone numbers in RevRing must use **E.164 format**: * Start with `+` * Include country code * Include area code and number * No spaces, dashes, or parentheses ### Valid Examples ``` +12025551234 (US number) +442071234567 (UK number) +61298765432 (Australia number) ``` ### Invalid Examples ``` (202) 555-1234 (contains parentheses and dashes) 2025551234 (missing + and country code) +1-202-555-1234 (contains dashes) ``` ## Troubleshooting * Verify the proxy address is correct and matches your provider's configuration (should be their Termination SIP URI without the `sip:` prefix) * Ensure phone numbers are assigned to the trunk in both your provider and RevRing * Check that your provider's Origination settings include RevRing's SIP URI: `sip:sip.revring.ai` * Confirm there are no firewall rules blocking SIP traffic * Verify the phone number is assigned to an agent in the Phone Numbers tab * Check that the agent status is set to Active * Ensure your provider is routing calls to RevRing's origination endpoint * Review call logs in your provider's dashboard for routing errors * Confirm your trunk has sufficient balance/credit with your provider * Verify the from number is assigned to your trunk * Check that the to number is in valid E.164 format * Review your provider's outbound calling restrictions and country permissions * If your trunk requires authentication, ensure username and password are correct * Verify your provider's authentication settings are configured correctly * Check your provider's authentication logs for specific error messages ## Best Practices * **Use descriptive names** for your trunks (e.g., "Twilio US Production", "Telnyx Canada") * **Keep phone numbers updated** in both your provider and RevRing when making changes * **Monitor trunk capacity** to ensure you have enough concurrent call capacity for your needs * **Set up redundancy** by configuring multiple trunks or providers for failover * **Review call logs** regularly to identify and resolve any routing issues ## Next Steps Configure agents to handle inbound calls Set up outbound calling through your agents # Test Suites Source: https://docs.revring.ai/platform/test-suites Automatically test your AI agents with scripted conversations and AI-powered scoring Test suites let you automatically validate your agents by running scripted test conversations and scoring the results. Define test cases with a script (what the tester should say) and a scoring rubric (what counts as a pass), then run them at scale to catch regressions and verify agent behavior. ## Overview A test suite consists of: * **Target agent** — the agent being tested * **Tester** — either the built-in **system tester** (simpler, requires no setup) or a **custom agent** you've created that role-plays as the caller * **Test cases** — individual scenarios, each with a script and scoring rubric * **Test runs** — executions of the full suite, producing pass/fail results for each case Each suite has a **test type** that controls how runs execute: | Type | What happens | Best for | | - | - | - | | **Chat** (default) | The tester and your agent hold a fast text conversation. Your agent uses its real prompt, knowledge bases, and tools — same brain, no audio. Runs finish in seconds. | Everyday iteration: build, test, fix, rerun | | **Voice** | A full simulated phone call between the tester and your agent. | Final validation of the live call experience before launch | | **SMS** | The tester texts your agent, and replies are produced by the same engine that answers real inbound texts: the agent's SMS prompt (or its main prompt if none is set), knowledge bases, and tools. | Validating your agent's texting behavior before pointing real traffic at it | | **Web chat** | The tester chats with your agent the way a website visitor would, using the agent's web chat prompt (or its main prompt if none is set), knowledge bases, and tools. | Validating the website chat experience, including page-aware behavior via a `page_context` variable | In chat tests, phone-only actions are simulated so the whole flow can be scored: when your agent transfers the caller, the transfer succeeds in the simulation, so rubrics can verify the agent transferred to the right number under the right conditions. A chat-type suite can still run a one-off voice test by passing `mode: "voice"` when starting a run. In voice tests, transfers are simulated the same way: the test call is isolated by design, so instead of dialing a live phone line the transfer completes in simulation and rubrics score the agent's transfer behavior normally. See [Simulating Transfer Outcomes](#simulating-transfer-outcomes) for testing the failure path. SMS and web chat tests exercise your agent's real messaging behavior end to end, with two safety guarantees: no actual message is ever sent to a phone number or visitor, and nothing is written to your Conversations. Custom tools do run for real, and their webhook calls are marked with channel `"test"` (plus the test call ID) so your integrations can tell test traffic apart. A test's conversation lives on the test attempt, exactly like every other test type. After each conversation, the transcript is evaluated against the scoring rubric to produce a pass, fail, or error result with reasoning. Test conversations run until they end naturally: the tester finishes its script, or your agent ends the call. There is no practical length limit — long intake cases with dozens of exchanges run to completion. (An extreme safeguard of several thousand messages exists purely to stop a misconfigured never-ending conversation, far beyond any realistic test.) Test runs are billed like regular usage at your listed per-minute rates, based on conversation length. Chat tests are typically much shorter — and therefore cheaper — than voice tests. ## Simulating Transfer Outcomes Transfers complete in simulation on every test call, in both chat and voice mode, so transfer cases score the agent's behavior rather than the test environment. By default the simulated transfer succeeds. To test how an agent handles a transfer that does not go through (for example a take-a-message flow), set the test variable `_sim_transfer` to `fail` on the case. In the dashboard this is the "Simulate a failed transfer in this test" checkbox on the test case. Via the API, set it in the case's `variables`: ```json theme={null} POST /v1/test-suites/{suiteId}/tests { "name": "Failed transfer takes a message", "script": "Ask to speak with a person. When offered a message instead, accept and provide your details.", "scoringRubric": "PASS only if the agent offers to take a message after the failed transfer and collects the caller's details.", "variables": { "_sim_transfer": "fail" } } ``` With the variable set, the simulated transfer fails and the agent follows its failure instructions, so rubrics can score that path. Real calls are never affected by this variable. ## Suggest a Fix When a test fails, you can ask for a suggested prompt change that would make it pass. The suggestion includes an explanation of the failure, a plain-language summary of what changed, the complete corrected prompt ready to save to the agent, and any problems found in the test's own pass criteria. In the dashboard, open a failed test result and choose **Suggest a fix**. Preparing a suggestion takes time, particularly for agents with long prompts, so over the API it works as a short-lived job: `POST /v1/test-suites/{id}/attempts/{attemptId}/suggest-fix?wait=0` returns immediately with `status: "running"`. `GET` the same URL every couple of seconds. `status` stays `running` while it is being prepared, then becomes `ready` with the suggestion in `result`. Save `result.suggestedPrompt` to the agent with the usual agent update call, then rerun the suite to confirm the fix. Because the request never waits for preparation, this pattern is unaffected by how long a suggestion takes. If you would rather not poll, omit `wait=0` and the request waits up to 105 seconds and returns the suggestion directly when it is ready in time; if your client gives up first, simply repeat the request and the finished suggestion comes back immediately. ### One Suggestion for a Whole Run When several tests fail in the same run, requesting a fix per attempt produces separate prompts that do not know about each other, and applying one can undo another. Instead, request one suggestion for the run: ``` POST /v1/test-suites/{id}/runs/{runId}/suggest-fix GET /v1/test-suites/{id}/runs/{runId}/suggest-fix ``` This analyzes every failed attempt in the run together and returns a single corrected prompt that addresses all of them at once, so failures that share a root cause get one coherent edit. The job, polling, and `wait=0` semantics are identical to the per-attempt endpoint, and the result has the same fields plus: * `result.perAttempt` — one entry per failed attempt (`attemptId`, `testCaseName`, `analysis`) explaining why that test failed and which part of the edit addresses it. * `result.skippedAttemptIds` — present only on very large runs: the run's oldest failures beyond the most recent 12, which were not analyzed. The run must be finished before requesting a suggestion, and it must contain at least one failed attempt. ### Knowing When a Suggestion Is Out of Date Every `ready` response includes a `stale` boolean next to `status` and `result`. It is `false` when the agent's prompt is still the one the suggestion was written against, and `true` if the prompt has changed since, in which case saving `result.suggestedPrompt` would overwrite those newer edits. When `stale` is `true`, request a fresh suggestion instead of applying the old one. This applies to both the per-attempt and the run-level endpoints. ## Creating a Test Suite ### From the Dashboard 1. Navigate to **Test Suites** in the sidebar 2. Click **New Test Suite** 3. Enter a **Name** 4. Select the **Target Agent** to test 5. Choose the **Test Type**: **Chat** (default) for fast text conversations, **Voice** for full simulated calls, or **SMS** / **Web chat** to test the agent's messaging channels 6. Choose a tester mode: * **Use system tester** — simpler, no setup required. RevRing's built-in tester follows each test script automatically. * **Use custom agent as tester** — select one of your own agents to play the caller role. Useful when you need the tester to have specific voice, language, or behavioral settings. 7. Set **Max Concurrency** to control how many test calls run in parallel (min 2, max 100, default 2). Applies to voice tests, where each test requires two concurrent calls — one outbound from the tester and one inbound to the target. 8. Click **Create** **Default Variables** can be added after creation by editing the test suite. Open the suite and click **Edit** to set variables that apply to all test calls. ### Via API ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Support Regression Tests", "targetAgentId": "agent_target_id", "testMode": "chat", "testerAgentId": "agent_tester_id", "maxConcurrency": 5, "defaultVariables": { "customer_name": "Test Customer", "account_id": "TEST-001" } }' ``` To list suites, optionally filtered to one agent (useful when older agents' suites would otherwise fall outside the first page): ```bash theme={null} curl "https://api.revring.ai/v1/test-suites?targetAgentId=agent_target_id&page=1&pageSize=25" \ -H "x-api-key: YOUR_API_KEY" ``` When starting a run you can override the suite's test type for that run only, for example a final voice validation on a chat suite: pass `{ "mode": "voice" }` to the run endpoint. The same override accepts `"sms"` and `"web_chat"`, so one suite of scenarios can be exercised across channels without duplicating test cases. SMS and web chat tests require the agent to have a usable text prompt: its channel-specific prompt if you have set one, otherwise its main prompt. Starting a run for an agent with neither returns a `channel_prompt_missing` error. ## Writing Test Cases Each test case has three key fields: | Field | Description | | - | - | | **Name** | A descriptive name for the test (e.g., "Handles refund request correctly") | | **Script** | Instructions for the tester agent — what to say, what scenario to simulate | | **Scoring Rubric** | Criteria for evaluating the target agent's performance — what constitutes a pass or fail | ### Script The script tells the tester agent how to behave during the call. Write it as natural language instructions: ``` You are calling a customer support line to request a refund for order #12345. 1. When the agent greets you, say you'd like a refund for your recent order. 2. Provide the order number #12345 when asked. 3. Say the reason is that the product arrived damaged. 4. If the agent offers a replacement instead, insist on a refund. 5. Accept the refund if offered and say thank you. ``` ### Scoring Rubric The scoring rubric defines what a successful interaction looks like. After the call, the transcript is evaluated against these criteria: ``` PASS if ALL of the following are true: - The agent acknowledged the refund request - The agent asked for the order number - The agent processed or offered to process the refund - The agent was polite and professional throughout FAIL if ANY of the following are true: - The agent refused the refund without explanation - The agent provided incorrect information - The agent was rude or dismissive - The agent failed to ask for the order number ``` ### Creating Test Cases **Dashboard:** Open your test suite, go to the **Configure Tests** tab, click **Add Test**, and fill in the name, script, and scoring rubric. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites/{suiteId}/tests \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Refund request - damaged item", "script": "You are calling to request a refund for order #12345 because the product arrived damaged...", "scoringRubric": "PASS if the agent acknowledges the request and processes the refund. FAIL if the agent refuses without reason.", "attemptsPerRun": 1 }' ``` The `attemptsPerRun` field (1–10, default 1) controls how many times each test case is executed per run. Use multiple attempts to test for consistency. ### AI-Generated Test Cases You can also generate test cases automatically using AI. Provide a prompt describing the scenarios you want to test, and RevRing generates test cases based on your target agent's configuration: **Dashboard:** Click **Generate Tests** in your test suite and enter a prompt describing the scenarios you want. RevRing generates test cases based on your target agent's configuration. You can refine the results with follow-up messages — the generator keeps the conversation context so you can ask for adjustments or additional scenarios. Each generated test can be individually **added** to your suite or **dismissed**. Review each one before adding to make sure the script and rubric match your expectations. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites/{suiteId}/generate-tests \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Generate test cases for edge cases like the caller being angry, asking to speak to a manager, or providing an invalid order number." }' ``` The response includes generated test cases with `name`, `script`, and `scoringRubric` fields that you can review, edit, and save. ### From a Real Call Turn any call into a test: open the call and select **Create test**, or call the API. The caller's side of the call becomes the test script (what they said, in order, with the turns that led to the problem) and what should have happened becomes the scoring rubric. If the call failed any of the agent's [success criteria](/platform/webhooks#post-call-evaluation), the test focuses on those; otherwise give a `focus`. The call's own variables are replayed. ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites/{suiteId}/tests/from-call \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "callId": "a1b2c3d4-...", "focus": "The agent should offer the earliest available slot." }' ``` Pass `"save": false` to get the drafted test back without saving it. A test built from a failed call is the fastest way to prove a fix: run it, apply the change (or [Suggest a fix](#suggest-a-fix)), and run it again. ### Fix Failing Calls When an agent's calls start failing a [success criterion](/platform/webhooks#post-call-evaluation), build a test suite from those calls in one step. On the dashboard Overview, pick the agent and select **Fix** next to the criterion, or: ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites/from-failing-calls \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "agentId": "cm...", "criterion": "booked_appointment", "days": 7, "limit": 10 }' ``` The most recent failing calls (up to `limit`, within `days`) each become a test in a new suite, and a run starts right away. Then use [Suggest a fix for the whole run](#one-suggestion-for-a-whole-run) to get one change that addresses every case, apply it, and run the suite again to confirm. ## Suggest a fix When a test attempt fails, open it and click **Suggest a fix**. RevRing reviews the conversation, the test script, the pass criteria, and your agent's prompt, then proposes a minimal prompt update with a clear explanation and a line-by-line view of exactly what would change. If the test's pass criteria themselves look unachievable (for example, requiring an outcome outside the agent's control), that is flagged too, so you can fix the test instead of the agent. **Apply and rerun** applies the update and starts a fresh run in one step — with chat tests, you'll see the result in seconds. ## Running Tests ### Starting a Test Run **Dashboard:** Go to the **Runs** tab and click **Run Tests**. Enter a run name (or accept the auto-generated timestamp name) and click **Start**. You can monitor progress in real time. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites/{suiteId}/runs \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "v2.1 regression check" }' ``` ### Test Run Lifecycle | Status | Description | | - | - | | `queued` | Run is queued and waiting to start | | `running` | Test calls are being placed and scored | | `completed` | All test cases have been evaluated | | `failed` | The run encountered an error | | `cancelled` | The run was manually cancelled | ### Monitoring Progress Each test run tracks summary statistics: * **Total Tests** — total number of test attempts * **Passed** — number of attempts that passed * **Failed** — number of attempts that failed **Dashboard:** The test run detail page shows real-time progress with results for each attempt. Click **View Call Details** on any attempt to see the full call record. **API:** ```bash theme={null} curl https://api.revring.ai/v1/test-suites/{suiteId}/runs/{runId} \ -H "x-api-key: YOUR_API_KEY" ``` ### Test Attempt Results Each test attempt produces: | Field | Description | | - | - | | `result` | `pass`, `fail`, or `error` | | `reasoning` | AI-generated explanation of why the test passed or failed | | `transcript` | The full conversation transcript | | `callId` | Link to the outbound call record | | `inboundCallId` | Link to the inbound call record (on the target agent) | ### Exporting Results After a run completes, you can export the results as JSON. Filter by **pass**, **fail**, or **error** before exporting to narrow down the data. Results can be copied to clipboard or downloaded as a file. ### Cancelling a Run **Dashboard:** Click **Stop** on a running test run. **API:** ```bash theme={null} curl -X POST https://api.revring.ai/v1/test-suites/{suiteId}/runs/{runId}/cancel \ -H "x-api-key: YOUR_API_KEY" ``` Only `queued` or `running` test runs can be cancelled. ## Test Variables Variables can be set at multiple levels. More specific values override broader ones: 1. **Default variables** on the test suite — apply to all test calls 2. **Per-test-case variables** — override suite defaults for a specific test 3. **Run variables** — passed when starting a run, override both for that run only During a test they are merged over the agent's own default variables, your pre-call webhook still runs as well (its payload includes `isTestCall: true`), and the prompt renders exactly as it would on a real call. Per-test-case variables are useful for testing different caller scenarios: ```json theme={null} { "name": "VIP customer refund", "script": "Call and ask for a refund...", "scoringRubric": "PASS if the agent offers expedited refund for VIP...", "variables": { "customer_name": "VIP Customer", "account_status": "premium" } } ``` Run variables let you supply values at the moment you start a run, without editing the suite or its tests — for example when the values come from your own application: ```bash theme={null} POST /v1/test-suites/{id}/runs { "variables": { "customer_name": "Jordan Alvarez", "claim_type": "auto" } } ``` ### Seeing What a Run Ran With Every attempt records the variables it ran with (the merged result of suite defaults, test case variables, and run variables) in its `variables` field, visible on the run's attempts via `GET /v1/test-suites/{id}/runs/{runId}`. The snapshot is taken when the run starts, so it stays inspectable even after the suite or its tests are edited. Attempts from before this field existed show `null`. ## Best Practices ### Writing Effective Scripts * Be specific about what the tester should say and do * Include numbered steps for multi-turn conversations * Specify how the tester should respond to common agent behaviors * Keep scripts focused on one scenario per test case ### Writing Effective Rubrics * Use clear PASS/FAIL criteria * Be specific about what constitutes success vs. failure * Include both positive (must happen) and negative (must not happen) criteria * Account for acceptable variations in agent responses ### Test Organization * Group related tests in the same suite (e.g., "Refund Scenarios", "Appointment Booking") * Use descriptive names so test results are easy to understand at a glance * Start with key happy-path scenarios, then add edge cases * Run tests after every agent prompt change to catch regressions ### Concurrency The `maxConcurrency` setting controls how many test calls run simultaneously (minimum 2, maximum 100). Higher concurrency completes runs faster but counts against your organization's call concurrency limit. Start with a low value (2–5) and increase as needed. ## Troubleshooting An `error` result means the test call itself failed (e.g., connection issue, agent not reachable). This is different from a `fail`, which means the call completed but didn't meet the rubric criteria. Check the call logs for the specific error message. Increase `maxConcurrency` on the test suite to run more calls in parallel. Note that concurrent test calls count against your organization's overall concurrency limit. Review the scoring rubric for ambiguous criteria. The AI evaluator takes the rubric literally — vague rubrics produce inconsistent results. Use multiple attempts per run (`attemptsPerRun`) to identify inconsistency. If using a custom tester agent, ensure its prompt instructs it to follow the test script closely. The script is injected into the tester agent's context, but a conflicting system prompt may override it. If you don't need special tester behavior, consider switching to the **system tester** instead. This means the tester and target agent are sharing the same SIP trunk phone numbers. Each test call requires two participants on separate lines — the tester calls the target. Add another phone number to your SIP trunk so the tester can call from a different number than the target receives on. ## Next Steps Learn about outbound calling and concurrency Manage test suites programmatically Build structured conversation flows to test Configure agent tools and capabilities # Agent Tools Source: https://docs.revring.ai/platform/tools Configure system and custom tools for your agents Tools enable your AI agents to take actions during phone calls beyond just conversation. RevRing provides built-in system tools and supports custom tools via webhooks. ## System Tools System tools are pre-built capabilities that agents can invoke during calls. These tools are configured in the **Tools** tab of your agent settings. **Flow agents**: End Call, Transfer, and Press Digits are handled by dedicated [flow nodes](/platform/conversation-flows) instead of system tools. These three tools only appear on the Tools tab for single-prompt agents. Voicemail Detection and custom tools are available for both modes. ### End Call The End Call tool allows agents to automatically terminate calls based on configurable rules. #### When to Use * Calls that have reached their natural conclusion * Customer explicitly ends the conversation * Maximum conversation time is reached * Call objective has been completed #### Configuration 1. Navigate to the **Tools** tab 2. Locate **End Call** under System Tools 3. Toggle the switch to enable 4. Click **Configure** to customize behavior 5. Set **End Call Rules Override** to define when the agent should end calls: ``` End the call when: - The customer says goodbye or indicates they're done - The appointment has been confirmed - The customer explicitly asks to end the call - You've provided all requested information and there are no further questions - The conversation has exceeded 5 minutes ``` 6. Click **Save** If you leave the rules override empty, RevRing uses sensible defaults. Custom rules give you more control over call termination behavior. #### How It Works When enabled, the agent has access to an `end_call` function. During the conversation, if the agent determines (based on the rules) that the call should end, it will: 1. Invoke the `end_call` function 2. Speak a brief closing message 3. Terminate the call gracefully You can see end call invocations in the call transcript labeled as "Tool: end\_call". ### Transfer The Transfer tool enables agents to transfer calls to other phone numbers or departments based on conditions you define. RevRing supports two transfer modes: **cold** and **warm**. #### When to Use * Customer requests to speak with a human representative * Issue requires specialized expertise beyond the agent's scope * Escalation to management or specific departments * Routing to on-call support staff #### Configuration 1. Navigate to the **Tools** tab 2. Locate **Transfer** under System Tools 3. Toggle the switch to enable 4. Click **Configure** to set up transfer routes 5. Select the **Default Transfer Mode** (applied to any route that doesn't set its own mode): * **Cold Transfer** — the agent transfers the call immediately and disconnects * **Warm Transfer** — the agent places the caller on hold, calls the recipient to brief them, then connects the parties 6. Add transfer routes: **Transfer Routes**: * **To number**: `+12025559999` (destination phone number in E.164 format) * **Extension** (optional): touch-tone digits dialed after the call connects, to reach an extension behind a phone menu or auto-attendant. Allowed characters: `0-9`, `*`, `#`, and `w` for a 0.5-second pause. Start with `wwww` (a 2-second pause) so the destination is ready for input, e.g. `wwww1234` — see [Reaching an extension](#warm-transfer) below. Sent on warm transfers only. * **Condition**: `Transfer to sales team for pricing questions or to close deals` * **Mode**: `Default`, `Cold`, or `Warm` — overrides the default transfer mode for this specific route Click **Add route** to add multiple destinations. **Transfer Description Override** (optional): Custom instructions for when the agent should offer a transfer. 7. Click **Save** You can configure multiple transfer routes for different scenarios. The agent will choose the appropriate route based on the conversation context and conditions you define. **Fallback chains**: Because each route can set its own mode, you can build a ring tree. For example, a warm transfer to a primary number, a warm transfer to a backup if the first isn't answered, and a final cold transfer as a last resort. When a warm transfer isn't answered, the agent continues to the next route based on your prompt and conditions. (Cold transfers hand off immediately and cannot fall back, so they belong last.) #### Cold Transfer In cold transfer mode, the agent invokes the `transfer_to_number` function and the call is immediately handed off to the destination number. RevRing's involvement ends — billing stops and recording stops once the transfer is initiated. The transfer appears in the call transcript as a `transfer_to_number` tool invocation. **Provider Setup Required**: Ensure your SIP trunk provider supports SIP REFER and has call transfers enabled. Some providers like Twilio require enabling **PSTN Transfer** in your trunk settings. See the [Telephony Setup](/platform/telephony) guide for details. #### Warm Transfer Warm transfer provides a more seamless handoff. When the agent decides to transfer: 1. The **caller is placed on hold** — they hear a hold message and optional hold music 2. The **agent calls the recipient** at the transfer destination number 3. The agent **briefs the recipient** with a summary of the conversation so far 4. The **caller is connected** to the recipient Warm transfer is configured with the following settings: | Setting | Description | | - | - | | **Hold Message** | Message spoken to the caller while on hold (e.g., "Please hold while I transfer you.") Max 500 characters. | | **Hold Music** | Enable or disable hold music while the caller waits | | **Summary Instructions** | Extra guidance for the AI when briefing the human agent. Max 2,000 characters. If omitted, a default summary is generated. | | **Intro Message** | Spoken after the human agent accepts, heard by both parties (e.g., "You're now connected. They're up to speed on your request.") Max 500 characters. | | **Continue Recording After Handoff** | Keep recording after the agent hands off, so the recording also includes the human agent and caller conversation. | | **Caller ID Shown to Recipient** | Which number the transfer recipient sees. `agent` (default) shows the agent's own number for the call. `original_caller` shows the customer's number, so the recipient sees who is on the line. | | **No Answer Timeout** | How long the destination may ring before the attempt is abandoned. 10 to 300 seconds, default 75. | | **Wait for a Person** | How long to wait after the destination answers for a person to actually come on the line. Covers queues and hold systems that pick up but never hand over. 15 to 600 seconds, default 300. | **Presenting the original caller's number**: a warm transfer places a new outbound call to the recipient over your own trunk, so `callerIdMode: "original_caller"` sends a caller ID your account does not own. Support for this is set by your telephony provider, not by RevRing. Many providers allow it, often as a per-trunk option named something like ANI override or caller ID passthrough, and some require it to be enabled before it takes effect. Others only permit caller IDs you own or have verified in advance. If the caller withheld their number, or it is not a usable phone number, the agent's number is presented instead. If your provider only permits owned or verified caller IDs, the provider refuses the dial and the transfer attempt fails, so confirm support with your provider before enabling this option on a live agent. A cold transfer is handed off by your provider rather than re-dialed, so the caller ID it presents is governed by your trunk's own transfer settings. **Reaching an extension**: If a route has an **Extension**, the agent dials it as touch tones the moment the recipient's line answers — sooner than any person could dial by hand. Many phone menus and auto-attendants are not ready for input in that first instant, and digits that arrive too early are silently ignored, leaving the call wherever the line answered (typically the main menu or front desk). To avoid this, begin the extension with `w` pauses. Each `w` waits 0.5 seconds before the digits are dialed, and we recommend starting with `wwww` (2 seconds), which works with nearly all phone systems: * `wwww1234` — wait 2 seconds, then dial `1234` * `wwww1ww8011` — pauses and digits can be mixed, e.g. to navigate a menu before an extension If the destination still doesn't pick up the digits, add more `w`s. Extensions are dialed on warm transfers only, because a cold transfer hands the call off immediately. #### Configuring via API Set `transferMode` (the default mode) and `warmTransferConfig` when creating or updating an agent. Each route may include an optional `mode` (`"cold"` or `"warm"`) that overrides the default for that route; omit it to inherit `transferMode`. The example below builds a warm primary, warm backup, then cold last-resort fallback chain: ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "transferEnabled": true, "transferMode": "warm", "transferRoutes": [ { "to_number": "+12025559999", "condition": "Primary: transfer to the support team", "mode": "warm", "extension": "123#" }, { "to_number": "+12025550000", "condition": "Backup: use if the primary line is not answered", "mode": "warm" }, { "to_number": "+12025551111", "condition": "Last resort: drop to the main line if no one is available", "mode": "cold" } ], "warmTransferConfig": { "holdMessage": "Please hold while I connect you with our support team.", "holdMusicEnabled": true, "summaryPrompt": "Focus on the customer's main concern and any account details mentioned.", "introMessage": "You're now connected. They're up to speed on your request.", "continueRecordingAfterTransfer": false, "dialTimeoutSeconds": 75, "waitForHumanSeconds": 300 } }' ``` **If nobody answers**: the agent lets the destination ring for `dialTimeoutSeconds` seconds (10 to 300, default 75), then tells the caller it could not connect and continues the conversation. The caller can ask to try again, or the conversation can fall through to another route. Raise the timeout for teams that take longer to pick up; lower it to fail over to a backup route faster. **If the line answers but nobody comes on**: some destinations pick up into a queue, a hold system, or an unattended handset. The agent waits `waitForHumanSeconds` (15 to 600, default 300) for a person to speak. If nobody does, it ends the attempt, releases that leg, tells the caller the destination answered but no one came on the line, and continues the conversation. Raise it for queues that routinely hold callers for several minutes. Both timeouts can also be set per route, which is useful when one agent transfers to both a fast internal desk and a slow queue line: ```json theme={null} "transferRoutes": [ { "to_number": "+12025550123", "condition": "Billing questions", "mode": "warm", "dialTimeoutSeconds": 30 }, { "to_number": "+12025550199", "condition": "Claims queue", "mode": "warm", "dialTimeoutSeconds": 120, "waitForHumanSeconds": 300 } ] ``` Warm transfer is also available as a node type in [Conversation Flows](/platform/conversation-flows), where you can configure hold and summary settings per-node. ### Voicemail Detection Voicemail Detection automatically identifies when an outbound call reaches an answering machine or voicemail system. #### When to Use * Outbound calling campaigns where live conversations are preferred * Appointment reminders or confirmations * Sales or follow-up calls * Any scenario where leaving a message is optional #### Configuration 1. Navigate to the **Tools** tab 2. Locate **Voicemail Detection** under System Tools 3. Toggle the switch to enable 4. Click **Configure** to set behavior 5. Choose **Voicemail Action**: * **Hangup**: Immediately end the call when voicemail is detected (saves costs) * **Leave Message**: Speak a pre-recorded message before hanging up 6. If you selected "Leave Message", configure **Voicemail Message**: ``` Hi, this is a message from Acme Corp. We tried reaching you regarding your appointment. Please call us back at 1-800-555-0123. Thank you. ``` 7. Click **Save** Voicemail detection is most effective for outbound calls. For inbound calls, it's typically not needed since customers are calling you. #### How It Works When an outbound call connects, RevRing analyzes the audio to detect voicemail systems. If detected: * **Hangup action**: Call ends immediately, marked as "voicemail" in logs * **Leave message action**: Agent speaks the configured message, then hangs up Detection accuracy is typically very high but not perfect. Review call logs to monitor detection performance. ### Press Digits (DTMF) The Press Digits tool allows the agent to send DTMF touch tones during a call. This is useful when navigating IVR (interactive voice response) systems — for example, when the agent needs to press digits to navigate a phone menu during a transferred call or an outbound call to a phone system. #### When to Use * Navigating automated phone menus (e.g., "Press 1 for sales, press 2 for support") * Entering account numbers or PINs via keypad * Interacting with external IVR systems during outbound calls #### Configuration 1. Navigate to the **Tools** tab 2. Locate **Press Digits** under System Tools 3. Toggle the switch to enable 4. Optionally set a **Custom Instructions Override** to customize when the agent uses this tool 5. Click **Save** #### Configuring via API ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "playKeypadTouchToneEnabled": true, "playKeypadTouchToneDescriptionOverride": "Use this to press digits when navigating phone menus." }' ``` [Conversation Flows](/platform/conversation-flows) can also press digits with the `press_digit` node, which navigates a phone menu on its own: it listens to the menu and decides which digit to press. ## Advanced Settings ### Block All Interruptions By default, callers can interrupt the agent while it's speaking. Enable **Block All Interruptions** to make the agent always finish speaking before listening for input. #### When to Use * Delivering important legal disclaimers or disclosures that must be heard in full * Reading back critical information (order numbers, confirmation codes) * Playing scripted messages that should not be cut short #### Configuration 1. Navigate to the **Advanced** tab 2. Toggle **Block All Interruptions** to on 3. Click **Save** #### Configuring via API ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "disableInterruptions": true }' ``` Block All Interruptions applies to all agent speech globally. In [Conversation Flows](/platform/conversation-flows), you can set `blockInterruptions` on individual nodes for more granular control. ### Ignore Brief Acknowledgments Callers naturally say things like "okay", "yeah", or "uh-huh" while listening. By default, these brief acknowledgments do not interrupt the agent, so it finishes its sentence smoothly instead of stopping mid-thought. Anything beyond a brief acknowledgment, such as "wait", "stop", a question, or "okay but wait", interrupts the agent immediately. When the agent is not speaking, acknowledgment words behave like any other reply. This is enabled by default with a recommended list that covers common acknowledgments and adapts to the agent's language setting (for example, Spanish agents also recognize "ajá" and "claro"). You can customize it per agent: * **Custom word list**: replace the recommended list with your own words or short phrases, for example to cover another language. * **Disable**: turn the toggle off (or set `interruptionIgnoreWords` to `[]` via API) so every caller utterance can interrupt. ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "interruptionIgnoreWords": ["okay", "yeah", "got it", "claro"] }' ``` ### Interruption Word Count Controls how many words a caller must say before the agent stops talking. The default of 1 keeps the agent responsive to any real interjection. Raise it to make the agent harder to interrupt, or set it to 0 to stop the agent on any caller sound, including background noise. Urgent phrases such as "stop", "wait", and "hold on" always interrupt the agent immediately, regardless of this setting. ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "minInterruptionWords": 2 }' ``` ### Interruption Threshold The **Interruption Threshold** controls how much speech is required from the caller before the agent treats it as an interruption and stops talking. This is measured in milliseconds. * **Lower values** (e.g. 100 ms) make the agent very sensitive — even a short utterance will interrupt it. * **Higher values** (e.g. 2000 ms) make the agent less sensitive — the caller must speak for longer before the agent pauses. * **Default**: 500 ms This is useful for tuning the conversational feel. If your agent is being interrupted too easily by background noise or brief acknowledgements (like "uh huh"), increase the threshold. If callers have difficulty getting a word in, lower it. #### Configuration 1. Navigate to the **Advanced** tab 2. Adjust the **Interruption Threshold** slider (100–2000 ms) 3. Click **Save** #### Configuring via API ```bash theme={null} curl -X PATCH https://api.revring.ai/v1/agents/{agentId} \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "interruptionThresholdMs": 750 }' ``` If **Block All Interruptions** is enabled, the interruption threshold has no effect since all interruptions are disabled. ## Live Appointment Booking with Cal.com RevRing hosts a Cal.com connector so agents can check real availability and book appointments live on a call, with no backend required. Two custom tools point at the hosted endpoints and carry your Cal.com API key in server side header configuration, where the model never sees it. Register these two tools on the agent (dashboard Custom Tools, or `POST /v1/agents/{agentId}/custom-tools`), replacing the key, event type id, and timezone: ```json theme={null} { "name": "Check Availability", "webhookUrl": "https://api.revring.ai/public/integrations/calcom/check-availability?eventTypeId=YOUR_EVENT_TYPE_ID&timeZone=America/New_York", "enabled": true, "headers": [{ "key": "x-cal-api-key", "value": "YOUR_CAL_API_KEY" }], "schema": { "type": "function", "name": "check_availability", "description": "Check the calendar for open appointment times. Use when the caller asks about available times or wants to schedule.", "parameters": { "type": "object", "properties": { "date": { "type": "string", "description": "Date to check availability (YYYY-MM-DD). Defaults to today." }, "days_ahead": { "type": "number", "description": "Days ahead to check. Default 2. Max 5." } } } } } ``` ```json theme={null} { "name": "Book Appointment", "webhookUrl": "https://api.revring.ai/public/integrations/calcom/book-appointment?eventTypeId=YOUR_EVENT_TYPE_ID&timeZone=America/New_York", "enabled": true, "headers": [{ "key": "x-cal-api-key", "value": "YOUR_CAL_API_KEY" }], "schema": { "type": "function", "name": "book_appointment", "description": "Book an appointment on the calendar. Only use after the caller has confirmed a specific date and time.", "parameters": { "type": "object", "required": ["date", "time"], "properties": { "date": { "type": "string", "description": "Appointment date YYYY-MM-DD." }, "time": { "type": "string", "description": "Time in HH:MM 24-hour format." }, "first_name": { "type": "string", "description": "Caller's first name." }, "last_name": { "type": "string", "description": "Caller's last name." }, "email": { "type": "string", "description": "Caller's email, required for the calendar invite." }, "phone": { "type": "string", "description": "Caller's phone number, if known." } } } } } ``` Then add booking behavior to the prompt: check availability before offering times, offer only times the tool returned, collect the email (and phone when the calendar requires it), book only after the caller confirms, and confirm only after the tool reports success. Have the agent speak a short bridge line ("Let me check what is open...") before each tool call so the lookup never sounds like dead air. The tools return short spoken ready text, including clear guidance on failures (slot taken, phone number required), so the agent recovers naturally. Phone numbers are normalized automatically, and both Cal.com response formats are handled. If you prefer to run the endpoints on your own backend for custom logic, point the same tools at your URLs instead and keep the key server side. ## Custom Tools Custom tools allow you to extend agent capabilities by integrating with your own systems via webhooks. When the agent invokes a custom tool, RevRing calls your webhook with the tool's arguments and includes the response in the conversation. ### Use Cases * **CRM lookups**: Fetch customer information during calls * **Order status**: Check order or shipment status * **Appointment booking**: Create or modify appointments in your calendar system * **Payment processing**: Initiate payments or check account balances * **Inventory checks**: Verify product availability * **Custom business logic**: Any operation specific to your workflow ### Creating a Custom Tool 1. Navigate to the **Tools** tab 2. Scroll to the **Custom Tools** section 3. Enter a **Friendly Name** for your tool (e.g., "Order Status Checker") 4. Enter your **Webhook URL** (e.g., `https://api.yourcompany.com/tools/check-order`) 5. Click **Add Tool** The tool will appear in the table below. Click **Edit** to define the tool's schema. The **Friendly Name** is just a label for the dashboard. The actual function name that the agent uses is defined in the `name` field of your JSON schema (e.g., `check_order_status`). ### Defining the Tool Schema The schema defines what parameters the tool accepts using OpenAI's function calling format: 1. Click **Edit** on your custom tool 2. In the **Schema JSON** field, enter your tool definition: ```json theme={null} { "type": "function", "name": "check_order_status", "description": "Check the status of a customer's order by order ID", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "The unique order ID (e.g., ORD-12345)" } }, "required": ["order_id"], "additionalProperties": false } } ``` 3. Toggle **Enabled** to activate the tool 4. Click **Save** The `description` fields are critical - they tell the agent when and how to use the tool. Be clear and specific. ### Schema Format Reference Follow the [OpenAI function calling format](https://platform.openai.com/docs/guides/function-calling#defining-functions): * **type**: Always `"function"` at the root level (required) * **name**: Unique identifier for the tool (lowercase, underscores allowed) (required) * **description**: When the agent should use this tool (required) * **parameters**: Object defining the tool's input parameters (required) * **type**: Always `"object"` for the parameters root (required) * **properties**: Each parameter with its type and description (use `{}` for tools with no inputs) * **required**: Array of required parameter names (optional) **For tools without inputs:** You must still include the `parameters` object with `type: "object"` and empty `properties`: ```json theme={null} { "type": "function", "name": "get_current_weather", "description": "Get the current weather", "parameters": { "type": "object", "properties": {} } } ``` ### Advanced Webhook Settings By default, RevRing sends a `POST` request with a JSON body to your webhook URL when a tool is invoked. You can customize this behavior per tool: | Setting | Description | Default | | - | - | - | | **HTTP Method** | The HTTP method used to call your webhook (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) | `POST` | | **Headers** | Custom HTTP headers to include in the request (e.g., `Authorization: Bearer token123`) | None | | **Query Parameters** | Custom query parameters appended to the webhook URL (e.g., `source=revring`) | None | | **Send Raw Request Body** | When enabled, sends the tool arguments directly as the request body instead of the default `{ callId, tool, arguments }` wrapper | Off | These settings are configured per-tool in the dashboard or via the API: ```bash theme={null} curl -X POST https://api.revring.ai/v1/agents/{agentId}/custom-tools \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Check Order", "webhookUrl": "https://api.yourcompany.com/tools/check-order", "httpMethod": "POST", "headers": [ { "key": "Authorization", "value": "Bearer your-token" } ], "queryParams": [ { "key": "source", "value": "revring" } ], "sendRawRequestBody": false, "enabled": true, "schema": { "type": "function", "name": "check_order_status", "description": "Check order status by order ID", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "The order ID" } }, "required": ["order_id"] } } }' ``` ### Webhook Request Format When the agent invokes your custom tool, RevRing sends a request to your webhook URL. **Default request body (when Send Raw Request Body is off):** ```json theme={null} { "callId": "dc582fd6-4e5c-4860-9ced-abc7390e62a9", "tool": "check_order_status", "arguments": { "order_id": "ORD-12345" } } ``` **Raw request body (when Send Raw Request Body is on):** ```json theme={null} { "order_id": "ORD-12345" } ``` Custom tools also run on text channels — [web widget chat](/platform/web-widget) and [SMS](/platform/two-way-messaging). Text invocations use the same request format, with `callId` set to `null` and two extra fields — `conversationId` and `channel` (`"web_chat"` or `"sms"`) — identifying the message thread instead of a call. **Response Examples:** You can return either JSON or plain text: ```json theme={null} { "status": "out_for_delivery", "expected_delivery": "2025-11-24T17:00:00Z", "tracking_number": "1Z999AA10123456784" } ``` Or: ``` Order ORD-12345 is currently out for delivery. Expected delivery today by 5 PM. ``` The agent will receive the response and incorporate it into the conversation naturally. ### Response Format Your webhook should return: * **200 OK** status code * JSON object or plain text response body * Response within 8 seconds (recommended: under 3 seconds for best user experience) **JSON Response Example:** ```json theme={null} { "appointments": [ { "date": "2025-11-25", "time": "14:00", "type": "checkup" } ], "total_count": 3 } ``` **Plain Text Response Example:** ``` Customer has 3 active appointments. Next appointment is tomorrow at 2 PM. ``` The agent will interpret the response and communicate the information to the caller naturally. ### Best Practices for Custom Tools #### Clear Descriptions The agent uses descriptions to decide when to invoke tools. Be specific: **Good:** ``` Check the status of a customer's order by order ID. Use this when the customer asks about their order, shipment, or delivery status. ``` **Bad:** ``` Check order ``` #### Error Handling Handle errors gracefully in your webhook. You can return structured error data or error messages: ```json theme={null} { "error": "order_not_found", "message": "Order number not found in system" } ``` Or plain text: ``` I couldn't find that order number in our system. Could you verify the order number? ``` The agent will communicate the error to the caller appropriately. #### Performance * Keep webhook response times under 3 seconds * Implement timeouts and retries in your webhook code * Cache frequently accessed data when possible * Use async processing for complex operations (return immediately with a status message) #### Security * Validate requests are coming from RevRing (contact support for HMAC signing setup) * Sanitize and validate all input parameters * Use HTTPS for all webhook URLs * Implement rate limiting to prevent abuse ### Testing Custom Tools 1. Create a simple test tool that returns static data 2. Use the **test panel** to make a call 3. During the call, prompt the agent to use the tool: * "Can you check order ORD-12345 for me?" 4. Review the transcript to see: * Tool invocation with arguments * Your webhook's response * How the agent incorporated the result into conversation ### Viewing Tool Invocations All tool invocations appear in call transcripts with detailed information: * **Tool name**: Which tool was invoked * **Arguments**: What parameters were passed * **Status**: Success (ok) or error * **Response**: What your webhook returned Navigate to [Call Logs](https://dashboard.revring.ai/app/calls), select a call, and review the transcript pane. ## Tool Limitations ### System Tool Constraints * **End Call**: Once invoked, the call ends immediately - the agent cannot continue conversation * **Transfer**: After transfer, the agent disconnects - the call continues with the transfer destination * **Voicemail Detection**: Only works on outbound calls, not inbound calls ### Custom Tool Constraints * **Response time**: 8 second timeout - webhooks must respond within this window * **Concurrent invocations**: Agents invoke tools sequentially, not in parallel ## Managing Tools ### Editing Tools To modify a custom tool: 1. Navigate to the **Tools** tab 2. Click **Edit** on the tool in the table 3. Update friendly name, webhook URL, schema, or enabled status 4. Click **Save** Changes take effect immediately for new calls. ### Deleting Tools To remove a custom tool: 1. Navigate to the **Tools** tab 2. Click **Delete** on the tool in the table 3. Confirm the deletion Deleting a tool that's actively referenced in your agent's behavior may cause unexpected results. Update your prompt to remove references to deleted tools. ### Disabling Tools Temporarily Instead of deleting, you can disable tools: 1. Click **Edit** on the tool 2. Toggle **Enabled** to off 3. Click **Save** The agent will not have access to disabled tools, but the configuration is preserved for future re-enabling. ## Advanced Tool Patterns ### Chaining Tools Agents can invoke multiple tools in sequence to accomplish complex tasks: 1. Customer asks: "What's my balance and when is my next payment due?" 2. Agent invokes `get_account_balance` → receives balance 3. Agent invokes `get_next_payment_date` → receives date 4. Agent combines both results in response Design tools to be focused and composable rather than monolithic. ### Conditional Tool Usage Guide the agent to use tools conditionally based on conversation context: ``` Use the transfer_to_billing tool only when: - Customer has billing questions you cannot answer - Customer disputes a charge - Customer wants to discuss payment plans Do NOT transfer for simple balance inquiries - use the check_balance tool instead. ``` ### Tool Results in Follow-Up Agents can reference previous tool results later in the conversation: > Agent: "I checked your order ORD-12345, and it's out for delivery today." > > Customer: "Great! And what about my other order?" > > Agent: "Let me check that for you..." *\[invokes tool again with new order ID]* ## Troubleshooting * Verify the tool is enabled (toggle is on) * Check that the description clearly indicates when to use the tool * Ensure your prompt doesn't contradict or override the tool's purpose * Test by explicitly asking the agent to use the tool during a test call * Review the schema - overly complex parameters may confuse the agent * Confirm the webhook URL is correct and publicly accessible * Check that your server is running and responding to POST requests * Test the endpoint independently with curl or Postman * Review your server logs for incoming requests * Verify webhook responds within 8 seconds * Ensure response is valid JSON or plain text * Check HTTP status code (should be 200) * Review your webhook logs for errors or exceptions * Test the webhook endpoint independently with sample data * Refine the tool's description to be more specific about usage * Update your prompt to provide clearer guidance on when to use tools * Adjust parameter descriptions to clarify expected formats * Consider splitting complex tools into simpler, focused tools * Verify the destination number is in valid E.164 format * Ensure your SIP trunk supports call transfers * Check that the destination number is reachable * Review call logs for specific transfer error messages ## Next Steps Learn about pre-call and post-call webhooks Manage custom tools programmatically via API Configure agents for handling inbound calls Send outbound calls with your agents # Two-way texting Source: https://docs.revring.ai/platform/two-way-messaging Let your agents hold text conversations and send SMS outside of a call Beyond texting during a call, your agents can hold full back-and-forth text conversations and send messages outside of a call, all from the same number they call from. There are two parts: * **Incoming texts:** when someone texts your number, your agent can reply automatically with AI, or RevRing can forward the message to your own system. * **Sending texts:** send a message from your own automations (for example, following up when a lead does not answer a call) using the [Send Message API](/api-reference/messages/send). ## Replying to incoming texts Each agent chooses what happens when a text comes in: RevRing writes a reply using the agent's SMS prompt and the recent conversation, then sends it back. Hands-off for you. RevRing forwards each incoming text to a URL you choose, so your own system can decide how to respond. With **Notify your webhook**, RevRing POSTs a `message_received` event to your URL. See [Inbound Message Webhook](/platform/webhooks#inbound-message-webhook) for the exact payload and a code sample. ### The SMS prompt For automatic replies, the agent uses its **SMS prompt** (set on the agent's **Messaging** tab) when you've added one, and otherwise falls back to the agent's main prompt — so auto-reply works out of the box. Add an SMS prompt only when you want texting to have its own tone, separate from calls. RevRing keeps the recent conversation with each contact, so replies stay in context. ### Knowledge bases, tools, and what texting knows Text replies use the same [knowledge bases](/platform/knowledge-bases) and [custom tools](/platform/tools) as the agent's calls — nothing separate to set up. Attach a knowledge base or add a tool to the agent, and texting picks it up automatically: * **Knowledge bases** — replies look up relevant information from the agent's attached knowledge bases, using the agent's existing retrieval settings. * **Custom tools** — when the conversation calls for it, the agent invokes its custom tools (booking, callbacks, live lookups against your systems) and uses the result in its reply. Tool webhooks receive the same request shape as on a call, with `callId` set to `null` and `conversationId` and `channel` (`"sms"`) identifying the thread instead. Call-only tools (phone transfer, keypad tones) stay call-only. * **Current date and time** — every reply knows the current date and time in the agent's timezone, so rules like opening hours apply on their own. `{{current_time}}` and the agent's default variables also render in the SMS prompt with `{{variable}}` templating, the same as on calls. This applies to automatic replies and to API sends with `generate: true` when an `agentId` is provided. ### Set up incoming texts Follow [Messaging](/platform/messaging) to connect a provider, then open the agent's **Messaging** tab and select the connection. On the **Messaging** tab, set **When a text comes in** to either reply automatically or notify your webhook. The SMS prompt is optional — auto-reply uses the agent's main prompt if you leave it blank. On the agent's **Numbers** tab, add the number that will receive texts. Incoming texts are routed to the agent that owns the number. In your provider, set the inbound message webhook for your Messaging Service to: ``` https://api.revring.ai/webhooks/twilio/inbound ``` To receive texts on a number, assign it to an agent, the same way you do to receive calls. That keeps calls and texts for one number flowing to the same agent. ## Sending texts from your own automations Use the [Send Message API](/api-reference/messages/send) to send an SMS outside of a call. You can either: * Send a **raw message** by passing `body`, or * Have RevRing **compose the message** with AI by passing `generate: true`. It uses the agent's SMS prompt and the ongoing conversation with that contact, or a history you pass inline with `messages` if you prefer to manage memory yourself. This fits reseller and CRM workflows well. For example, when a lead does not answer a call, your automation platform can trigger a follow-up text. Messages you send through the API join the same conversation as incoming texts for that contact. So if you send an opener and the contact replies, an automatic reply already has the context of what you sent. You bring your own provider account, so messages are sent from your numbers and billed to you by your provider, at your provider's rates. ## Managing conversations RevRing keeps a managed conversation for each contact, on each of your numbers. This is the memory behind automatic replies and AI-composed sends: the recent back-and-forth that keeps responses in context. * **Automatic idle reset.** After 30 days with no messages, a conversation starts fresh on its own, so a contact who returns much later isn't answered with stale context. This window is configurable per agent. * **Read the full thread.** Open any conversation on the **Conversations** page to see the whole back-and-forth. When the agent used a tool or its knowledge base to answer, that activity is shown right under the reply, so you can see what the agent did and where its answer came from. The same detail is available on the API with `GET /v1/conversations/{id}`, where each generated reply includes a `meta` object listing the tools it called and the knowledge base sources it cited. * **Clear a conversation.** See active threads on the **Conversations** page in the dashboard, or with the [Conversations API](/api-reference/conversations/list). Clearing a thread deletes it and its logged messages, so the very next text from that contact starts a brand-new conversation. Clearing is handy when you want to re-test an agent as if you were a new contact, or to forget a contact's history on request. ## Next steps Connect a provider and enable texting on a call Send and generate messages programmatically The same conversations as a chat bubble on your website # Versions and Publishing Source: https://docs.revring.ai/platform/versioning Every change to an agent is recorded, restorable, and attributable. Choose whether saves go live at once or wait for you to publish. ## Version history Every write to an agent creates a version automatically. There is no "save a version" step to remember: a prompt edit, a custom tool change, a phone number added or removed, a restore, all of them land in the history with: * **Who** made the change (the dashboard user's email, or the API key name) * **What** changed (the list of fields, with before/after values on the version detail) * **When** * A full snapshot of the configuration at that point The history survives agent deletion, so a deleted agent's final configuration is still readable. See the [List Agent Versions](/api-reference/agents/list-revisions) and [Get Agent Version](/api-reference/agents/get-revision) references. In the dashboard, open the agent and use the **History** tab. ## Restoring a version Restoring applies a version's configuration back onto the agent through the same validation as a normal save, and records the restore as its own version, so the state you had before stays in the history. Restore never changes phone numbers or knowledge base links (live routing and knowledge lifecycle are kept as they are), and references to a trunk, messaging connection or folder that no longer exist are skipped and reported. See [Restore Agent Version](/api-reference/agents/restore-revision). ## Publishing By default **every save goes live**: the next call uses whatever is saved. That is the right default for a single builder iterating quickly. When several people work on the same agent, or you want to prepare a change and release it deliberately, switch the agent to **manual publishing**. The agent then has two states: | State | What it is | Who sees it | | - | - | - | | Draft | The configuration as saved in the dashboard or by `PATCH /agents/{id}` | Editors, the dashboard test panel, chat and voice test runs | | Published | The version conversations run on | Callers and texters: outbound calls, inbound numbers, the web widget (calls and chat), SMS replies | While you edit, calls keep using the published version. When the draft is ready, **Publish** makes it live for new calls (calls already in progress are not changed). ### Switching modes ```json theme={null} PATCH /v1/agents/{id} { "publishMode": "manual" } ``` Switching to manual publishes the current draft at once, so the agent never silently runs an older state. Switching back to `auto` clears the published version and every save is live again. The agent carries three fields for this: * `publishMode`: `auto` or `manual` * `publishedRevision`: the version calls run on (manual mode); `null` when nothing is published yet, in which case the draft runs * `hasUnpublishedChanges`: manual mode only, `true` when the draft differs from the published version (on `GET /agents/{id}`; the dashboard shows it as a badge next to the Publish button) ### Publishing ```json theme={null} POST /v1/agents/{id}/publish {} ``` Publishes the current draft. The response carries the version calls now run on. Publishing is recorded in the history as a `published` version with the actor, so "who put this live?" is always answerable. Publishing a draft that is already live is a no-op. To make an earlier version live without touching the draft: ```json theme={null} POST /v1/agents/{id}/publish { "revision": 12 } ``` This is the fastest rollback: the draft keeps your in-progress work, and calls go back to version 12 immediately. See [Publish Agent](/api-reference/agents/publish). ### Testing before you publish The dashboard test panel (talk to the agent from the browser) and chat, text and voice test runs use the **draft** by default. That is the point of manual publishing: test the change, then publish it. To check what callers get today, start a run with `"environment": "production"` (or an environment name) on `POST /test-suites/{id}/runs`. Real calls, inbound numbers, the web widget and SMS replies use the published version (prompt, SMS and web-chat prompts, variables, custom tools, voice and conversation settings). ### Which version a call ran on Every call record and post-call webhook carries `agentRevision`, the version the call ran on (`null` for agents in auto mode). When a caller reports "the agent said X", that number tells you exactly which configuration was live, and the version detail shows the prompt it ran with. Phone numbers, the SIP trunk, knowledge base links and the agent's active/disabled status are infrastructure, not configuration: they are always live, whatever version is published. Adding a number to an agent in manual mode takes effect at once. ## Environments Environments let one agent serve more than one audience without duplicating it: for example `staging` pinned to a newer version with its own variable defaults, while `production` stays on the published version. * `production` always exists. It is the published version and changes through publishing. * A named environment pins a **version** (or the saved draft, with `revision: null`) and carries **variable defaults** that override the agent's on calls in that environment. Up to 10 per agent. * Names are lowercase slugs and cannot be renamed. ```json theme={null} PUT /v1/agents/{id}/environments/staging { "revision": 14, "variables": { "tone": "formal", "region": "EU" } } ``` ### What runs in an environment | Entry point | How to choose the environment | | - | - | | Outbound call | `environment` on `POST /calls` (omit for production) | | Inbound number | `environment` on the number (`POST /agents/{id}/phone-numbers` or `PATCH /agents/{id}/phone-numbers/{phoneId}`), or a `trafficSplit` across versions (see [Split calls between versions](#split-calls-between-versions)) | | Web widget (calls and chat) | `environment` in the agent's widget config | | SMS replies | Production | | Chat, text and voice tests | The draft by default; `environment` on the run (`production` or a name) to test a live version | Unknown names are refused on `POST /calls` with `environment_not_found` before anything is dialed. A number or widget bound to an environment that was later deleted falls back to production rather than failing the caller. Deleting an environment is refused while numbers are still bound to it. Every call record and post-call webhook carries `environment` (`null` for production) next to `agentRevision`. See [List Agent Environments](/api-reference/agents/list-environments), [Create or Update Environment](/api-reference/agents/upsert-environment) and [Delete Environment](/api-reference/agents/delete-environment). ### Split calls between versions Split an inbound number's calls between versions to see which one works better on real callers. In the dashboard, open the agent's **Numbers** tab and choose **Split between versions** for the number; the **History** tab compares the versions. Through the API, give the number a `trafficSplit` of 2 to 5 environments with weights; each call runs the version picked by weight (`null` is production): ```json theme={null} PATCH /v1/agents/{id}/phone-numbers/{phoneId} { "trafficSplit": [ { "environment": null, "weight": 80 }, { "environment": "candidate", "weight": 20 } ] } ``` Every call records the version it ran on (`environment` and `agentRevision`), so you can compare them: ```bash theme={null} curl "https://api.revring.ai/v1/analytics/versions?agentId=AGENT_ID&from=2026-10-01T00:00:00Z" \ -H "x-api-key: YOUR_API_KEY" ``` Each row is one version with its `calls`, `failureRate`, `avgDurationSeconds`, `negativeSentimentRate`, `evaluationPassRate` and per-criterion pass rates from your [success criteria](/platform/webhooks#post-call-evaluation). The period defaults to the last 7 days (up to 90). When you have a winner, publish it (or point production at it) and remove the split with `"trafficSplit": null`. The number's own `environment` is kept while a split is set, and an environment that a split uses cannot be deleted. A common setup: keep `production` on manual publishing, point `staging` at the draft (`revision: null`) and bind a spare inbound number to `staging`. Call that number to hear the draft on a real phone; publish when it sounds right. # Web Widget Source: https://docs.revring.ai/platform/web-widget Put your agent on any website as a chat bubble, with memory that follows the visitor Add your agent to any website as a floating chat bubble. Visitors chat with it right on the page, and the agent remembers each visitor across pages, reloads, and return visits — no account or login needed on your visitor's side. The widget works on any site, including traditional websites where every click is a full page reload: the conversation picks up exactly where it left off on the next page. Web chat runs on the same managed conversations as [two-way texting](/platform/two-way-messaging), so everything you know about conversations, memory, and the Messages API applies here too. Web chat messages appear in your Message Logs and Conversations pages with a **Web** badge. ## How it works 1. You enable the widget on an agent and list the websites it may run on. 2. You paste one line of code into the website. 3. Visitors click the bubble and chat. Replies come from your agent's prompt, knowledge bases, and tools, with the recent conversation kept in context per visitor. 4. Every conversation shows up in your dashboard under **Conversations**, and each message in **Message Logs**. ## Setup Open your agent, go to the **Widget** tab, and list the website domains the widget is allowed to run on (one per line, for example `www.example.com`). Click **Turn on the widget**. The tab now shows your embed code. Copy it. It looks like this: ```html theme={null} ``` Paste the snippet once, ideally right before the closing `` tag, on every page where the widget should appear. Most site builders and CMSs have a "custom code" or "scripts" area for this. That's the whole setup. The widget loads its look and behavior from your agent's settings, so changes you make in the dashboard apply to the live website automatically — no code changes needed. ## Make it yours Everything visual is configurable on the agent's **Widget** tab, with a live preview: * **Display name** and **avatar image** shown in the chat header * **Button label** on the floating bubble (for example "Chat with Ava") * **Welcome message** shown when a visitor opens the chat for the first time * **Accent color**, **position** (any corner), and **light/dark theme** * **Message placeholder** text in the input There is no RevRing branding on the widget. What your visitors see is your brand only. ## The web chat prompt Replies use the agent's **Web chat prompt** (set on the Widget tab) when you've added one, and otherwise fall back to the agent's main prompt — so the widget works out of the box on any agent. Add a web chat prompt when website chat should have its own tone or rules, separate from calls and texting. ## Knowledge bases and tools in chat Web chat uses the same [knowledge bases](/platform/knowledge-bases) and [custom tools](/platform/tools) as the agent's voice calls — there is nothing separate to set up. Attach a knowledge base or add a tool to the agent, and chat picks it up automatically. * **Knowledge bases** — chat looks up relevant information from the agent's attached knowledge bases when answering, using the agent's existing retrieval settings. Keep reference content (product details, policies, FAQs) in a knowledge base so it stays current without editing the prompt. * **Custom tools** — when the conversation calls for it, chat invokes the agent's custom tools (for example booking an appointment, requesting a callback, or looking up live data from your systems) and uses the result in its reply. The tool's webhook receives the same request shape as on a call, with `callId` set to `null` and `conversationId` and `channel` (`web_chat`) identifying the chat thread instead. * **Live data** — for values that change minute to minute (inventory, pricing, availability), a custom tool pointed at your own API is the best fit; the agent fetches fresh data at the moment it's asked. Knowledge bases are best for content that changes occasionally. Tools that only make sense on a phone call (transferring the call, touch-tone dialing) stay call-only. ## What chat knows automatically Each chat reply is generated with: * **The current date and time** in the agent's timezone, so rules like opening hours or seasonal schedules apply on their own. You can also place `{{current_time}}` anywhere in the prompt to control exactly where it appears. * **The page the visitor is on** (title and URL), so the agent can speak to what they're looking at — a specific product or listing, for example. Available in the prompt as `{{page_context}}` if you want to reference it explicitly. * **The agent's default variables**, which render into the prompt with `{{variable}}` templating, the same as on calls. ## Voice calls Let visitors talk to your agent by voice, right in the browser. Turn on **Voice calls** on the agent's **Widget** tab and a call button appears in the widget next to chat. * **Nothing to install for your visitors.** They click the call button, allow their microphone, and start talking. The call happens in the browser, so there is no phone number and no ringing. * **Same agent, one place to manage.** A voice call uses the same prompt, voice, and settings as the agent's phone calls. Pick a professional voice on the agent's **Voice** tab. * **Everything is recorded like a normal call.** Voice widget calls show up in your [Call Logs](https://dashboard.revring.ai/app/calls) with full transcript, recording, and webhooks, exactly like phone calls. They're marked as web calls (a `source` of `web_widget` in the API), so you can tell them apart from phone calls at a glance. * **Calls continue across pages.** If a visitor clicks to another page or reloads mid-call, the same call picks back up automatically in a few seconds, with the same conversation in progress. No redial, no starting over. If they're gone longer and the call has already wrapped up, coming back within a couple of minutes quietly starts a follow-up call where the agent picks up right where things left off (the two calls are linked in the API via `continuedFromCallId`). On by default; turn off **Resume calls across pages** on the Widget tab to make each page a fresh call. * **Chat and voice share one memory.** The agent treats a visitor's chat messages and voice calls as one ongoing relationship: on a call it already knows what was discussed in chat, and back in chat it remembers what was said on the call. Clearing the visitor's conversation clears all of it. On by default; turn off **Share memory between chat and voice** on the Widget tab to keep them separate. * **HTTPS required.** Browsers only allow microphone access on secure (https) pages, which any live website already uses. Voice is off by default. Text chat keeps working whether or not you enable voice. You can offer chat only, voice only, or both. ## Memory and conversations The widget remembers each visitor automatically. A visitor who browses five pages, leaves, and comes back tomorrow continues the same conversation with full context. Voice calls are part of that same memory: after a call, the chat knows what was said, and the next call knows what was typed. * Threads appear on the **Conversations** page (and the [Conversations API](/api-reference/conversations/list)) alongside your texting threads, marked **Web**. * After a period of inactivity the conversation starts fresh, using the same per-agent idle window as texting. See [Managing conversations](/platform/two-way-messaging#managing-conversations). * Clearing a thread from the dashboard makes that visitor start over as brand new. ## Staying in control * **Allowed websites.** The widget only runs on the domains you list. On any other site it stays hidden and prints a short explanation in the browser console for your web team. * **Sensible limits.** Conversations have per-minute and per-day message budgets so a stuck script or bad actor can't run up your usage. Visitors see a friendly "come back later" note if a limit is reached. * **No secrets in the page.** The embed key in your snippet is public by design and only works on your allowed domains. ## Troubleshooting ### The bubble doesn't appear **Solution:** 1. Open the browser console on the page. The widget logs one line explaining exactly why it did not start (domain not on the allowlist, widget turned off, or an invalid key) and what to change. 2. Confirm the widget is on: agent **Widget** tab, the switch at the top. 3. Confirm the page's domain is listed under **Allowed websites**. Subdomains count separately, so `www.example.com` and `example.com` are different entries. ### Replies stopped coming **Cause:** the conversation hit its per-minute or daily message budget, or the site is offline. **Solution:** wait a moment and try again. Budgets reset automatically. If it persists, check the conversation on the **Conversations** page. ### The website has a strict Content Security Policy **Solution:** allow `https://widget.revring.ai` in `script-src` and `https://api.revring.ai` in `connect-src`. ## Next steps The same conversations and memory, over SMS List and clear conversation memory programmatically Shape how your agent talks Connect conversations to your systems # Webhooks Source: https://docs.revring.ai/platform/webhooks Integrate RevRing with your systems using webhooks Webhooks allow RevRing to communicate with your systems before and after phone calls, enabling deep integration with your CRM, databases, and business logic. ## Webhook Types RevRing supports two types of webhooks: * **Pre-Call Webhooks**: Called before an inbound call is answered, allowing you to personalize the agent's behavior * **Post-Call Webhooks**: Called after any call ends, providing complete call details including transcript and recording ## Pre-Call Webhooks Pre-call webhooks enable dynamic customization of agent behavior based on caller information. Before answering an inbound call, RevRing sends caller details to your webhook and uses the response to personalize the conversation. ### When to Use * Look up caller information in your CRM * Customize greetings based on customer status or history * Route calls differently based on caller context * Apply business hours logic or special handling * Personalize the agent's knowledge with customer-specific data ### Configuration 1. Navigate to your agent's **Advanced** tab 2. Set the **Pre-call Webhook URL**: ``` https://api.yourcompany.com/webhooks/pre-call ``` 3. Click **Save** Pre-call webhooks only apply to inbound calls. Outbound calls receive variables directly via the API when the call is sent. ### Request Format When an inbound call arrives, RevRing sends a POST request to your webhook: ```json theme={null} { "event": "pre_call", "agentId": "agent_456def", "fromNumber": "+14155551234", "toNumber": "+12025559999", "dialedAgentId": null } ``` **Field Descriptions:** * `event`: Always `"pre_call"` for pre-call webhooks * `agentId`: The agent that will handle the call * `fromNumber`: Caller's phone number (E.164 format) * `toNumber`: The number that was dialed * `dialedAgentId`: The agent ID when the call was routed directly to an agent (null if routed via phone number) ### Response Format Your webhook must respond within 5 seconds (aim for well under one: the caller is waiting in silence) with a JSON object containing variables: ```json theme={null} { "variables": { "customer_name": "John Smith", "account_status": "premium", "last_order_date": "March 10, 2024", "outstanding_balance": "$0.00", "preferred_language": "English" } } ``` These variables will be available in the agent's prompt and first message using `{{variable_name}}` syntax. Variables you return can override system variables like `{{current_time}}` and `{{timezone}}` if needed. However, system variables (`{{direction}}`, `{{user_number}}`, `{{agent_number}}`, `{{current_time}}`, `{{timezone}}`, `{{language}}`) are automatically provided and don't need to be set via webhook. ### Route, personalize or reject the call The response can also decide how the call runs. Every field is optional: ```json theme={null} { "variables": { "customer_name": "John Smith" }, "agentId": "cm_spanish_support_agent", "environment": "staging", "overrides": { "firstMessage": "Hi John, welcome back.", "voiceId": "revring-maya", "promptAppend": "This caller has an open claim. Offer to check its status first." } } ``` | Field | Effect | | - | - | | `agentId` | Run the call on another agent in your organization (for example by language, region or customer tier) | | `environment` | Run the agent's version for this [environment](/platform/versioning) | | `overrides.firstMessage` | Opening line for this call | | `overrides.voiceId` | Voice for this call (an id from [List Voices](/api-reference/voices/list)) | | `overrides.language` | Language for this call (e.g. `es`); the agent's [language preset](/platform/multilingual#language-presets) for it supplies the voice and opening line unless you set those too | | `overrides.promptAppend` | Text added to the end of the prompt for this call | | `overrides.prompt` | Replace the prompt for this call | | `reject` | `true` or `{ "reason": "..." }`: the call is not answered by the agent; it is recorded as FAILED with the reason | If the agent or environment you name does not exist, the call runs on the dialed agent as usual, so a mistake in your webhook never drops a caller. Moving from another platform? A Retell inbound webhook answer (`{ "call_inbound": { "override_agent_id", "dynamic_variables" } }`) and an ElevenLabs conversation initiation answer (`dynamic_variables`, `conversation_config_override.agent.first_message` and `.prompt.prompt`) work as they are. A Retell agent id resolves to the agent you [imported](/platform/importing-agents) from it. ### Using Variables in Agent Configuration Reference webhook variables in your **First Message**: ``` Hello {{customer_name}}, thank you for calling. I see you're a {{account_status}} member. How can I help you today? ``` And in your **Prompt**: ``` You are a customer service agent. You're speaking with {{customer_name}}, who is a {{account_status}} customer. Their account information: - Last order: {{last_order_date}} - Balance: {{outstanding_balance}} - Preferred language: {{preferred_language}} Use this context to provide personalized assistance. ``` ### Error Handling If your webhook fails, times out, or returns invalid JSON: * RevRing will proceed with the call using default variables (if configured) * The call will not be rejected - the agent will answer without personalization * Check the call's `preCallWebhookStatus` field to see webhook delivery status **Webhook Status Values:** * `sent`: Webhook responded successfully * `failed`: Webhook returned an error or timed out * `skipped`: No webhook URL configured * `null`: Call has not yet reached the webhook stage ### Example Implementation **Node.js / Express:** ```javascript theme={null} app.post('/webhooks/pre-call', async (req, res) => { const { fromNumber, agentId } = req.body; try { // Look up customer in your CRM const customer = await crm.findByPhone(fromNumber); if (customer) { res.json({ variables: { customer_name: customer.name, account_status: customer.tier, last_order_date: customer.lastOrderDate, outstanding_balance: customer.balance, vip: customer.isVIP ? "yes" : "no" } }); } else { // Unknown caller res.json({ variables: { customer_name: "valued customer", account_status: "new", vip: "no" } }); } } catch (error) { console.error('Pre-call webhook error:', error); // Return minimal data rather than erroring res.json({ variables: { customer_name: "caller" } }); } }); ``` **Python / Flask:** ```python theme={null} @app.route('/webhooks/pre-call', methods=['POST']) def pre_call_webhook(): data = request.json from_number = data.get('fromNumber') try: # Look up customer customer = crm.find_by_phone(from_number) if customer: return jsonify({ 'variables': { 'customer_name': customer.name, 'account_status': customer.tier, 'last_order_date': customer.last_order_date, 'outstanding_balance': f"${customer.balance:.2f}" } }) else: return jsonify({ 'variables': { 'customer_name': 'valued customer', 'account_status': 'new' } }) except Exception as e: logger.error(f'Pre-call webhook error: {e}') return jsonify({'variables': {'customer_name': 'caller'}}), 200 ``` ## Post-Call Webhooks Post-call webhooks deliver complete call details immediately after a call ends, including the transcript, recording URL, summary, and metadata. ### When to Use * Update your CRM with call outcomes * Log call details for compliance or auditing * Trigger follow-up workflows (emails, tasks, tickets) * Track agent performance metrics * Store transcripts and recordings in your system * Implement custom analytics or reporting ### Configuration 1. Navigate to your agent's **Advanced** tab 2. Set the **Post-call Webhook URL**: ``` https://api.yourcompany.com/webhooks/post-call ``` 3. Click **Save** Post-call webhooks fire for both inbound and outbound calls. ### Request Format After a call ends, RevRing sends a POST request with complete call details: ```json theme={null} { "id": "a3c18021-192f-565e-87b5-5af428f29e81", "organizationId": "org_abc123", "agentId": "agent_456def", "agentName": "Customer Support", "sipTrunkId": "trunk_789ghi", "direction": "INBOUND", "status": "COMPLETED", "fromNumber": "+14155551234", "toNumber": "+12025559999", "agentCallerIdName": "Acme Support", "recordingUrl": "https://secure.revring.ai/recordings/a3c18021-192f-565e-87b5-5af428f29e81.ogg", "hangupCause": "END_CALL_TOOL", "errorMessage": null, "variables": { "customer_name": "John Smith", "account_status": "premium", "direction": "INBOUND", "current_time": "2:30 PM", "timezone": "America/New_York" }, "transcript": [ { "at": 0, "seq": 0, "role": "assistant", "text": "Hello, how can I help you today?", "type": "message", "turnId": "item_1a1f40b6737f", "isFinal": true }, { "at": 4.588, "seq": 1, "role": "user", "text": "I have a question about my order.", "type": "message", "turnId": "user_71be2a_079a32", "isFinal": true, "language": "en-US" }, { "at": 18.242, "startedAt": 16.104, "durationMs": 328, "seq": 2, "role": "tool", "tool": { "args": "{\"order_id\": \"ORD-12345\"}", "name": "check_order_status" }, "type": "tool_call", "result": { "output": "{\"status\": \"shipped\", \"tracking\": \"1Z999AA10123456784\"}" }, "turnId": "tool_10cde5_59dcaf" }, { "at": 20.5, "seq": 3, "role": "assistant", "text": "Your order has been shipped. The tracking number is 1Z999AA10123456784.", "type": "message", "turnId": "item_2aba75c0b341", "isFinal": true }, { "at": 28.5, "seq": 4, "role": "user", "text": "Thanks, that's all I needed.", "type": "message", "turnId": "user_728ab3_4f2a1c", "isFinal": true, "language": "en-US" }, { "at": 30.2, "seq": 5, "role": "tool", "tool": { "args": "{}", "name": "end_call" }, "type": "tool_call", "result": { "output": "Ending call." }, "turnId": "tool_7281cd_8717e4" } ], "summary": "Customer inquired about their order status. Agent checked the order and confirmed it has been shipped with tracking number provided.", "extraction": { "order_number": "A1234", "callback_requested": false, "sentiment": "positive" }, "evaluation": { "order_status_given": { "result": "pass", "rationale": "The agent confirmed the order shipped and gave the tracking number." } }, "sentiment": "positive", "metrics": { "llm_prompt_tokens": 7585, "stt_audio_duration": 58.65, "tts_audio_duration": 27.14, "tts_characters_count": 491, "llm_completion_tokens": 114, "llm_input_audio_tokens": 0, "llm_output_audio_tokens": 0, "llm_prompt_cached_tokens": 819, "latency": { "turns": 9, "e2e": { "count": 9, "p50_ms": 1180, "p90_ms": 1610, "max_ms": 2240 }, "eou_delay": { "count": 9, "p50_ms": 420, "p90_ms": 610, "max_ms": 880 }, "transcription_delay": { "count": 9, "p50_ms": 180, "p90_ms": 260, "max_ms": 330 }, "llm_ttft": { "count": 10, "p50_ms": 520, "p90_ms": 790, "max_ms": 1020 }, "tts_ttfb": { "count": 10, "p50_ms": 240, "p90_ms": 300, "max_ms": 410 } } }, "initiatedAt": "2024-03-15T14:30:00.076Z", "startedAt": "2024-03-15T14:30:02.529Z", "endedAt": "2024-03-15T14:32:00.876Z", "durationSeconds": 118, "createdAt": "2024-03-15T14:29:58.000Z", "updatedAt": "2024-03-15T14:32:01.000Z" } ``` **Key Fields:** * `id`: Unique call identifier * `organizationId`: Your organization ID * `agentId`: The agent that handled the call * `agentName`: The agent's display name * `sipTrunkId`: SIP trunk used for the call (null if not applicable) * `direction`: `INBOUND` or `OUTBOUND` * `status`: Final call status (`COMPLETED`, `TRANSFERRED`, `FAILED`, `CANCELED`) * `fromNumber` / `toNumber`: Caller and recipient numbers (E.164) * `agentCallerIdName`: Caller ID name configured on the agent (null if not set) * `variables`: All template variables used during the call — includes variables from pre-call webhooks, API call variables, and system variables (`direction`, `current_time`, `timezone`, etc.) * `recordingUrl`: URL to download the call recording (null if recording disabled or call not answered) * `hangupCause`: Reason call ended. Common values include `USER_ENDED`, `END_CALL_TOOL`, `SILENCE_TIMEOUT`, `MAX_DURATION`, `VOICEMAIL_DETECTED` * `transcript`: Complete conversation with timestamps and tool invocations (both custom tools and system tools like `end_call`, `transfer_to_number`) * `summary`: AI-generated summary of the call * `extraction`: Structured fields pulled from the transcript, as configured in the agent's `postCallExtraction` (see [Post-call data extraction](#post-call-data-extraction)); `null` when not configured * `evaluation`: Pass/fail/unknown verdicts for the agent's `postCallEvaluation` criteria, each with a rationale (see [Post-call evaluation](#post-call-evaluation)); `null` when not configured * `campaignId`: The [campaign](/platform/campaigns) that placed the call; `null` for calls not placed by a campaign * `metrics`: Token usage, audio durations and response latency (see [Response latency](#response-latency)) * `durationSeconds`: Total call duration (null if never answered) The example above shows both a custom tool (`check_order_status`) and a system tool (`end_call`). When a call is transferred using `transfer_to_number`, the transcript will contain a similar tool entry with `name: "transfer_to_number"` and `args` containing `to_number`. ### Response latency `metrics.latency` shows how quickly the agent answered on this call, measured per agent turn and summarized as `count`, `p50_ms`, `p90_ms` and `max_ms` (milliseconds): | Field | Measures | | - | - | | `e2e` | What the caller waits: from the end of their speech to the start of the agent's reply (the sum of the three below) | | `eou_delay` | From the end of the caller's speech until the agent decides they have finished their turn | | `transcription_delay` | From the end of the caller's speech until their words are transcribed | | `llm_ttft` | From the agent starting to think to the first word of its reply | | `tts_ttfb` | From the reply text to the first audio the caller hears | | `turns` | Number of agent turns measured | Interrupted or discarded replies are not counted. Calls where the caller never spoke have `count: 0`. **Tool timing fields** Custom tool entries carry how long your webhook took to respond, so you can measure tool latency yourself from the transcript, with no need to ask support: | Field | Meaning | | - | - | | `startedAt` | Seconds from the start of the call to the moment the tool began running | | `durationMs` | Milliseconds the tool took to return, measured server side. Present on both successful and failed executions | | `at` | When the entry was written to the transcript. Tool entries are written once the turn finishes, so `at` can be later than `startedAt`. Use `startedAt` and `durationMs` for timing questions | The same timing is shown on each tool card in the call transcript in the dashboard. For complete field descriptions and additional examples, see the [Post-Call Webhook API Reference](/api-reference/webhooks/post-call). ### Post-call data extraction Define the fields you want pulled out of every conversation and the platform fills them in from the transcript after each call. The result lands on the call record as `extraction` (`GET /calls/{id}`) and in the post-call webhook, so your systems get typed values instead of parsing the summary. Configure it on the agent with `postCallExtraction`: ```json theme={null} PATCH /v1/agents/{id} { "postCallExtraction": { "fields": [ { "name": "callback_requested", "type": "boolean", "description": "Did the caller ask to be called back?" }, { "name": "preferred_day", "type": "enum", "values": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "description": "Day of the week the caller prefers for the callback" }, { "name": "budget", "type": "number", "description": "Budget the caller mentioned, in dollars" }, { "name": "notes", "type": "string", "description": "Anything else the caller asked for" } ] } } ``` Each field has: | Property | Meaning | | - | - | | `name` | Key in the result. Letters, digits and underscores, starting with a letter. Unique per agent | | `type` | `string`, `number`, `boolean` or `enum` | | `values` | The allowed values, for `enum` fields only | | `description` | What to extract, written the way you would brief a person. The clearer it is, the better the result | How it behaves: * Up to 20 fields per agent. Set `postCallExtraction` to `null` to turn it off. * Values come only from what was said on the call. A field that was not stated, or was unclear, is `null`, never guessed. * Every configured field is present in the result, so your handler can rely on the keys. * Extraction runs after the call ends, alongside the summary, and is skipped for calls with no caller speech (unanswered dials, dead lines). Those calls carry `extraction: null`. * The result is included in the same post-call webhook delivery. No extra request to make. A matching result for the config above: ```json theme={null} "extraction": { "callback_requested": true, "preferred_day": "Tuesday", "budget": 450000, "notes": null } ``` Keep descriptions specific. "The caller's budget in dollars" extracts better than "budget", and enum fields with a short list of values give you clean data for routing and reporting. ### Post-call evaluation Write the outcomes you care about in plain language and every call is judged against them after it ends. Each criterion comes back as `pass`, `fail` or `unknown` with a one-sentence rationale, on the call record as `evaluation` (`GET /calls/{id}`) and in the post-call webhook. Use it for success rates, QA sampling and routing follow-ups. Configure it on the agent with `postCallEvaluation`: ```json theme={null} PATCH /v1/agents/{id} { "postCallEvaluation": { "criteria": [ { "name": "appointment_confirmed", "description": "The caller confirmed an appointment date and time" }, { "name": "objection_handled", "description": "If the caller raised a concern about price, the agent addressed it before moving on" }, { "name": "callback_requested", "description": "The caller asked to be called back" } ] } } ``` How it behaves: * Up to 10 criteria per agent. Set `postCallEvaluation` to `null` to turn it off. * A criterion that asks the agent to do something is `fail` when the call reached that point and the agent did not do it: a skipped required action counts against the agent. * A criterion about an outcome that depends on the caller (they book, buy, pay or share information) is `fail` when the call got to that topic and it did not happen, and `unknown` when the call never got there. * `unknown` also covers calls that ended before the agent could respond, so your success rate reflects what actually happened rather than guesses. * Every criterion is present in the result, so your handler can rely on the keys. * Runs after the call ends, alongside the summary and extraction, and is skipped for calls with no caller speech. Those calls carry `evaluation: null`. * Included in the same post-call webhook delivery. A matching result: ```json theme={null} "evaluation": { "appointment_confirmed": { "result": "pass", "rationale": "The caller agreed to Tuesday at 10am and the agent confirmed it." }, "objection_handled": { "result": "unknown", "rationale": "The caller never raised a concern about price." }, "callback_requested": { "result": "fail", "rationale": "The caller said not to call again." } } ``` Pass rates per criterion over any date range are in the [analytics overview](/api-reference/analytics/overview) (`evaluation`) and on the dashboard's Analytics page, so success rates need no spreadsheet. State each criterion as a single observable outcome. "The caller confirmed an appointment date and time" judges cleanly; "the call went well" does not. Conditional criteria work too: "If the caller asked about pricing, the agent quoted the monthly price". ### Caller sentiment Every call with caller speech gets a `sentiment`: how the caller felt over the call as a whole, `positive`, `neutral`, `negative` or `unknown` (the caller barely spoke or the call ended too soon to tell). It is on the call record (`GET /calls/{id}`), in the post-call webhook, and in the call-history export. Calls with no caller speech carry `sentiment: null`. With the Retell compatibility format, it fills `call_analysis.user_sentiment` (an imported `user_sentiment` extraction field takes precedence). ### Re-run analysis on past calls Changed an agent's extraction fields or success criteria? Re-run extraction, evaluation and sentiment on past calls with the agent's current settings, without placing new calls: ```bash theme={null} # One call (optionally deliver the post-call webhook again with the new results) curl -X POST https://api.revring.ai/v1/calls/{id}/reanalyze \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "resendWebhook": false }' # Up to 50 of an agent's calls in a time window, newest first curl -X POST https://api.revring.ai/v1/calls/reanalyze \ -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{ "agentId": "cm...", "from": "2026-10-01T00:00:00Z", "limit": 50 }' ``` In the dashboard, open a call and select **Re-run analysis**. A field or criterion that was removed from the agent is cleared on the call. Calls still in progress, calls with no caller speech, and calls whose content was deleted under the agent's [data controls](/platform/data-controls) are skipped. ### Export call history `GET /v1/calls/export` returns a CSV of calls, newest first (up to 10,000 per file), filtered by `agentId`, `status`, `direction`, `from` and `to`. Columns: call details, `sentiment`, `summary`, `recordingUrl`, one `var.*` column per call variable, one `extraction.*` column per extracted field, and one `evaluation.*` column per success criterion (its result). In the dashboard, use **Export CSV** on Call Logs. Cells that would run as a spreadsheet formula are prefixed with an apostrophe. ### Call started event To also hear when a call connects, set the agent's `webhookEvents` to `["call.started"]`. The post-call webhook URL then receives, once per answered call: ```json theme={null} { "event": "call.started", "call": { "id": "a1b2c3d4-...", "agentId": "cm...", "agentName": "Front Desk", "direction": "OUTBOUND", "fromNumber": "+14155550100", "toNumber": "+14155550123", "variables": { "first_name": "Ana" }, "campaignId": null, "startedAt": "2026-10-05T22:03:56.005Z" } } ``` The post-call payload itself has no `event` field, so a handler can tell the two apart. With the Retell compatibility format the event is Retell's `call_started`. Started events are sent once and are not retried. ### Transfer events To hear about transfers as they happen, add any of `transfer.started`, `transfer.bridged` and `transfer.cancelled` to the agent's `webhookEvents` (or turn on **Also send transfer events** in the agent's **Advanced** tab). The post-call webhook URL then receives: | Event | When | | - | - | | `transfer.started` | The agent starts a transfer. `transfer.type` is `warm` (the agent briefs the person first) or `cold` (the caller is handed straight over). | | `transfer.bridged` | A warm transfer connected: the person accepted and the caller is now talking to them. | | `transfer.cancelled` | A warm transfer did not go through. `transfer.reason` is `declined`, `voicemail` or `failed`; the agent stays with the caller. | ```json theme={null} { "event": "transfer.started", "at": "2026-10-06T14:02:11.418Z", "call": { "id": "c1f3...", "organizationId": "cm...", "agentId": "cm...", "agentName": "Front Desk", "direction": "INBOUND", "fromNumber": "+14155550123", "toNumber": "+14155550100", "campaignId": null, "startedAt": "2026-10-06T14:00:52.003Z" }, "transfer": { "type": "warm", "to": "+14155550199" } } ``` With the Retell compatibility format the events are Retell's `transfer_started`, `transfer_bridged` and `transfer_cancelled`. Transfer events are sent once and are not retried; the post-call webhook still arrives when the call ends. ### Compatibility formats Moving from another voice platform? Set the agent's `postCallWebhookFormat` and the post-call webhook is sent in that platform's payload shape, so the handler you already run keeps working while you switch. Agents created by the [importer](/platform/importing-agents) get this set for you. | `postCallWebhookFormat` | What your endpoint receives | | - | - | | `revring` (default) | The RevRing payload above | | `retell` | Retell AI's `call_ended` event, then `call_analyzed` with `call_analysis` (`call_summary`, `in_voicemail`, `user_sentiment`, `call_successful`, `custom_analysis_data`). Timestamps and durations in milliseconds, `transcript`, `transcript_object` and `transcript_with_tool_calls`, `disconnection_reason` | | `elevenlabs` | ElevenLabs Agents' `post_call_transcription` (`data.transcript`, `data.metadata` in seconds, `data.analysis` with `evaluation_criteria_results`, `data_collection_results`, `call_successful` and `transcript_summary`), or `call_initiation_failure` for an outbound call that never connected | The values come from the call record: extraction fields become Retell's `custom_analysis_data` or ElevenLabs' `data_collection_results`, evaluation criteria become `call_successful` (and ElevenLabs' `evaluation_criteria_results`), and an extraction field named `user_sentiment` fills Retell's `user_sentiment`. Fields that only exist on the other platform, such as costs or per-word timings, are not sent. Deliveries are signed with RevRing's `X-RevRing-Signature` header, not the other platform's signature, so replace your signature check (see [Verify webhook sources](#verify-webhook-sources)). Retries work as described below. ### Response Expectations Your webhook should: * Respond with `200 OK` status code * Respond within 30 seconds (a few seconds is better: acknowledge first, then process) * Handle the webhook asynchronously if processing takes time RevRing does not require any specific response body. Simply acknowledge receipt: ```json theme={null} { "received": true } ``` **Retries:** a delivery that fails (connection error, timeout, or any non-2xx response) is retried three times, 1 minute, 5 minutes and 30 minutes later. Make your handler idempotent on the call `id`: a retry carries the same payload, and a slow handler that returned after our 30 second timeout may be retried even though it processed the call. The call record shows the outcome in `postCallWebhookStatus` (`sent`, `retrying`, `failed`) with `postCallWebhookAttempts`. ### Error Handling If your webhook fails (non-2xx status), can't be reached, or times out: * The delivery is retried automatically 1, 5 and 30 minutes later (see Retries above); `postCallWebhookStatus` is `retrying` meanwhile * After the last retry it is marked `failed` in the call record * To backfill anything that ended `failed` (for example after a long outage on your side), list recent calls with `GET /calls` and check `postCallWebhookStatus` **Webhook Status Values:** * `sent`: Delivered successfully * `failed`: Webhook returned an error or timed out * `skipped`: No webhook URL configured * `null`: Call has not yet reached the webhook stage ### Example Implementation **Node.js / Express:** ```javascript theme={null} app.post('/webhooks/post-call', async (req, res) => { const call = req.body; // Acknowledge receipt immediately res.status(200).json({ received: true }); // Process asynchronously try { // Update CRM with call details await crm.logCall({ callId: call.id, phone: call.fromNumber, duration: call.durationSeconds, summary: call.summary, recording: call.recordingUrl, timestamp: call.endedAt }); // Create follow-up task if needed if (call.summary.includes('callback')) { await tasks.create({ title: `Follow up from call`, dueDate: tomorrow(), notes: call.summary }); } // Store transcript for compliance await db.transcripts.insert({ callId: call.id, transcript: call.transcript, recordingUrl: call.recordingUrl }); console.log(`Processed call ${call.id}`); } catch (error) { console.error('Post-call processing error:', error); // Don't throw - webhook already acknowledged } }); ``` **Python / Flask:** ```python theme={null} @app.route('/webhooks/post-call', methods=['POST']) def post_call_webhook(): call = request.json # Acknowledge immediately response = jsonify({'received': True}) # Process in background Thread(target=process_call, args=(call,)).start() return response, 200 def process_call(call): try: # Update CRM crm.log_call( call_id=call['id'], phone=call['fromNumber'], duration=call['durationSeconds'], summary=call['summary'], recording=call['recordingUrl'] ) # Check for action items in summary if 'refund' in call['summary'].lower(): tickets.create( title=f"Refund request from call", priority='high', description=call['summary'] ) logger.info(f"Processed call {call['id']}") except Exception as e: logger.error(f"Post-call processing error: {e}") ``` ## Security Best Practices ### Verify Webhook Sources **Signature verification (recommended):** Turn on webhook signing for your organization and verify every request. One secret signs every webhook RevRing sends you: pre-call, post-call and message received. 1. Create a secret (the full value is returned only by this call; store it with your other secrets): ```bash theme={null} curl -X POST https://api.revring.ai/v1/webhooks/signing-secret -H "x-api-key: $REVRING_API_KEY" # { "data": { "secret": "whsec_...", "enabled": true, "last4": "a1b2" } } ``` 2. From then on each webhook carries two headers: | Header | Value | | - | - | | `X-RevRing-Timestamp` | Unix time in seconds when the request was signed | | `X-RevRing-Signature` | `v1=` followed by hex HMAC-SHA256 of `"{timestamp}.{raw body}"` using your secret | 3. Verify on the raw request body, before parsing JSON, and reject timestamps older than a few minutes: ```javascript theme={null} import crypto from "node:crypto"; function verifyRevRingSignature(rawBody, headers, secret, toleranceSeconds = 300) { const timestamp = Number(headers["x-revring-timestamp"]); const signature = headers["x-revring-signature"] || ""; if (!Number.isFinite(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false; const expected = "v1=" + crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); return expected.length === signature.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } // Express: keep the raw body for verification app.post("/webhooks/post-call", express.raw({ type: "application/json" }), (req, res) => { if (!verifyRevRingSignature(req.body.toString("utf8"), req.headers, process.env.REVRING_WEBHOOK_SECRET)) { return res.status(401).end(); } const call = JSON.parse(req.body.toString("utf8")); // ... res.status(200).end(); }); ``` ```python theme={null} import hmac, hashlib, time def verify_revring_signature(raw_body: bytes, headers, secret: str, tolerance_seconds: int = 300) -> bool: try: timestamp = int(headers.get("X-RevRing-Timestamp", "")) except ValueError: return False if abs(time.time() - timestamp) > tolerance_seconds: return False expected = "v1=" + hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, headers.get("X-RevRing-Signature", "")) ``` Rotate with the same `POST` (the new secret is live within a minute; verify against both during the switch), check the state with `GET`, and turn signing off with `DELETE`. See the [signing secret reference](/api-reference/webhooks/signing-secret). **Request Validation:** Implement validation to ensure webhook requests contain expected fields and structure. ### Use HTTPS Always use HTTPS for webhook URLs to ensure data is encrypted in transit: ``` ✓ https://api.yourcompany.com/webhooks/pre-call ✗ http://api.yourcompany.com/webhooks/pre-call ``` ### Validate Payload Validate incoming webhook data before processing: ```javascript theme={null} app.post('/webhooks/post-call', (req, res) => { const call = req.body; // Validate required fields if (!call.id || !call.status || !call.direction) { return res.status(400).json({ error: 'Invalid payload' }); } // Validate id format (UUID) if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(call.id)) { return res.status(400).json({ error: 'Invalid call id' }); } // Process... }); ``` ### Rate Limiting Implement rate limiting to prevent abuse: ```javascript theme={null} const rateLimit = require('express-rate-limit'); const webhookLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 100 // max 100 requests per minute }); app.post('/webhooks/post-call', webhookLimiter, (req, res) => { // Process webhook }); ``` ## Testing Webhooks ### Local Development with ngrok For local testing, use [ngrok](https://ngrok.com) to expose your local server: ```bash theme={null} # Start your local server on port 3000 npm start # In another terminal, start ngrok ngrok http 3000 ``` Use the ngrok HTTPS URL in your webhook configuration: ``` https://abc123.ngrok.io/webhooks/pre-call ``` ### Testing Tools **Request Inspection:** Use [webhook.site](https://webhook.site) to inspect webhook payloads without writing code. **Manual Testing:** Use curl to simulate webhook responses: ```bash theme={null} curl -X POST http://localhost:3000/webhooks/pre-call \ -H "Content-Type: application/json" \ -d '{ "event": "pre_call", "agentId": "cmhckjdxw0001ik0hj4jnn49f", "fromNumber": "+14155551234", "toNumber": "+12025559999" }' ``` ### Test with Real Calls 1. Configure your webhook URL in the **Advanced** tab 2. For **pre-call webhooks**: use the **test panel** to make a browser call and check your server logs 3. For **post-call webhooks**: use either the test panel or the [Send Call](https://dashboard.revring.ai/app/testing/send) page 4. Check call details in [Call Logs](https://dashboard.revring.ai/app/calls) to verify webhook status ## Monitoring Webhooks ### Check Webhook Status For each call in [Call Logs](https://dashboard.revring.ai/app/calls): 1. Click on the call to view details 2. Check **Webhook Status** in the Overview card: * Pre-call webhook status * Post-call webhook status 3. If failed, review your server logs for errors ### Common Issues * Ensure your webhook responds within 5 seconds (pre-call; the caller is waiting) or 30 seconds (post-call) * Move slow operations (database writes, external API calls) to async background processing * Return acknowledgment immediately, then process in background * Check your server's response time and optimize slow queries * Verify your endpoint is accessible from the internet * Check that you're returning a 200 status code * Ensure response is valid JSON (even if empty: `{}`) * Review server logs for exceptions or errors * Test endpoint with curl or Postman to verify it works * Verify your pre-call webhook returns valid JSON with variable keys * Check variable names match exactly between webhook and prompt (case-sensitive) * Ensure the webhook responds within 5 seconds; after that the call starts without its variables * Test webhook independently to confirm it returns expected data * Review the call's `variables` field in call logs to see what was actually received * Confirm the webhook URL is configured in the agent's **Advanced** tab * Verify the URL is correct and accessible * Check your server logs - RevRing may be sending but your server isn't logging * Test with webhook.site to confirm RevRing is sending requests * Review firewall rules that might block incoming requests ## Best Practices ### Performance * **Respond quickly**: Acknowledge webhooks within 1-2 seconds * **Process asynchronously**: Handle heavy processing in background jobs * **Cache frequently**: Cache CRM lookups or database queries when possible * **Use connection pooling**: Reuse database connections across requests * **Set timeouts**: Configure timeouts for external API calls to prevent hanging ### Reliability * **Idempotency**: Design webhooks to handle duplicate calls (use `id` as idempotency key) * **Error handling**: Wrap processing in try/catch and return 200 even if internal processing fails * **Logging**: Log all webhook requests and responses for debugging * **Monitoring**: Set up alerts for webhook failures or high error rates * **Fallbacks**: Provide default values if lookups fail ### Security * **HTTPS only**: Never use HTTP for webhook URLs * **Validate input**: Sanitize and validate all incoming data * **Request validation**: Verify requests contain expected structure and fields * **Rate limiting**: Protect against potential abuse * **Audit logging**: Log all webhook activity for security review ## Advanced Patterns ### Dynamic Agent Customization Use pre-call webhooks to provide variables that customize agent behavior for each caller: **Webhook Response:** ```json theme={null} { "variables": { "firstName": "John", "accountType": "premium", "lastPurchase": "March 15, 2024", "supportHours": "Monday-Friday 9am-5pm EST" } } ``` **Reference in First Message:** ``` Hi {{firstName}}, thanks for calling! How can I help you today? ``` **Reference in Prompt:** ``` You are a customer service agent speaking with {{firstName}}, who is a {{accountType}} customer. Their last purchase was on {{lastPurchase}}. If asked about business hours, say: {{supportHours}} ``` ### Call Outcome Tracking Use post-call webhooks to track outcomes and trigger workflows: ```javascript theme={null} app.post('/webhooks/post-call', async (req, res) => { const { summary, id, durationSeconds } = req.body; res.json({ received: true }); // Categorize call outcome let outcome = 'unknown'; if (summary.includes('appointment scheduled')) outcome = 'scheduled'; if (summary.includes('transferred')) outcome = 'escalated'; if (summary.includes('resolved')) outcome = 'resolved'; // Update analytics await analytics.track({ event: 'call_completed', callId: id, outcome: outcome, duration: durationSeconds }); // Trigger workflows if (outcome === 'scheduled') { await calendar.sendConfirmation(id); } }); ``` ## Inbound Message Webhook When an agent's inbound messaging mode is set to **notify**, RevRing forwards each incoming text to a webhook URL you choose, and your own system composes and sends the reply. (In **auto-reply** mode RevRing replies automatically from the agent's SMS prompt, and no webhook is sent — see [Two-way texting](/platform/two-way-messaging).) ### When to Use * You orchestrate replies yourself (in N8N, Make, your CRM, etc.) and want full control over each reply. * You want to log, score, or branch on an inbound text before responding. ### Configuration 1. On the agent's **Messaging** tab, set **When a text comes in** to **Notify my webhook** and enter your webhook URL. 2. Assign the receiving number to the agent under **Numbers**. 3. Point your provider's inbound message webhook at RevRing: ``` https://api.revring.ai/webhooks/twilio/inbound ``` ### Request Format When an inbound text arrives, RevRing sends a POST request to your webhook URL: ```json theme={null} { "event": "message_received", "organizationId": "org_123abc", "agentId": "agent_456def", "conversationId": "conv_789ghi", "fromNumber": "+14155551234", "toNumber": "+12025559999", "body": "Yes, that time works for me", "messageSid": "SM0123456789abcdef" } ``` **Field Descriptions:** * `event`: Always `"message_received"` for inbound messages. * `organizationId`: Your organization ID. * `agentId`: The agent that owns the number that received the text. * `conversationId`: RevRing's id for the thread with this contact. Use it with the [messages log](/api-reference/messages/list) to read the full conversation. * `fromNumber`: The contact's number (E.164). * `toNumber`: Your number that received the text (E.164). * `body`: The text content of the incoming message. * `messageSid`: Your provider's id for the inbound message. ### Response Expectations Respond with `200 OK` promptly. RevRing does not require a response body and does not retry. To reply, call the [Send Message API](/api-reference/messages/send). ### Example Implementation **Node.js / Express:** ```javascript theme={null} app.post('/webhooks/revring-inbound', async (req, res) => { const { fromNumber, toNumber, agentId } = req.body // Acknowledge immediately res.status(200).json({ received: true }) // Reply via RevRing. Pass your own `body`, or let RevRing write it from the // agent's SMS prompt with `generate: true`. Set `from` to the number that // received the text so the reply continues the same conversation. await fetch('https://api.revring.ai/v1/messages', { method: 'POST', headers: { 'x-api-key': process.env.REVRING_API_KEY, 'content-type': 'application/json', }, body: JSON.stringify({ agentId, to: fromNumber, from: toNumber, generate: true }), }) }) ``` **Python / Flask:** ```python theme={null} @app.route('/webhooks/revring-inbound', methods=['POST']) def revring_inbound(): data = request.get_json() requests.post( 'https://api.revring.ai/v1/messages', headers={'x-api-key': os.environ['REVRING_API_KEY']}, json={ 'agentId': data['agentId'], 'to': data['fromNumber'], 'from': data['toNumber'], 'generate': True, }, ) return {'received': True}, 200 ``` ## Next Steps Configure system and custom tools View complete webhook API documentation Set up inbound call handling Configure outbound calling # WebRTC Source: https://docs.revring.ai/platform/webrtc Add voice AI agents to web browsers and mobile apps Enable voice calling directly from web browsers, iOS apps, and Android apps using the RevRing WebRTC SDK. End users can talk to your AI agents without leaving your website or application. ## How RevRing WebRTC Works WebRTC enables real-time voice communication in browsers and mobile apps: 1. End user clicks a call button in your web or mobile app 2. Your app uses the RevRing SDK to connect them to one of your AI agents 3. The call bridges through your connected Twilio account 4. End user speaks directly with your agent through their device microphone and speakers 5. After the call ends, you receive full transcript, recording, and analytics just like phone calls WebRTC calls appear in your call logs with a "WebRTC" badge for easy identification. All features like transcripts, recordings, webhooks, and custom tools work exactly the same as regular phone calls. ## Prerequisites Before you begin: * Active RevRing account with at least one configured agent * Your own Twilio account (can be the same account used for your SIP trunk) * At least one phone number assigned to your agent for inbound routing ## Twilio Rates RevRing WebRTC uses your own Twilio account - Twilio might charge you the following: * **User call leg**: \$0.004/minute * **Agent call leg**: \$0.004/minute * **Total**: around \$0.008/minute (check Twilio's website to calculate your cost) RevRing does not charge any additional fees for WebRTC. You only pay Twilio voice rates on your own Twilio account. Your existing RevRing plan covers WebRTC calls just like regular phone calls. ## Setup ### Step 1: Connect Your Twilio Account 1. Navigate to [Developer → WebRTC](https://dashboard.revring.ai/app/webrtc) in your RevRing dashboard 2. You'll need two values from your Twilio Console: #### Get Your Twilio Account SID 1. Log into [Twilio Console](https://console.twilio.com) 2. From the console homepage, copy your **Account SID** 3. Paste it into the **Twilio Account SID** field in RevRing #### Get Your Twilio Auth Token 1. In the Twilio Console, click **Show** next to Auth Token 2. Copy the **Auth Token** 3. Paste it into the **Twilio Auth Token** field in RevRing Your Auth Token is used only during initial setup to create voice credentials and configure your Twilio account. It is not stored by RevRing. 3. Click **Set Up WebRTC** to submit the form RevRing will automatically create and configure the necessary Twilio resources (credentials for voice calls and TwiML application) for WebRTC calling. ### Step 2: Assign a Phone Number to Your Agent Your agent needs at least one assigned phone number for WebRTC routing: 1. Navigate to [Agents](https://dashboard.revring.ai/app/agents) and select your agent 2. Go to the **Phone Numbers** tab 3. Add an inbound number in E.164 format: ``` +12025551234 ``` 4. Click **Add Number** This number is used internally to route WebRTC calls to the correct agent. End users never see or dial this number - you'll specify it in your SDK code. You can use any valid E.164 number for WebRTC routing. It doesn't need to be a Twilio number - it just needs to be assigned to your agent in RevRing. ## JavaScript SDK The RevRing JavaScript SDK works in any modern browser and supports TypeScript out of the box. ### Installation Install the SDK via npm: ```bash theme={null} npm install @revring/webrtc-sdk ``` Or using yarn: ```bash theme={null} yarn add @revring/webrtc-sdk ``` ### Basic Usage Here's a minimal example of a call button: ```typescript theme={null} import { RevRingWebRtcClient } from "@revring/webrtc-sdk"; // Initialize the SDK client const client = new RevRingWebRtcClient(); // Phone number assigned to your agent (end users never see this) const AGENT_NUMBER = "+12025551234"; // Start a call when end user clicks your button async function startCall() { try { const call = await client.startCall({ to: AGENT_NUMBER }); // Listen for call events call.on("accept", () => { console.log("Call connected"); }); call.on("disconnect", () => { console.log("Call ended"); }); call.on("error", (error) => { console.error("Call error:", error); }); } catch (error) { console.error("Failed to start call:", error); } } // Hang up the call function hangUp() { client.hangupAll(); } // Clean up when done function cleanup() { client.destroy(); } ``` ### Configuration Options #### Initialize Client ```typescript theme={null} const client = new RevRingWebRtcClient({ // Optional: custom API base URL (defaults to production) apiBaseUrl: "https://api.revring.ai" }); ``` #### Start Call Options ```typescript theme={null} const call = await client.startCall({ // Required: phone number assigned to your agent (E.164 format) to: "+12025551234", // Optional: Twilio SDK log level twilioLogLevel: "error" // "trace" | "debug" | "info" | "warn" | "error" | "silent" }); ``` ### Call Events The call object returned by `startCall()` emits several events: ```typescript theme={null} call.on("accept", () => { // Call successfully connected }); call.on("disconnect", () => { // Call ended (by either party) }); call.on("cancel", () => { // Call was cancelled before connecting }); call.on("reject", () => { // Call was rejected (agent busy, offline, etc.) }); call.on("error", (error) => { // Error occurred during call console.error(error); }); ``` ### Methods #### `startCall(options)` Initiates a WebRTC call to connect the end user with one of your agents. **Parameters:** * `to` (string, required): Phone number assigned to your agent (E.164 format) * `twilioLogLevel` (string, optional): Log level for debugging * `apiBaseUrl` (string, optional): Override API base URL **Returns:** Promise that resolves to a Twilio Call object #### `hangupAll()` Disconnects all active calls. #### `destroy()` Cleans up the SDK and releases resources. Call this when unmounting your component or page. ## Complete Example Here's a full implementation with UI state management: ```typescript theme={null} import { RevRingWebRtcClient } from "@revring/webrtc-sdk"; const AGENT_NUMBER = "+12025551234"; class CallWidget { private client: RevRingWebRtcClient; private activeCall: any = null; private isConnected = false; constructor() { this.client = new RevRingWebRtcClient(); } async startCall() { try { const call = await this.client.startCall({ to: AGENT_NUMBER }); this.activeCall = call; call.on("accept", () => { this.isConnected = true; this.updateUI("Connected"); }); call.on("disconnect", () => { this.isConnected = false; this.activeCall = null; this.updateUI("Call ended"); }); call.on("error", (error) => { console.error("Call error:", error); this.updateUI("Call failed"); }); this.updateUI("Connecting..."); } catch (error) { console.error("Failed to start call:", error); this.updateUI("Failed to connect"); } } hangup() { if (this.activeCall) { this.activeCall.disconnect(); this.activeCall = null; this.isConnected = false; } } destroy() { this.hangup(); this.client.destroy(); } private updateUI(status: string) { // Update your UI elements here console.log(status); } } // Initialize const widget = new CallWidget(); // Clean up on page unload window.addEventListener("beforeunload", () => { widget.destroy(); }); ``` ## Mobile SDKs WebRTC support extends beyond the browser. Build native mobile experiences with our platform SDKs: * **iOS SDK**: Native Swift implementation for iPhone and iPad apps * **Android SDK**: Native Kotlin/Java implementation for Android devices * **React Native SDK**: Cross-platform support for React Native apps Contact support for help with mobile SDKs and documentation. ## Best Practices ### Microphone Permissions Modern browsers require user interaction (like a button click) before accessing the microphone. Always initiate calls from an end user action: ```typescript theme={null} // ✅ Good - triggered by end user click button.addEventListener("click", async () => { await client.startCall({ to: AGENT_NUMBER }); }); // ❌ Bad - automatic call on page load window.addEventListener("load", async () => { await client.startCall({ to: AGENT_NUMBER }); }); ``` ### Error Handling Handle permission denials gracefully: ```typescript theme={null} try { const call = await client.startCall({ to: AGENT_NUMBER }); } catch (error) { if (error.message.includes("Permission")) { alert("Please allow microphone access to make calls"); } else { alert("Failed to start call. Please try again."); } } ``` ### Resource Cleanup Always clean up when your component unmounts or page unloads: ```typescript theme={null} // React example useEffect(() => { const client = new RevRingWebRtcClient(); return () => { client.destroy(); }; }, []); ``` ### Testing Test your WebRTC implementation thoroughly: 1. Test with microphone permissions granted and denied 2. Test hang up during connecting state 3. Test hang up during active call 4. Test network interruptions 5. Test on different browsers (Chrome, Safari, Firefox, Edge) 6. Test on mobile browsers (iOS Safari, Android Chrome) If you are facing call connection issues, use `twilioLogLevel: "debug"` during development to see detailed connection logs. Set it to `"error"` or `"silent"` in production. ### Embedding on Your Website To add a call widget to your website, build your widget as a standalone page and embed it using an iframe. Host your widget on platforms like Cloudflare Pages, Vercel, or AWS S3/CloudFront, then embed it in your main website: ```html theme={null} ``` The `allow="microphone"` attribute is required for WebRTC calls to work inside an iframe. ## Monitoring Calls WebRTC calls appear in your [Call Logs](https://dashboard.revring.ai/app/calls) with a "WebRTC" badge in the "From" column. All standard call features are available: * Full conversation transcript * Call recording playback * Call duration and status * Pre-call and post-call webhook data * Custom tool invocations To filter for only WebRTC calls via the API, use the `fromNumber` parameter with `+19999999999` in the [List Calls](/api-reference/calls/list) endpoint. ## Troubleshooting ### "Failed to get WebRTC token" **Cause:** Twilio account not configured or phone number not assigned to an agent. **Solution:** 1. Verify WebRTC is configured in [Developer → WebRTC](https://dashboard.revring.ai/app/webrtc) 2. Verify the phone number is assigned to an agent in the agent's Phone Numbers tab ### "Permission denied" errors **Cause:** End user denied microphone access or browser doesn't support microphone API. **Solution:** 1. Ensure calls are triggered from end user interactions (button clicks) 2. Check browser permissions and prompt end user to allow microphone access 3. Test in a supported browser (Chrome, Safari, Firefox, Edge) ### Calls not connecting **Cause:** Invalid phone number or agent offline. **Solution:** 1. Verify the phone number is in E.164 format (`+12025551234`) 2. Verify the agent status is "Active" 3. Check [Call Logs](https://dashboard.revring.ai/app/calls) for error details ### No audio in call **Cause:** Browser audio permissions or speaker issues. **Solution:** 1. Check browser permissions for microphone and speaker 2. Verify device audio output is working 3. Test with headphones to rule out speaker issues ## Next Steps Full SDK reference on NPM Integrate with your systems Customize agent behavior Monitor WebRTC calls # Quickstart Source: https://docs.revring.ai/quickstart Get your first RevRing AI agent up and running in minutes This guide walks you through connecting a Twilio Elastic SIP trunk, creating your first AI voice agent, and making your first test call. ## Prerequisites Before you begin, ensure you have: * A RevRing account with access to the [dashboard](https://dashboard.revring.ai) * A Twilio account with Elastic SIP Trunking enabled * At least one phone number provisioned in Twilio ## Step 1: Connect Your SIP Trunk SIP trunks connect your AI agents to the phone network, enabling both inbound and outbound calling. ### Configure Twilio 1. Log into your [Twilio Console](https://console.twilio.com) 2. Navigate to **Elastic SIP Trunking** → **Credential Lists** and create a new Credential List with a username and password 3. Go to **Elastic SIP Trunking** → **Trunks** and create or select an existing trunk 4. In **Origination**, add an **Origination SIP URI**: `sip:sip.revring.ai` 5. In **Termination**, note your **Termination SIP URI** (e.g., `yourtrunk.pstn.twilio.com`) 6. In **Termination** → **Authentication** → **Credential Lists**, select your Credential List 7. Assign your phone numbers to the trunk in the **Numbers** tab ### Add the Trunk to RevRing 1. Navigate to [SIP Trunks](https://dashboard.revring.ai/app/sip-trunks) in your RevRing dashboard 2. Click **Add SIP Trunk** 3. Fill in the form: * **Name**: `Twilio Elastic` (or any descriptive name) * **Proxy**: Your Twilio Termination SIP URI from step 5 above (e.g., `yourtrunk.pstn.twilio.com`) * **Username**: Enter the username from your Twilio Credential List * **Password**: Enter the password from your Twilio Credential List * **Phone Numbers**: Enter your phone numbers in E.164 format, separated by commas (e.g., `+12025551234, +14155552671`) 4. Click **Add Trunk** Phone numbers must be in E.164 format starting with `+` followed by country code and number (no spaces or dashes). For detailed Twilio setup instructions, see the [Telephony Setup](/platform/telephony) guide. ## Step 2: Create Your First Agent Agents are AI-powered voice assistants that handle phone conversations based on the instructions you provide. ### Create the Agent 1. Navigate to [Agents](https://dashboard.revring.ai/app/agents) 2. Click **New Agent** 3. Enter a **Name** (e.g., `Sales Assistant`) 4. Click **Create** ### Configure Agent Behavior After creation, you'll be taken to the agent detail page with several tabs. Configure the **Overview** tab: 1. **SIP Trunk**: Select the trunk you just created 2. **Status**: Set to `Active` to enable the agent 3. **First Message**: Enter an opening greeting (optional) ``` Hello! Thanks for calling. How can I help you today? ``` 4. **Prompt**: Define how the agent should behave during calls ``` You are a helpful sales assistant for Acme Corp. Your role is to: - Greet callers warmly - Answer questions about our products and services - Collect contact information if they want a callback - Transfer to a human rep if needed Be professional, friendly, and concise. Keep responses under 30 seconds. ``` 5. Click **Save** The prompt is the most important field - it defines your agent's personality, knowledge, and behavior. You can use `{{variables}}` to personalize responses based on caller data. ### Configure Voice Settings Switch to the **Voice** tab to customize how your agent sounds: 1. **Voice**: Select a voice that matches your brand (click play to preview) 2. Click **Save Voice** Browse all 300+ available voices in the [Voice Library](https://dashboard.revring.ai/app/agents/voices). You can filter by language and preview each voice before selecting. ## Step 3: Test Your Agent The **test panel** on the right side of the agent page lets you call your agent directly from the browser — no phone required. ### Make a Test Call 1. Open your agent and look at the **test panel** on the right 2. Click the **call button** (microphone icon) to start a WebRTC call 3. Speak into your computer's microphone — you're now talking to your agent 4. Click the button again (X icon) to end the call The **Transcript** section updates in real time as the conversation progresses. After the call ends, review the full transcript to check for errors or unexpected behavior. Expand the **Test Variables** section at the bottom of the test panel to set variable values (like `{{customer_name}}`). These are passed to your agent during test calls so you can verify personalization works correctly. ### Send an Outbound Test Call To test outbound calling over the phone network (real SIP calls): 1. Navigate to [Send Call](https://dashboard.revring.ai/app/testing/send) 2. Select your **Agent**, enter a **From Number** and **To Number** 3. Click **Send Test Call** The call will initiate within seconds. Answer your phone to speak with your agent. ### Iterate and Improve Based on your test call: 1. Return to the **Overview** tab 2. Refine your **Prompt** to improve agent responses 3. Adjust **First Message** if needed 4. Make another test call to validate changes ## Next Steps Now that you have a working agent, explore more advanced features: Learn about configuring Twilio and Telnyx for production use Set up phone numbers to receive inbound calls Enable call transfer, voicemail detection, and custom tools Integrate with your systems using pre-call and post-call webhooks ## Common Issues * Verify your SIP trunk proxy address is correct * Ensure phone numbers are in valid E.164 format * Check that your Twilio trunk has the phone number assigned * Confirm your agent status is set to `Active` * Review and refine your prompt for clearer instructions * Check the transcript for specific errors * Ensure your prompt isn't too long or complex * Try different voices to find the best fit for your use case * Phone numbers must be added to both your telephony provider AND your RevRing SIP trunk * Use E.164 format: `+[country code][number]` with no spaces or special characters * You can edit phone numbers by clicking the menu on any trunk in the SIP Trunks list