Summary
Data you can sync
Sync Aircall call logs and transcripts into Planhat as Conversations
Sync direction
One direction: from Aircall to Planhat
Sync frequency
Near instant, through webhooks
Who is this article for?
Anyone interested in syncing Aircall data to Planhat
Article contents
Introduction
π Important to note
This is a new (as of September 2025) Aircall integration - a version 2. It's a different (advanced) integration compared to the original Aircall integration (v1), as separately described here.
One of the key differences is that the new integration imports Aircall transcripts into Planhat.
The Planhat advanced integration with Aircall enables automatic logging of call data and transcripts, synced in as Conversations mapped to the relevant Company and End User(s) within Planhat. It:
Logs Aircall calls as Conversations
Logs Aircall transcripts as Conversations
Differentiates between inbound and outbound calls
This streamlines Sales and Support processes:
It ensures call data is automatically logged in Planhat
It makes it easy for you to track the performance/KPIs of your team members (Planhat Users) - e.g. you can see how many calls each User is making
It brings call data/transcripts into Planhat, which can be viewed alongside a wide variety of other data on your prospects/customers, giving you and your team a full 360-degree understanding with context
In this article, we show you what the Aircall (Advanced) integration looks like, describe the advantages it brings you, and explain how to set it up.
π Definitions
Planhat "data models" are equivalent to the "objects" you may be familiar with in other tools
"Company" is the Planhat model corresponding to organizations that are your prospects or customers
"End User" is the model corresponding to individual people at those Companies
"User" is the model corresponding to you and your colleagues (i.e. users of Planhat)
"Conversation" is the model corresponding to communications, such as emails, calls and meetings
"Records" are data items inside a model
For example, within the Company model, records could be "Apple", "Microsoft" and "Google"
What is the Aircall (Advanced) integration?
The Aircall (Advanced) integration is a "light" integration. It's actually a Custom Automation that you can build in the App Center of your Planhat tenant (if it doesn't already exist there) - we give setup instructions below, and you can speak with your Planhat Technical Deployment Specialist (TDS) if you need additional support. (Note that this is a difference from the older - and more basic - Aircall integration.)
How it works is that an Aircall webhook sends information to Planhat (when a call is made or received, or a call transcript is created), where a Custom Automation processes the data and saves it as Conversation records, with mapping to the appropriate Company, End User and User. As part of the Automation, a "connection" to Aircall in Planhat is used to fetch transcript details.
Here are some examples of Conversation records generated via the Aircall integration - a call made, and then a call transcript. As you can see, they include "Aircall" in the title, and are using the custom Conversation Type "Cold Call". Each Conversation shows the "Team Members" (Users), "Involved Contacts" (End Users), date/time, and a description of what happened with the call (e.g. whether it was answered). The transcript record also includes a link to the call recording.
(The gray boxes are where customer information has been hidden for the images.)
π Important to note
Because the Aircall (Advanced) integration is actually a Custom Automation, it's possible for its design to be customized if desired - so some details may vary.
This article will describe how to create our recommended setup.
π Important to note
In the standard Aircall Automation setup we describe in this article, you can potentially have 2 different Conversation records for a single call, as the Automation will be triggered by both the call being made, and then again by the transcript, creating a Conversation record each time. It's possible to change the Automation if you would like the first record (for "call made") to be updated with the transcript details when available; we mention this in further detail in the setup part of this article.
Background information - making Aircall calls
To make calls using Aircall directly from Planhat, put your mouse over a phone field on an End User record, and click on the arrow that appears (with the "Call" tooltip), as shown in the screenshots below.
From this, you can open the relevant application - in this case, Aircall. To be able to use Aircall in this way, you should first download and install the Aircall desktop application. There is also an Aircall Chrome extension.
π Important to note
You should include the country code in the phone number in Planhat (e.g. +1 for the United States), to align with Aircall.
Why use the Aircall (Advanced) integration?
With the Aircall (Advanced) integration, you ensure that all of your Aircall calls are logged in Planhat. This means:
You can view and analyze the performance of your team members, such as how many calls BDRs are having
You can view and analyze characteristics of calls as a group, identifying trends, such as whether outcomes have improved over time in response to a new sales strategy
Other teams in your Planhat tenant can view the details of calls with prospects/customers - e.g. if the Aircall integration imports a transcript with a Customer Support Specialist, the relevant Customer Success Manager (Account Owner) can see the call details for a greater understanding of their customer
Compared to the original Planhat Aircall integration, the main advantage of this new "Advanced" version is that transcripts are included in the synced information, rather than simply logging that the call has taken place.
Once the integration is set up, you can use Planhat's Pages to organize and analyze your Aircall Conversation records.
Click the image to view it enlarged
How to set up the Aircall (Advanced) integration
π Important to note
Depending on your package, you may already have this integration (Automation) in your Planhat tenant. If you don't and would like to add it, you can either follow these instructions to set it up yourself, or a Planhat Technical Deployment Specialist (TDS - formerly TAM) may help build it for you.
π Important to note
Remember that this article is specifically about the second, advanced Aircall integration, which is an Automation-based "light" integration. For instructions on the older Aircall integration, see here.
Preliminary steps in Planhat
Before you build the Automation, there are a few things to set up in your Planhat tenant:
Custom Conversation Type
Each Aircall call or transcript is saved as a Conversation record in Planhat. Each Conversation record has a corresponding Conversation Type
You can create your own custom Conversation Types in Planhat
In the Aircall Automation as shown in this article, we use a custom Conversation Type called "Cold Call" - so you should either create this following the instructions in the article linked to above (assuming you don't already have this particular Conversation Type); or if you want to use a different Conversation Type, you can do so, but note that this name is referenced in the Automation so you will need to take that into account when building your Automation
Custom Fields
Most of the fields that we use in this Automation are system (i.e. standard/default) fields, but ...
We save each Aircall call link in a custom field called "Call Recording" on the Conversation record
Again, assuming you want to copy the setup described in this article, you should create a field (type: URL) on the Conversation model called "Call Recording" (following the general instructions here)
If you use a different field - with a different name - then make a note of it when you're building the Automation, as it's referenced
The Automation references a custom field on the End User model called "Phone 2" (in two of the "Execute Function" steps)
... so if you would like to copy this Automation exactly, you should also add this field
Note that there is already a system (default/standard) field called "Phone 2" on the Company model; here we are specifically referring to the End User model
Having this field enables you to include an additional phone number on each End User record in Planhat
Connection
"Connections" are a feature in Planhat that you use to connect to third-party tools such as Aircall. You need to set up a connection in Planhat for Aircall, to use in the Automation
To do this:
Go to the "App Center" (one of the Global Tools for admins) by clicking on your tenant logo or name in the top left of your tenant
Click on "Connections" in the bottom left of the App Center
Click on "+ Connection" in the top right, and then click on "Custom connection" at the bottom of the dropdown list
Give your connection a name (e.g. "Aircall Advanced"), and change "Authentication" to "Basic"
This will open up the following form to fill in for your new connection:
In the "Authentication" section:
For the "Username" and "Password", enter your Aircall username and password. This should be the Aircall username and password of an Aircall user who has the permission to view calls and transcripts (e.g. an admin)
For "API Base URL*", enter
https://api.aircall.io/v1
In the "Endpoints" section, add the first endpoint:
Click "+ Add endpoint" to open up this modal to complete:
In "Action name", enter
Get TranscriptYou can leave "Action description" blank
In "URL path", enter
/calls/[[callId]]/transcriptionIn "HTTP Method", you can leave this as
GETClick "Add" in the bottom right; this will close the modal
Add a second endpoint:
Click "+ Add endpoint" to open up the modal to complete
In "Action name", enter
Get Call DetailsYou can leave "Action description" blank
In "URL path", enter
/calls/[[callId]]In "HTTP Method", you can leave this as
GETClick "Add" in the bottom right; this will close the modal
Click on "Save" in the top right to save your connection
Preliminary steps in Aircall
Webhook
π Important to note
To be able to use the transcription.created event, you need the Aircall AI package.
The Automation in Planhat is triggered when it receives an incoming webhook from Aircall. You need to set up the webhook within Aircall:
From the Aircall Dashboard, go to the "Integrations & API" page, and click on the "Webhook" integration
Click on the "Install" button
In the "integration name (custom)" field, give a name so you will be able to easily identify the webhook in the list - e.g. "Planhat"
In the "url" field, add in the URL from Planhat*
Ensure the toggle switches are enabled for the events you want to receive. For this integration/Automation, you will need:
call.createdtranscription.created
Click "Save"
*This is a URL unique to your Automation. To get it, you need to start building the Automation in Planhat (instructions below), and save it. Then, when you open it and select the incoming webhook trigger, you will see a "Copy URL" button, as shown in this screenshot:
You can read more about building Automations with webhooks (more generally) here, and see the instructions specifically for the Aircall Custom Automation below.
π Tip
Note that this Aircall (Advanced) integration uses different Aircall "events" than the original Aircall integration, which worked via the call.ended event.
Custom Automation in Planhat
Within Planhat, the Aircall (Advanced) "integration" is actually a Custom Automation, which you can build in your Planhat tenant, within the App Center. You can read about setting up Custom Automations generally here, and we'll link to more specific Custom Automation articles (e.g. about step types) at the appropriate times below. In this article, we will focus specifically on the details of the Aircall (Advanced) Automation.
Your overall Automation will look similar to this:
Click the image to view it enlarged
π Important to note
Each step of a Custom Automation (so that's after the trigger) is automatically assigned a random name such as "s-CKA" (as you can see in the branch step above). You can rename individual steps (e.g. to "Step 1"), as described here. This renaming can make things clearer in the UI. Note that if you then refer to that step in a subsequent step, you need to use its actual current step name, whether that's a random code name or an edited name. We will point out where you should rename steps in the instructions below.
To summarize the process of the Aircall (Advanced) Automation, shown in the screenshot above:
The trigger is a webhook received from Aircall (in response to either the
transcription.createdorcall.createdevents - as you set up in a preliminary step)"Step 1" is an "Execute Function" step that makes the data more readable for the next step
"Step 2" is another "Execute Function" step, and this one finds End Users (prospects/customers) via phone numbers
Next, we have a "Branch" step - this is used to identify whether the webhook is reporting a call created or a transcription created - there are then different subsequent steps depending on which of these is the case
In the longer branch, corresponding to a transcript being created, we begin with a "Connection" step (also called a "Use an integration" step). This uses the connection you set up in Planhat as a preliminary step - specifically the "Get Call Details" endpoint - to get the call details
Next, we have a short "Wait" step, to give slightly more time for the complete transcript to be available
The next step is to use the connection again, but this time the "Get Transcript" endpoint, to get the transcript details
After that, it's another "Execute Function" step, which looks up the End User's associated Company using their phone number, and formats the call transcript for the Conversation record
Finally, we have a "Call a webhook" step, where we create a Conversation record via the Planhat API
In the shorter branch, corresponding to a call (not a transcript), the only step is a "Call a webhook" step that communicates with the Planhat API to create a Conversation record with the relevant call data
π Tip
Note that, if you set up the Automation as described here, each branch creates a separate Conversation record (using "POST" in the Planhat API to create a Conversation - described here), meaning that in some cases you will end up with two records for a single call - this is by design.
If you would like to adapt/customize the Automation so that it updates the existing Conversation record if a transcript becomes available, then discuss with your Technical Deployment Specialist (TDS) about how this can be done (via the "PUT" API call).
Let's look at each of the Automation steps in further detail.
Trigger - incoming webhook
The trigger for this Automation is an incoming webhook from Aircall.
It's set to "anything" (as shown), but it's connected to Aircall by clicking "Copy URL" (which gives a unique URL for this Automation) and pasting that into Aircall, as we mentioned above.
Step 1 - function step
This "Execute Function" step processes the data received in the webhook, making the data more readable for the subsequent steps in the Automation.
π Important to note
When building your Automation, make sure you rename this step to "Step 1" (from the randomly generated code name), as we reference this step name later in the Automation.
You can copy and paste the following into your step:
var webhookData = <<update>>;
const participants = webhookData?.data?.participants || [];
const externals = participants.filter(p => p?.type === "external");
let externalNumbers = externals.map(e => e?.phone_number);
// Fallback: if no participants but we have raw_digits
if ((!externalNumbers || externalNumbers.length === 0) && webhookData?.data?.raw_digits) {
externalNumbers = [webhookData.data.raw_digits];
}
// β Final fallback: if still nothing, force default number
if (!externalNumbers || externalNumbers.length === 0) {
externalNumbers = ["4135678707"];
}
return {
event: webhookData?.event || null,
// β prefer call_id if present (transcription.created), otherwise use id
callId: webhookData?.data?.call_id || webhookData?.data?.id || null,
status: webhookData?.data?.status || null,
direction: webhookData?.data?.direction || null,
startedAt: webhookData?.data?.started_at || null,
endedAt: webhookData?.data?.ended_at || null,
recordingUrl: webhookData?.data?.recording || null,
ownerEmail: webhookData?.data?.user?.email || null,
ownerName: webhookData?.data?.user?.name || null,
externalNumbers
};
Step 2 - function step
This next "Execute Function" step finds End Users by phone numbers.
π Important to note
When building your Automation, make sure you rename this step to "Step 2" (from the randomly generated code name), as we reference this step name later in the Automation.
You can copy and paste the following into your step:
const m = <<Step 1>>;
if (!m) return "No call";
// ---- Helpers ----
const normalizePhone = (p) =>
(p || "")
.replace(/\D/g, "")
.replace(/^1/, "");
async function findEnduserByPhone(phone) {
const target = normalizePhone(phone);
let offset = 0;
const limit = 2000;
while (true) {
const batch = await ph.models.endUsers.getAll({ limit, offset });
if (!batch.length) break;
const match = batch.find(eu => {
const candidates = [
eu.phone,
eu.custom ? eu.custom["Phone 2"] : null
]
.filter(Boolean)
.map(normalizePhone);
return candidates.includes(target);
});
if (match) return match;
if (batch.length < limit) break;
offset += limit;
}
return null;
}
// ---- Team Members ----
let users = [];
if (m?.ownerEmail) users.push({ email: m.ownerEmail });
users = users.filter((u, i, arr) => u?.email && i === arr.findIndex(v => v.email === u.email));
// ---- End Users ----
let endusers = [];
let companyId = null;
let contactId = null;
if (m?.externalNumbers?.length) {
let match = await findEnduserByPhone(m.externalNumbers[0]);
// β fallback: if no match, force lookup on 4135678707
if (!match) {
match = await findEnduserByPhone("4135678707");
}
if (match) {
endusers.push({ email: match.email });
companyId = match.companyId;
contactId = match.id;
}
}
endusers = endusers.filter(
(e, i, arr) => e?.email && i === arr.findIndex(v => v.email === e.email)
);
// ---- Handle events ----
if (m.event === "call.created" || m.event === "call.answered") {
let subject;
if (m.event === "call.created") {
if (m.direction === "inbound") {
subject = `Aircall β inbound call from ${endusers[0]?.email || m.externalNumbers?.[0] || "Unknown"}`;
} else {
subject = `Aircall β call made to ${endusers[0]?.email || m.externalNumbers?.[0] || "Unknown"}`;
}
} else {
subject = `Aircall β call answered with ${endusers[0]?.email || m.externalNumbers?.[0] || "Unknown"}`;
}
const description =
m.event === "call.created"
? "Call attempt logged."
: "Call was answered.";
return {
subject,
type: "Cold Call",
externalId: `${m.callId}-cold call`,
companyId,
contactId,
endusers,
users,
custom: { "Call Recording": m?.recordingUrl || null },
description,
date: m?.endedAt || m?.startedAt || new Date().toISOString(),
callId: m.callId
};
}
// ---- Case: transcription.created ----
if (m.event === "transcription.created") {
return {
type: "transcription.created",
callId: m.callId || null,
companyId,
contactId,
endusers,
users,
externalNumbers: m.externalNumbers || []
};
}
Branch step
The next step is a branch. It looks at Step 1, and assesses whether the incoming webhook from Aircall was for a call created or a transcription created. Based on this, the subsequent steps are different (although in both cases, the final step will be a Conversation record being created), as we will go through next.
Click the image to view it enlarged
Left-hand side (longer branch) - condition 1 - transcription created
π Tip
In practice, for a call that is answered and ends up with a transcript, what will happen is that the right-hand side (shorter branch) will be triggered first - when the call is created - and then the left-hand side (longer branch) will be triggered when the transcript is created in Aircall. However, in this article we will describe the transcript branch first.
First connection step
This is a "Connection" - also called a "Use an integration" - step. It uses the connection you set up as a preliminary step in Planhat - specifically the "Get Call Details" endpoint.
Click the image to view it enlarged
Wait step
The next step - a Wait step - schedules a brief pause in the Automation run, to give slightly more time for the full transcript to be available in Aircall.
Second connection step
This is another "Use a connection" type step, and again you use the connection you set up earlier - but this time, the "Get Transcript" endpoint.
Third function step
We next have another "Execute Function" step.
This one processes the information received from Aircall in the previous two connection steps, and prepares a payload that's then used in the next step, when we will create a Conversation record via the Planhat API.
As part of this process, it identifies the relevant Company (to link the Conversation record to).
π Important to note
When building your Automation, make sure you rename this step to "CleanOutput" (from the randomly generated code name), as we reference this step name later in the Automation.
Click the image to view it enlarged
You can copy and paste the following into your step:
// Inputs
const callData = <<Get Call Details>>.call; // note: unwrap .call
const transcriptData = <<Get Transcript>>;
// ---- Helpers ----
const normalizePhone = (p) =>
(p || "").replace(/\D/g, "").replace(/^1/, "");
async function findEnduserByPhone(phone) {
const target = normalizePhone(phone);
let offset = 0;
const limit = 2000;
while (true) {
const batch = await ph.models.endUsers.getAll({ limit, offset });
if (!batch.length) break;
const match = batch.find(eu => {
const candidates = [
normalizePhone(eu.phone),
normalizePhone(eu.custom?.["Phone 2"])
].filter(Boolean);
return candidates.includes(target);
});
if (match) return match;
if (batch.length < limit) break;
offset += limit;
}
return null;
}
// ---- Team Members ----
let users = [];
if (callData?.user?.email) users.push({ email: callData.user.email });
users = users.filter((u, i, arr) => u?.email && i === arr.findIndex(v => v.email === u.email));
// ---- End Users ----
let endusers = [];
let companyId = null;
let contactId = null;
// β use raw_digits as external number
const extNum = callData?.raw_digits || null;
if (extNum) {
const match = await findEnduserByPhone(extNum);
if (match) {
endusers.push({ email: match.email });
companyId = match.companyId;
contactId = match.id;
}
}
endusers = endusers.filter(
(e, i, arr) => e?.email && i === arr.findIndex(v => v.email === e.email)
);
// ---- Guard: skip if still no company match ----
if (!companyId) {
return {
skip: true,
reason: `No company found for number ${extNum}`
};
}
// ---- Transcript handling ----
if (!transcriptData?.transcription?.content?.utterances?.length) {
return {
subject: callData?.subject || "Aircall β call",
type: "Cold Call",
externalId: `${callData?.id || callData?.callId}-notranscript`,
description: "Transcript not available yet.",
date: callData?.ended_at || callData?.started_at || new Date().toISOString(),
companyId,
contactId,
endusers,
users,
url: callData?.recording || null
};
}
const utterances = transcriptData.transcription.content.utterances
.map(u => {
const speaker =
u.participant_type === "internal"
? "Agent"
: (u.phone_number || "External");
return `<p><strong>${speaker}:</strong> ${u.text}</p>`;
})
.join("");
// ---- Subject w/ direction ----
let subject;
if (callData?.direction === "inbound") {
subject = `Aircall β inbound call transcript from ${endusers[0]?.email || extNum || "Unknown"}`;
} else {
subject = `Aircall β outbound call transcript with ${endusers[0]?.email || extNum || "Unknown"}`;
}
// ---- Final payload ----
return {
subject,
type: "Cold Call",
externalId: `${callData?.id || callData?.callId}-transcript`,
description: `
<p>Call was answered.</p>
<p><strong>Transcript:</strong></p>
${utterances}
`,
date: callData?.ended_at || callData?.started_at || new Date().toISOString(),
companyId,
contactId,
endusers,
users,
url: callData?.asset || callData?.recording || null
};
"Call a webhook" step
This step creates a Planhat Conversation record of type "Cold Call" (the custom Conversation Type you created in a preliminary step in Planhat). You can see in the screenshot below the fields that are populated, including the custom field "Call Recording" that was created earlier too.
π Tip
This step uses the "POST" API call for Conversations, so it will create a new Conversation record for the transcript (even though it's expected that there will already have been a Conversation record created for the call logging itself). This is by design.
If you would like to instead use "PUT" to update Conversations, please discuss with your Technical Deployment Specialist (TDS) about how the Automation could be adapted.
π Important to note
The URL shown in the screenshot below is https://api.planhatdemo.com/conversations because this has been set up in a demo tenant (on the demo cluster), as reflected in the URL of the tenant itself (starting https://ws.planhatdemo.com/).
Presuming you are setting this Automation up in a tenant that is not on the demo cluster, you should use https://api.planhat.com/conversations (as stated in the API documentation).
Click the image to view it enlarged
You can copy and paste the following into your step:
{
"subject": "<<CleanOutput.subject>>",
"type": "Cold Call",
"externalId": "<<CleanOutput.externalId>>",
"companyId": "<<CleanOutput.companyId>>",
"contactId": "<<CleanOutput.contactId>>",
"endusers": "<<CleanOutput.endusers>>",
"users": "<<CleanOutput.users>>",
"custom": {
"Call Recording": "<<CleanOutput.url>>"
},
"description": "<<CleanOutput.description>>",
"date": "<<CleanOutput.date>>"
}
Right-hand side (shorter branch) - condition 2 - call created
π Tip
This is the branch that will run for all calls made ("created" in Aircall), regardless of whether or not the call is answered and there is a transcript.
Compared to the longer branch you've just seen, involving fetching and processing the transcript, this is very quick and simple!
Another "Call a webhook" step
In this other branch, which runs if the webhook from Aircall triggering the Automation corresponds to a call being created, we simply have a "Call a webhook" step to create a Conversation record, again using the custom "Cold Call" Conversation Type and the custom "Call Recording" field amongst others, as you've just seen for the webhook step at the end of the other branch.
You will note that this step includes reference to Step 2, which you created/renamed previously. It creates the Conversation record by using "POST" in the Planhat API.
π Important to note
The URL shown in the screenshot below is https://api.planhatdemo.com/conversations because this has been set up in a demo tenant (on the demo cluster), as reflected in the URL of the tenant itself (starting https://ws.planhatdemo.com/).
Presuming you are setting this Automation up in a tenant that is not on the demo cluster, you should use https://api.planhat.com/conversations (as stated in the API documentation).
Click the image to view it enlarged
You can copy and paste the following into your step:
{
"subject": "<<Step 2.subject>>",
"type": "Cold Call",
"externalId": "<<Step 2.externalId>>",
"companyId": "<<Step 2.companyId>>",
"contactId": "<<Step 2.contactId>>",
"endusers": "<<Step 2.endusers>>",
"users": "<<Step 2.users>>",
"custom": {
"Call Recording": "<<Step 2.url>>"
},
"description": "<<Step 2.description>>",
"date": "<<Step 2.date>>"
}

























