Set up an Outbound AI Voice Agent that qualifies leads created in HubSpot and updates them automatically using HubSpot AI Actions. This article walks through creating the agent, connecting it to HubSpot, and building the workflow that triggers the outbound call.
Note: Throughout this article, we'll use an example based on ACME, a SaaS company with an inbound lead form on their website, powered by HubSpot. Rather than manually calling and qualifying every new lead, ACME wants an AI Voice Agent to do this automatically. Larger accounts are transferred to sales reps in real time, while smaller accounts are qualified by the agent and logged for the sales team to follow up on. The agent also retrieves the lead's HubSpot Contact record and updates the lifecycle stage field to reflect that qualification has been completed.
By implementing this configuration, you can qualify leads the moment they come in, ensuring hot leads reach your sales reps faster while they're still warm. This speeds up your sales cycle, improves lead-to-demo conversion rates, and keeps your pipeline cleaner by filtering out unqualified leads before they reach your reps, freeing your team to focus on selling rather than chasing down and qualifying every inbound lead manually.
How it works
This setup can be adapted to suit virtually any scenario, but here's what a typical call flow looks like for the ACME example:
- A new lead submits a form on the website, powered by HubSpot, creating a Contact record.
- The form submission triggers a HubSpot workflow, enrolling the Contact record and launching an outbound call via the AI Voice Agent.
- The agent connects with the lead, triggers the Get Contact action to retrieve the Contact record, and begins qualification, referencing contact fields as
{{variable}}context throughout the conversation, including how many licenses the lead requires. - If the lead has 15 or more users, the agent triggers the Update Contact action to update the lifecycle stage property, then immediately transfers the call to a live sales rep.
- If the lead has 14 or fewer users, the agent triggers the Update Contact action to update the lifecycle stage property, then informs the caller that the sales team will be in touch shortly before politely ending the call.
Before you begin
This guide assumes the following are already in place:
- You have an active HubSpot account with a lead capture web form in place.
- Form submission triggers the creation of a Contact record in HubSpot and populates the standard fields firstname, lastname, phone, and email on the Contact object.
- The lifecycle stage property exists on the Contact object and is available to update via the Update Contact AI Action.
- You have an active HubSpot integration on the Aircall Dashboard.
- The contact_phone field must be in E.164 format (for example, +14155551234).
Step 1: Create your Aircall API key and Basic Auth token
Before creating your AI Voice Agent, generate an API key in Aircall. HubSpot's workflow will use this to authenticate its request when triggering the outbound call. Since HubSpot expects credentials as a Basic Auth token rather than a raw API ID and token pair, you'll need to combine and encode them first.
Steps
- On the Aircall Dashboard, go to Integrations > API keys from the left-hand navigation menu.
- Click Generate an API key and give the new key a name.
- Copy your API ID and API token and combine them as a single string:
API_ID:API_Token(no spaces, colon separator). -
Base64-encode the result using one of the following methods:
Browser tool: paste the combined string into a Base64 encoding tool.
Terminal (macOS/Linux):
echo -n "YOUR_API_ID:YOUR_API_TOKEN" | base64Windows PowerShell:
[Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("YOUR_API_ID:YOUR_API_TOKEN")) - The result is a string like
YWJjMTIzOnRlc3R0b2tlbg==. Copy it and store it somewhere safe, you'll need it in Step 5.
Step 2: Activate AI Actions
Enable AI Actions on the specific HubSpot integration you want to connect your AI Voice Agent to. For the ACME example, this means enabling Get Contact and Update Contact.
Steps
- Return to the API landing page on the Aircall Dashboard.
- Find and select your existing HubSpot integration.
- Click the AI Actions tab in the top navigation bar.
- Enable Update Contact and Get Contact under AI Voice Agent.
Step 3: Create and configure your AI Voice Agent
Create your agent.
Steps
- Navigate to AI Agents > AI Voice Agents from the left-hand navigation menu.
- Click Create an AI Voice Agent and select Outbound.
Once the agent is created, it needs to be configured. The sections below follow the order they appear in the left-hand navigation menu of the AI agent editor and explain the role each one plays in the ACME use case.
For more help with these configuration steps, refer to Configuring your AI Voice Agent.
Number assignment, agent profile, and company information
- Number assignment: the phone number the agent will make the call on when triggered.
- Agent profile: sets the agent's core identity.
- Company information: defines how the agent refers to your business.
Knowledge base
The knowledge base is where you provide the information the agent uses to answer caller questions.
Tip: For this use case, add a concise knowledge source in case the prospect has questions about your company. For best performance, avoid trying to cover everything, stick to your most frequently asked questions on the topics most relevant at this stage of the sales cycle. Overloading the knowledge base can slow down responses and reduce accuracy.
Call script: greeting and farewell messages
The greeting and farewell messages define what callers hear at the start and end of a call.
The agent can personalize its greeting using dynamic variables populated from your HubSpot Contact record. Whatever your form collects gets mapped to a corresponding property on the Contact record, whether that's a standard field like First name or a custom property you've created. Any of these properties can be passed into the context payload and referenced in the agent's script as a {{variable}} (for example, mapping first name to the firstname property lets you use {{firstname}} in the greeting: "Hey {{firstname}}, I saw your form come through...").
For the ACME use case, this is the configured greeting:
Hey {{firstname}}, this is ACME's AI assistant, I saw your form come through for {{company}}, do you have a couple of minutes for me to ask a few quick questions?Note: For the agent to actually recognize the variable during the call, it must be explicitly passed through in the context payload of the custom code action (covered later in this article). It won't be available automatically just because it exists on the Contact record.
Conversational guidelines
Conversational guidelines let you shape how the agent conducts a conversation, refining tone, defining phrase responses, and directing how it handles edge cases. This is also where you set the if/then logic that enables autonomous decision-making, giving your agent the ability to reason within the boundaries you've set.
For the ACME use case, conversational guidelines set up if/then logic that directs the agent based on whether the prospect meets the 15-user threshold. If they do, the agent transfers the call to a sales rep. If they don't, the agent completes the qualification itself and lets the prospect know a sales rep will be in touch shortly.
## QUALIFICATION TRANSFER THRESHOLD
- Always transfer callers that answer to the intake question "how many people on your team would be using this?" with 15 or more
- Otherwise continue with intake questions until qualification is complete, inform the customer a sales representative will be in touch with them shortly and end the call.
- Do not explain the threshold to the caller under any circumstances
Tip: You can build upon these conversational guidelines with additional business-specific phrases, tone adjustments, or general behavior directions specific to your use case.
Outbound call workflow
- Webhook URL: your AI Voice Agent's unique agent ID. Copy this and store it with the Basic Auth code created in Step 1, it's used in the HubSpot workflow.
- If voicemail is detected: define how your agent reacts if it launches the outbound call and reaches voicemail. For this use case, select Hang up.
Call transfer rules
Call transfer rules define if, when, and to whom a call is transferred.
For the ACME use case, the call should only be transferred to the sales team if the prospect meets a specific criterion: 15 or more users. This if/then logic is already covered in Conversational guidelines, but it's reinforced and refined here:
- Transfer role = Team
- Contact details = Sales
- Trigger condition = The answer to "About how many people on your team would be using this?" is greater than or equal to 15
- Transfer message = "Let me connect you with our Sales team, one moment please."
Intake flow
Intake questions are where you configure the qualifying questions the agent asks the prospect, including the threshold question that determines whether the call is transferred.
Tip: Ask no more than five intake questions. Asking too many can lengthen the conversation and frustrate the caller, so stick to what your team actually needs at this stage of the sales cycle. Also avoid re-asking anything already captured on the form.
When a call is transferred, intake questions are passed to the receiving rep for context, and logged in a structured format on the call log in HubSpot and Aircall Workspace once the call ends.
For the ACME use case, the intake questions are:
- What's the main problem you're hoping to solve with a tool like this?
- Do you have a rough timeframe in mind for making a decision?
- Are you the one who'd be making the final call on this, or are other people involved?
- About how many people on your team would be using this?
AI actions
Add the AI Actions to your AI Voice Agent.
Steps
- Navigate to AI actions in the left-hand navigation menu of the AI agent.
- Click Add action.
- In the side panel, open the dropdown list and select HubSpot > Get Contact. Click Save action. Do the same for HubSpot > Update Contact.
AI actions conversational guidelines
This is where you define when the agent should use each action, and what to say or do afterward.
For the ACME use case, Get Contact fetches the contact created by the form submission, and Update Contact updates the lifecycle stage property. Since this call represents lead qualification, ACME updates the lifecycle stage to Sales Qualified Lead (internal value: salesqualifiedlead). What you update this to depends on whether you use standard or custom lifecycle stages, and what point in the sales cycle this call represents. Be sure to replace salesqualifiedlead with the value relevant to your own use case.
##Get Contact
- Once the caller confirms it is okay to ask them questions, use 'Get Contact' caller's {{phone}} number from context
- Ask "Just to confirm, {{phone}} is the phone number we have on record for you?" and confirm yes or no. Read it in E164 format
- If YES - identity confirmed. Store the contactId for use in later actions and move on to the intake questions.
- If NO - ask caller for their email address and try 'Get Contact' again. If record is found, identity confirmed. Store the contactId for use in later actions and move on to the intake questions.
- If still unable to retrieve contact record, apologise and say to the caller "I am unable to retrieve your contact record in our system. Someone from our sales team will be in touch as soon as possible using the information you provided on your form" and end the call.
##Update Contact
- Once the caller has answered the qualifying question (number of users), use 'Update Contact' with the contactId stored earlier to update lifecyclestage to "salesqualifiedlead".
- Update Contact should always fire before the call branches into a transfer or sign-off, so the lifecycle stage is updated regardless of outcome.
Step 4: Test your lead qualification agent
Once everything is configured, and before proceeding to the HubSpot setup, test your agent thoroughly using the Test call panel on the right-hand side of the agent editor. This simulates a real call experience without firing any call activity to your team or triggering activity in HubSpot.
Run through three scenarios to confirm the agent behaves as expected:
- Happy path: call in as a lead with 15 or more users and confirm the agent will transfer you to a sales rep (the transfer won't actually happen, since it's a simulated call). Then call in as a lead with fewer than 15 users and confirm the agent completes qualification itself and lets you know a sales rep will be in touch.
- Fuzzy path: give a vague or uncertain answer when asked about your number of users (for example, "not sure yet") and see how the agent handles the ambiguity, whether it asks a clarifying question or makes an assumption.
- Unhappy path: push the agent with challenging conditions, like interrupting mid-response, giving contradictory answers about user numbers, or asking it to do something outside its scope (for example, "just book me a demo anyway"). Confirm it handles these gracefully rather than giving incorrect information.
If you encounter unexpected results, identify the issue, make the necessary adjustments, and repeat until you're confident the agent is ready to qualify leads.
Step 5: Create your HubSpot workflow and custom code action
Next, configure things on the HubSpot side by creating a workflow that will trigger the outbound AI Voice Agent call.
Steps
- In HubSpot, navigate to Automation > Workflows.
- Click Create workflow > From scratch.
Enrollment trigger
For the enrollment trigger, use two options: manual enrollment, a handy testing tool that lets you run the workflow without submitting the actual lead form each time, and the real-world condition prospects will meet, submitting the specific lead form.
Steps
- On the enrollment trigger, select Trigger manually > Contact > Save & continue.
- Click again on the enrollment trigger and under OR, select When an event occurs.
- In Add criteria, search for and select Form submission.
- Form submission has been completed any number of times anytime will be added by default.
- Click + Add criteria and select Form name.
- Set Form name = is any of = [your lead capture form].
- Click Save.
Re-enrollment
Note: Keep re-enrollment off for this workflow, since each lead should only be qualified once per form submission. Turning it on could trigger duplicate outbound calls if someone submits the form more than once. If you want to handle no-answer calls, Aircall logs the call outcome back to HubSpot, so consider a separate follow-up workflow that uses this to re-trigger the call after a delay, rather than relying on re-enrollment here.
Custom code action
Steps
- Under your enrollment trigger, click + to add an action.
- Search for Custom code and select it.
- Choose Node.js 18.x as the language.
- Under Secrets, click Choose a secret > Add new secret.
- Secret name:
AIRCALL_BASIC_AUTH(ensure there are no spaces). - Secret value: paste the Base64 string from Step 1.
- Save the secret and return to the action.
- Back in the action, select
AIRCALL_BASIC_AUTHfrom the dropdown, it will appear as a chip.
Property to include in code
Click Add property for each field the AI Voice Agent needs to qualify and personalize the call. For each one, select the HubSpot property to pull the data from, and assign it a variable name, the name you'll use to reference that value in the code (for example, event.inputFields['firstname']).
Whatever {{variable}} you've already used in the agent's greeting and conversational guidelines must be included here with a matching variable name, otherwise the agent won't have access to it during the call.
For the ACME lead qualification use case, and for most standard qualification use cases, keep this list lean, the agent only needs enough context to personalize the greeting and confirm the caller's identity:
| HubSpot property | Variable name | Why we need it |
|---|---|---|
| First name (firstname) | firstname | Personalizes the agent's greeting |
| Last name (lastname) | lastname | Personalizes the agent's greeting |
| Phone number (phone) | phone | Confirmed with the caller via Get Contact |
| Company name (company) | company | Personalizes the agent's script |
| Email (email) | Fallback identifier if phone isn't confirmed | |
| Record ID (hs_object_id) | hs_object_id | Used to prevent duplicate calls and identify the record for CRM actions |
Note: Record ID (hs_object_id) is HubSpot's internal numeric ID for the contact record. It's used as the idempotency_key to prevent duplicate calls, and as the hubspot_contact_id context variable so the agent's Get Contact and Update Contact actions reference the correct record throughout the call.
Pasting the code
Select all in the code editor and replace it with the template below. After pasting, update the following placeholders:
| Placeholder | Replace with |
|---|---|
| YOUR_AGENT_ID | The AI agent ID from the webhook URL you copied in Step 3. (for example, 01KS46D6J80BCRWG5KCBHJVKSC) |
| YOUR_PREFIX | A short identifier for this workflow (for example, acme-qualification) |
const axios = require('axios');
exports.main = async (event, callback) => {
// Pull contact fields from HubSpot
const firstname = event.inputFields['firstname'];
const lastname = event.inputFields['lastname'];
const phone = event.inputFields['phone'];
const company = event.inputFields['company'];
const email = event.inputFields['email'];
const hs_contact_id = event.inputFields['hs_object_id'];
// Auth and endpoint
const basicAuth = process.env.AIRCALL_BASIC_AUTH;
const agentUrl = 'https://api.aircall.io/v1/outbound-calls/agents/YOUR_AGENT_ID';
try {
const response = await axios.post(
agentUrl,
{
contact_phone: phone,
idempotency_key: `acme-qualification-${hs_contact_id}`, // unique per workflow + contact
context: {
firstname: firstname,
lastname: lastname,
company: company,
email: email,
hubspot_contact_id: String(hs_contact_id)
},
expiration_seconds: 600 // call attempt expires after 10 minutes if it can't connect
},
{
headers: {
Authorization: `Basic ${basicAuth}`,
'Content-Type': 'application/json'
}
}
);
callback({ outputFields: { aircall_status: String(response.status), aircall_message: 'Call triggered successfully' } });
} catch (error) {
const status = error.response?.status || 500;
const body = JSON.stringify(error.response?.data) || 'No response body';
callback({ outputFields: { aircall_status: String(status), aircall_message: `Error: ${error.message} | ${body}` } });
}
};
Important: The event.inputFields and context payload rows in the code must match the variable fields you configured under Property to include in code, which in turn should match the variables used in your AI Voice Agent's greeting and conversational guidelines.
Data outputs
Under Data outputs, add two outputs. These don't affect whether the call fires, they surface in HubSpot's workflow history for debugging.
- Output name =
aircall_status. Type = string. - Output name =
aircall_message. Type = string.
Your completed workflow should look like this:
Step 6: Test your custom code action and workflow
Before turning this workflow on for real leads, test it end-to-end. This confirms three things: that HubSpot can successfully authenticate with Aircall, that the outbound call actually fires, and that the agent receives the correct context to run the conversation as intended. Testing now, on a dummy contact, means you catch authentication issues, formatting errors, or mismatched variables before they affect a real prospect.
Steps
- In the Custom code action, click Test.
- Select a contact record with a phone number, use yourself or a dummy internal contact for this first test, not a real prospect.
- Check the Data outputs section after the run.
-
aircall_status= 200 means success. The agent will call your test contact's phone number and run through the script. - If the status isn't 200, here are common errors and how to fix them before your next test:
| Error | Root cause | Fix |
|---|---|---|
| 409, outbound agent has no number | No phone number assigned to the agent in Aircall | Dashboard > AI Voice Agents > [Agent] > Connect number |
| 401, unauthorized | Wrong or expired Basic Auth token | Regenerate `ID:TOKEN |
| 400, contact_phone invalid | Phone number not in E.164 format | Normalize the phone property in HubSpot |
| 404, not found | Agent ID in the URL doesn't exist in the account | Copy the webhook URL fresh from Dashboard > AI Voice Agents > [Agent] > Outbound call workflow |
| Call fires but agent has no context | Context keys in code don't match {{variable}} names in the script | Check case and spelling, for example firstname in code must match {{firstname}} in the script |
| Duplicate calls on the same contact | idempotency_key isn't unique | Make sure idempotency_key includes both the workflow prefix and hs_contact_id |
Workflow activation
Once testing is complete, click Review and turn on. When prompted "Do you want to enroll contacts that currently meet the enrollment criteria," select No, only enroll contacts who meet the enrollment criteria after the workflow is turned on.
Your workflow and AI Voice Agent are now live.
Best practices
- Keep the knowledge base lean and focused on questions most relevant at this stage of the sales cycle, rather than trying to cover everything.
- Limit intake questions to five or fewer, and avoid re-asking anything already captured on the lead form.
- Keep re-enrollment off so each lead is only qualified once per form submission.
- Test the AI Voice Agent using the Test call panel before moving on to the HubSpot setup, then test the full HubSpot workflow end-to-end on a dummy contact before activating it for real leads.
- Make sure every
{{variable}}used in the agent's greeting and conversational guidelines is also configured under Property to include in code, with matching variable names. - Ensure the idempotency_key includes both the workflow prefix and the contact's record ID, to prevent duplicate outbound calls.
FAQs
Can the AI Voice Agent update HubSpot records without transferring the call?
Yes. The Update Contact action fires before the call branches into a transfer or sign-off, so the lifecycle stage is updated regardless of the outcome.
What happens if the AI Voice Agent can't confirm the caller's identity?
It asks for the caller's email and tries Get Contact again. If the contact record still can't be retrieved, it apologizes, lets the caller know a sales team member will follow up using the information from their form, and ends the call.
Why does the phone number need to be in E.164 format?
The contact_phone field must be in E.164 format (for example, +14155551234) for the outbound call to fire correctly. If this isn't the case, the workflow will return a 400 error, and you'll need to normalize the phone property in HubSpot.
Why should re-enrollment stay off for this workflow?
Turning re-enrollment on could trigger duplicate outbound calls if a lead submits the form more than once. If you need to handle no-answer calls, use a separate follow-up workflow based on the call outcome logged back to HubSpot, rather than relying on re-enrollment.
What if I want to change the number of users required to transfer to sales?
Update the trigger condition in both the Conversational guidelines and the Call transfer rules to reflect your own threshold, so the agent's behavior and the transfer logic stay aligned.
Can I test the workflow without submitting the actual lead form?
Yes. Use the manual enrollment option on the enrollment trigger, this lets you run the workflow without submitting the form each time you want to test it.