To build a voice agent for law firms in Dograh, greet with the firm's name, ask for names before facts, and run a conflict lookup with a stop path. Put the no-advice line in fixed Transition Speech, book with a tool that cannot double-book, transfer urgent matters, send the record by webhook, and test on a real phone line.
Key Takeaways
- Ask for names first; the conflict lookup runs before any case facts.
- The no-advice line is fixed text on a pathway, not a prompt rule.
- Test on a real phone line; transfers do not run in the browser.
This post is part of our guide to AI voice agents for law firms. The guide's section "Choosing a platform and building the agent" points here for the build: an intake and booking agent with a conflict lookup, a booking tool that cannot double-book and a test over a real phone call. This tutorial builds it step by step in Dograh, an open-source, self-hostable voice agent platform.
What you need before you start
You need Dograh, a Twilio phone number connected to Dograh (Transfer Call works on Twilio only), and a few endpoints on the firm's side. Dograh runs as Dograh Cloud or on your own machine. A local install needs at least 2 CPU cores, 8 GB of RAM (4 GB available for Docker), 10 GB of free disk and Docker 20.10 or later, per the prerequisites. Endpoints are web addresses your own system answers at. This build uses four, on the firm's case system or a small service in front of it: a conflict lookup, a list of open consultation slots, a booking writer and an intake-note writer.
What you'll build:
- An intake agent that asks for names before it asks what happened.
- A conflict lookup that stops intake on a possible match.
- A fixed no-advice line on a pathway into a handoff.
- A booking tool that writes one tentative consultation.
- A transfer to the intake team, and a webhook to the case system.
Dograh is the orchestration layer: it runs the call flow, tools and telephony, and the speech and language models are a separate layer you choose. For privileged intake, where the audio should reach no outside company, run Dograh and the models on the firm's own servers. Texas ethics Opinion 705 lets a lawyer share confidential information with a service provider only if "reasonably confident" it will be protected, and the fewer providers hear the call, the easier that is to show; On-Prem Voice AI for Law Firms covers that setup. Self-hosted Dograh has no per-minute platform fee (servers, model keys and phone minutes still cost); Dograh Cloud has a 1¢-a-minute platform fee.
Step 1: Create the agent and write the greeting
Start with the Create Voice Agent form. Set Call Type to Inbound, enter Legal Intake as the Use Case, and describe the job in Activity Description: "Answer new-client calls for a personal injury and family law firm, collect names for a conflict check, and book a consultation." Dograh generates a prompt from that description for you to edit.
Add the firm's rules in plain words to the Global Prompt of the Global Node (add one from the node panel if the generated flow has none). Every Start Call and Agent node with Add Global Prompt switched on, the default, receives it. The agent collects information and books time. It never says whether the caller has a case or what it is worth. It never quotes a fee the tools did not return.
Then open the Start Call node and write the Greeting Text. Dograh speaks it word for word, so the firm writes it and the model does not: "Thank you for calling Harbour Law. You're speaking with the firm's automated assistant. I can take your details and book a consultation with one of our lawyers." Name no caller in it, since it plays before anyone is known.
Check: on a browser test call, the greeting names the firm and says it is automated if you wrote that.
Step 2: Ask for names before facts
The first questions collect what the conflict check needs, before the caller describes the matter. Under Model Rule 1.18, "A person who consults with a lawyer about the possibility of forming a client-lawyer relationship with respect to a matter is a prospective client", so the caller is owed confidentiality from the first sentence.
In the intake node's prompt, set the order: the caller's full name, the names of the other people or companies involved, the practice area and the state where it happened. Tell the agent that if the caller starts describing the facts, it thanks them and says a lawyer will go through the details, then returns to the names. Callers often lead with the story anyway, which is why the lookup in the next step runs as early as the names arrive. AI Voice Agents for Law Firm Intake explains why this order matters.
Check: on a test call, start with "I was in a crash last week." The agent acknowledges it and asks for your name and the other driver's name next.
Step 3: Run the conflict lookup with an HTTP API tool
The conflict lookup asks the firm's own system whether any name matches a current or former client, and the agent learns only whether to continue. Create an HTTP API tool named conflict_lookup with the method POST and its Endpoint URL set to your conflict lookup. On the Parameters tab, add two LLM Parameters: caller_name (String) and other_parties (Array, the other people's and companies' names). Attach it to the intake node under Tools. Every tool in this build is attached the same way, to the node that uses it. Write its description so the model knows when to call it: "Check the names for conflicts as soon as you have the caller's name and the other party's names."
Your endpoint does the matching, including the hard parts such as married names and parent companies, and returns only a status. It never sends back who the match is:
{
"status": "possible_match",
"review_id": "CR-20931"
}
What this does: It tells the agent to stop, without naming the client who matched. The agent cannot leak what it never received, and
review_idlets staff find the request later.
On the tool's Settings tab, add a Custom Message, such as "One moment while I note that down," so the caller hears something during the lookup. HTTP API tools wait 5 seconds by default and do not retry, so a slow conflict system returns an error to the agent; write that case into the prompt as "treat it like a possible match".
Now add the stop path. Draw a pathway from the intake node to a "Staff review" node. Set its Condition Label to "Staff review" and its Condition to "the conflict_lookup tool returned possible_match or an error". On a possible match, the agent stops asking about the case, takes a callback number and ends politely. A tool on that node writes the callback request with the review_id to the firm's system.

