How to connect your CRM to an AI voice agent
By the end of this guide your voice agent will open each call already knowing the CRM record behind it, and your CRM will get one verified, deduplicated result per call. Eight steps, the failures we've seen in production, and a copy-paste checklist.
By the end of this guide your voice agent will open each call already knowing the CRM record behind it, and your CRM will receive one typed, verified, deduplicated result for every call, including the calls that never connected.
The core idea: a CRM connection is four separate data flows, and the AI model should own only one of them. Starting the call, reading the record and writing the result back are plumbing. They should run the same way every time, whether or not the model remembers them.
One thing to know first: Callaro has no one-click connector for a named CRM. Everything here uses its API, webhooks and agent tools, which work with any CRM that can send and receive HTTP requests.
Before you start
- A working agent. If you don't have one, start with how to set up an AI voice agent.
- An API user in your CRM with only the permissions this job needs.
- A stable record ID for each lead or contact, and phone numbers stored in international format, with the country code.
- A public HTTPS endpoint to receive call results. If your CRM's inbound webhook can't verify signatures and deduplicate, plan a small service or an automation tool in between.
- A written field map: what the agent reads, and what each call writes back.
Step 1: Draw the four flows and give each an owner
- Trigger. A CRM event, such as a new lead, starts a call. Owner: your CRM.
- Read. The platform fetches the record before the agent says hello. Owner: the platform.
- Act. During the call the agent checks availability or books a slot. Owner: the model, through tools.
- Write back. After the call the platform posts the result to your CRM. Owner: the platform.
Only the third flow should depend on the model's judgement. We've seen models skip a tool call they were explicitly told to make. A write-back that relies on the model calling an "update CRM" tool is one you'll sometimes lose, and always lose when the caller hangs up mid-sentence.
A quick test: if the caller would notice it didn't happen (a booked slot), it's a tool. If only your team would (stage, disposition, notes), it's write-back.
Step 2: Carry your CRM record ID through the call
Phone numbers are a poor join key: two leads can share one, and people call back from a different phone. Callaro keys a contact on its phone number, so two CRM leads with the same number are one contact on the calling side.
Pass the record ID into the call and get it back with the result:
- For event-driven calls, your CRM calls Callaro's outbound-call API with a contact and per-call variables. Put the record ID in those variables. They fill placeholders in the prompt and are echoed back unchanged in the result.
- For a whole list, run a campaign and store the record ID as a custom field on each contact. The agent's main webhook carries the contact record, custom fields included. Campaigns are paced against your line capacity, so don't imitate one with a burst of single calls.
Prefix your variable names (crmLeadId, say). A few platform-reserved names, such as "direction", are removed from the echo.
Step 3: Read the record before hello
Configure a pre-call lookup so the agent starts with the caller's name, stage or open ticket. In Callaro the request is built from call details (such as the caller's number) and your variables, and each response field maps to a prompt variable with its own default. It runs at call set-up, inbound and outbound, so a retry three days later fetches the record as it is now.
Four rules keep the lookup from hurting the call:
- Make it fast. The lookup has to finish before the greeting, on a short timeout. Search an indexed phone field and return only the fields you mapped.
- Return a JSON object. Some search endpoints return a bare list. In Callaro a response whose top level is a list maps nothing, and the call carries on with defaults.
- Write defaults that sound natural. A failed or slow lookup doesn't block the call; each variable falls back to its default. "Hi there" is fine. "Hi ," is not.
- Check the recorded outcome. Callaro stores the lookup's result on the call, including the case where a URL placeholder couldn't be filled and the request was never sent.
Step 4: Give the model a few narrow tools
Mid-call actions are custom HTTP tools, or tools discovered from an MCP server, and the model decides when to call them. Keep the list short, make every write endpoint idempotent, and bind what you already know: in Callaro a tool parameter can take its value from a call variable, such as the record ID or the caller's number, and a bound value overrides anything the model supplies.
More on tool descriptions in the prompt-writing guide, and on idempotent booking in where bookings actually break.
Step 5: Build the receiver before you switch the webhook on
This is where integrations fail quietly: a duplicate activity here, a missed result there. Your endpoint needs five behaviours.
- Acknowledge fast, process later. Return a 2xx status straight away and do the CRM write from a queue. Callaro treats any 2xx as delivered and anything else, including a timeout, as a failure. It retries a few times with a widening gap, then marks the delivery failed. Stripe's webhook documentation gives the same advice.
- Verify the signature on the raw body. When a signing secret is set for the agent, each delivery carries a timestamp and an HMAC-SHA256 signature computed over the timestamp, a full stop and the request body. Verify before your framework parses the JSON, because a re-serialised body will not match, and refuse timestamps more than a few minutes old. Signing stays off until you set a secret, so set one.
- Accept two secrets during rotation. While you rotate, Callaro keeps the previous secret valid next to the new one and sends a signature for each. Accept the request if either matches.
- Deduplicate on the idempotency key. Every delivery has a fresh event ID, plus an idempotency key that stays the same across retries of one event for one call and destination. Store that key before any side effect. If you've seen it, return 2xx and do nothing. The Standard Webhooks specification describes the same pattern.
- Branch on status, not on arrival. A completed event can also be sent for a call that never connected. The payload separates the call's runtime status, the business outcome code and the telephony status, and adds flags for voicemail and call screening. Log a conversation only when there was one.
Two edge cases. A test delivery looks like a real completed call apart from its test flag, so a receiver that ignores the flag writes a fake call into your CRM. And on a very long call the transcript can be replaced by a short stub, with a flag listing what was cut. When you see it, fetch the call record through the API.
Step 6: Choose events and shape the payload
By default an agent's webhook receives the completed event only. You can add call started, transcript updated (every turn), analysis completed and transfer started.
- For a helpdesk handoff, subscribe to transfer started. It carries the destination, so the ticket can open while the human's phone is still ringing.
- If a CRM stage depends on a post-call analysis result, read the analysis status in the payload before trusting the results, or key the update to the analysis completed event.
- The completed event is not instant. It follows post-call processing, and during a campaign Callaro limits how fast it posts to any one URL.
You can also add further named webhooks to an agent, each with its own URL, authentication and event list. Each chooses which blocks it includes (captured data, variables, transcript, recording link, analysis results) and can add fields under your own key names, so a small, flat payload can go straight to a CRM's inbound webhook.
Step 7: Map fields with types and a write policy
Give every captured field a type in the agent's extraction schema and match it in the CRM: an enum to a picklist with identical values, a boolean to a checkbox, a date to a date field. Dates arrive in the agent's configured time zone, so convert once, in the receiver.
Then decide what a write may do to each field: overwrite, fill only if empty, or append as a note. Two rules we'd apply everywhere:
- A person's edit beats the AI. Tag AI-written values with their source and the call ID, and never let an automated write replace a human one. Callaro's own contact memory follows the same rule.
- One inferred flag should not trigger anything irreversible. We've seen a live conversation wrongly flagged as voicemail, and the flag travelled to the CRM in the call result. Before closing a lead, combine it with call duration or the number of caller turns.
Sync opt-outs both ways. When a campaign call resolves as "do not call" or "wrong number", Callaro adds the number to your do-not-call list automatically. Mirror that outcome into the CRM, and push CRM-side opt-outs to that list through the API.
Step 8: Test the round trip, then watch deliveries
- Test-run the pre-call lookup from the agent builder with a sample number.
- Send the test webhook and confirm your signature check passes.
- Place a test call to a real contact and confirm the record ID comes back.
- Retry the same delivery from the call's detail page. Your CRM should still show one activity.
- Make your receiver return a 500. Confirm the retries arrive and the failure shows in the call's webhook log.
- For the first week, filter call logs for failed webhook deliveries daily.
Gotchas from real deployments
- Private URLs. Callaro refuses to deliver webhooks to private or internal network addresses. An on-premise CRM needs a public gateway in front of it.
- Syncing the whole record. Callaro passes a limited number of custom contact fields into the prompt, truncates long values, and skips any key that isn't a plain identifier. A key copied from a CRM label, with spaces or hyphens, never reaches the agent.
- Late and out-of-order events. Retries and recovery sweeps mean a result can arrive late. Order by the timestamps inside the payload, never by arrival time.
Your CRM connection checklist
- [ ] Four flows drawn; the model owns only mid-call actions
- [ ] CRM record ID passed into every call and read back from the result
- [ ] Phone numbers normalised to international format in the CRM
- [ ] Pre-call lookup is fast, returns a JSON object and has a natural default per variable
- [ ] Tools are few and narrow; known values are bound, not left to the model
- [ ] Receiver returns 2xx first and processes from a queue
- [ ] Signature verified on the raw body; stale timestamps rejected; two secrets accepted during rotation
- [ ] Deliveries deduplicated on the idempotency key; test deliveries kept out of live records
- [ ] CRM activity branches on call status and outcome code
- [ ] Every field has a type and a write policy; human edits win; opt-outs sync both ways
- [ ] Duplicate, failed and truncated deliveries rehearsed before launch
Common questions
Does Callaro have a native HubSpot or Salesforce connector?
No. The connection is generic: an API to start calls, a pre-call lookup, tools and webhooks. The trade-off is setup work. With neither a developer nor an automation tool, a platform with a native connector may suit you better.
Next step
Build the receiver first. Point the test webhook at it, break it on purpose, and only then connect a live agent. Then read the complete guide to AI voice agents, see how to design captured fields in voice AI for lead qualification, or check how Callaro fits your stack on the integrations page.