Check: with your endpoint set to return possible_match, the agent asks no further case questions and your system receives one callback request.
Open Source Alternative to Vapi / Retell
Self-hosted voice agent platform — no per-minute fees
dograh-hq/dograh
Star on GitHub
Step 4: Put the no-advice line in fixed text
The sentence that refuses advice must be exact, so it lives in fixed text, not in a prompt. A model told "never give legal advice" can avoid the phrase and still say "that sounds like a strong claim". Exact wording holds only in fixed text: the Greeting Text, a pathway's Transition Speech (text or a recorded clip), or the Transfer Call tool's Pre-Transfer Message.
Add a pathway from the intake node to a "Lawyer callback" node with the Condition Label "Lawyer callback" and the Condition "the caller asks whether they have a case or what it is worth". In Transition Speech, write the line: "I can't advise on your situation, but I can book you with a lawyer who can." Dograh plays it as written before the next node starts.
Choosing the pathway is still the model reading your Condition, so it is not a hard rule. You prove it worked on transcripts later. Prompt and flow design patterns for reliable voice agents covers this split in depth.
Check: ask "Do you think I'd win?" three different ways. Each time you hear the exact line, and the call moves to the Lawyer callback node.
Step 5: Book the consultation without double-booking
The booking reads open slots, offers only those, and writes one tentative consultation. Create a GET tool named get_open_slots with parameters for practice area and office. The agent offers only the times that come back.
Then create book_consultation with the method POST. Its LLM Parameters are slot_id (String), caller_name (String), caller_phone (String) and adverse_parties (Array, the names collected in Step 2). Add a Preset Parameter named workflow_run_id, type Number, with the value {{initial_context.workflow_run_id}}, and switch Required on. Add a second Preset Parameter named status, type String, with the fixed value tentative. Dograh fills it from the call, so the model never types it, and Dograh adds no idempotency header of its own. Your endpoint receives a body like this, with the booking marked tentative and the parties attached:
{
"workflow_run_id": 58214,
"slot_id": "2026-11-04T10:00-PI-DOWNTOWN",
"caller_name": "Dana Ruiz",
"caller_phone": "+1415555XXXX",
"adverse_parties": ["Westline Freight LLC"],
"status": "tentative"
}
What this does: It books one tentative consultation and ties it to this call through
workflow_run_id, so a repeated request finds the same booking. The parties travel with it, so staff can clear the conflict check before the consult.
workflow_run_id stops one call from booking twice. To stop two callers taking the same time, your endpoint should also refuse a slot that was taken after get_open_slots ran, and return a sentence the agent reads so it can offer another time.
Tell the agent in the prompt to read the booking back before it calls the tool: date, time, office and the caller's surname spelled letter by letter. The scheduling side, including reminders, is covered in AI voice agents for legal appointment scheduling.
Check: one test call creates one tentative booking, and sending the same request twice still leaves one.
Step 6: Hand urgent matters to a person
Some callers need a person now, such as someone with a hearing tomorrow or a family member just arrested. Add a pathway with the Condition Label "Urgent" and a Condition describing those cases, to a node that uses Transfer Call.
Transfer Call works on Twilio only and passes no summary, so before it, the agent calls a log_handover tool that writes the reason and the names to the firm's system. In the Transfer Call tool, set Destination Source to Static / Template and enter the intake team's number in E.164 format, such as +1415555XXXX. Then choose Custom Message under Pre-Transfer Message for a fixed line such as "I'm connecting you to our intake team now." Transfer Timeout defaults to 30 seconds and can be set from 5 to 120. If nobody answers, the caller stays on the line with the agent, so add a prompt line: if the transfer fails, write an urgent callback request with log_handover.
Before the first phone test, publish the agent, because edits reach test calls straight away but live calls only after publishing, and set your Twilio number's inbound workflow to this agent.
Check: on a phone call, say "I have a court date tomorrow." The intake phone rings, and the handover note is already in your system.
Join the Dograh Community
Dograh is an OSS alternative to Vapi. Join our Slack community for queries, releases, best practices & community interactions.
Step 7: Send the intake record to the case system
A webhook writes the outcome of every call to the case system. On the End Call node, turn on Enable Variable Extraction. Under Variables to Extract, add outcome (String, Extraction Hint: "booked, staff review, transferred or no booking") and practice_area (String, Extraction Hint: "the practice area as the caller stated it"). Then add a Webhook node. Its fields are Name, Enabled, HTTP Method, Endpoint URL, Authentication, Custom Headers and Payload Template. The payload below sends the run ID, the extracted fields and the transcript link:
{
"call_id": "{{workflow_run_id}}",
"outcome": "{{gathered_context.outcome}}",
"practice_area": "{{gathered_context.practice_area}}",
"transcript_url": "{{transcript_url}}"
}
What this does: After each call, Dograh fills the braces with that call's values and posts the record to your case system, so the intake team sees every call, booked or not, in one place.
A temporary failure, such as a timeout or a 5xx, is retried automatically, up to 5 attempts. Extraction on the End Call node runs only when a call reaches that node, so a call where the caller hangs up early still sends a record, without those two fields filled in.
Check: one finished test call adds one record to your case system, with a working transcript link.
Step 8: Add the firm's words to the Dictionary
Legal terms and local names are where speech-to-text slips. In the agent's Settings, open Dictionary, type them into the box separated by commas and click Save Dictionary: practice terms such as "subrogation" and "PIP", and the names of local courts.
Check: say "personal injury protection, PIP" and the court name on a phone call, then read the transcript. Both are spelled the way you entered them.
Step 9: Test in the browser, then on a real phone line
A browser test proves the flow, and a phone test proves the call. A web call runs at 16 kHz, the most Dograh's pipeline uses, and it cannot run telephony-only tools such as Transfer Call. Most phone calls still reach the agent as 8 kHz audio, where spelled names are misheard more often. How much depends on the speech-to-text model and the phone path, so measure it on your own line.
Run each leg several times, because AI replies vary. Spell surnames with letters that sound alike, like M and N. Ask for advice in different words. For each phone call, where tracing is set up, open View Trace on the run page to see what speech-to-text heard and which tools ran, with their requests and responses. Each transcript turn is saved with the node it was spoken in, so you can see whether the no-advice line came from the right pathway. Testing Voice Agents Before You Go Live gives the tests in order. Mask any real numbers in what you share, as in +1415555XXXX.
Check: five phone calls in a row collect names before facts and spell the surname right.
Before you ship it
Add a QA node so a random sample of live calls is scored step by step, and read every call that ended in a staff review or a transfer. Recordings and transcripts have no retention setting and are not deleted automatically, so your own policy decides when the firm deletes them from its storage. Then publish, and start with one line, such as calls after the office closes.
Glossary
- Conflict lookup
- An HTTP API tool that sends the caller's and other parties' names to the firm's own system and gets back only a status. The agent learns whether to continue, never who matched.
- Transition Speech
- Fixed text or a recorded clip that Dograh plays as the call moves along a pathway. It is where wording must be exact, such as the line refusing legal advice.
- Preset parameter
- A tool value Dograh fills from the call itself, such as workflow_run_id, rather than from the conversation. The firm's system uses it to keep one booking if a request arrives twice.
- Tentative booking
- A consultation written to the calendar before staff clear the conflict check. Staff confirm it or cancel and refer the caller.

