Connect Heilo to your CRM
Eight steps to connect Heilo to any CRM — using Zapier, Make, or any other tool that supports webhooks.
After every call, Heilo sends a notification with the call details to an address you choose. This guide shows how to connect Heilo to any CRM using Zapier or Make — no code, in 15–20 minutes.
It’s a universal path that works with any CRM supported by Zapier or Make. Dedicated step-by-step recipes for Pipedrive, HubSpot, and Zoho CRM are included below. One-click Zapier/Make templates are still on the roadmap — until then, each recipe walks you through the last step (saving to your CRM) manually.
1Before you start: what the Zapier and Make plans require
The “Webhooks by Zapier” trigger is a premium app and does not run on the Free plan: Free gives 100 tasks a month and two-step Zaps, meaning one trigger and one action. The recipe in this guide has at least three actions after the trigger, so you need Professional or higher; Professional starts at $19.99/month billed annually, or $29.99/month billed monthly, for 750 tasks. Only a successful action spends a task — the trigger, Filter and Formatter steps do not count. Zapier plans and pricing
Make puts no plan gate in front of webhooks — the “Custom webhook” module runs on the Free plan too, which gives up to 1,000 credits a month and two active scenarios. The billing unit in Make is the credit, not the operation; one module run is one credit, and the trigger module counts as well. Do not convert that into a number of calls: after the first live event, open the scenario history and read the credits that one run consumed. Make plans and pricing
We checked the providers' prices and limits on their own pages on 9 September 2026. They change without us, so confirm them at the links above before you decide.
2Create a new Zap in Zapier
The “Webhooks by Zapier (Catch Hook)” trigger generates an address that Heilo will send events to.
- Sign in to your Zapier dashboard
- Create Zap → Choose Trigger → “Webhooks by Zapier”
- Event = “Catch Hook” → Continue
- Zapier shows an address like https://hooks.zapier.com/hooks/catch/… — copy it
3Paste the address into Heilo as a webhook subscription
Heilo verifies the address — your Zapier endpoint responds automatically. After activation, every call is sent to this address.
- Go back to Settings → Integrations in Heilo
- Click “Add subscription”
- Paste the Zapier address into the “Endpoint URL” field, choose the event types (completed call + outbound call attempt) and click “Create + handshake”
- The status should change to “Active”. We show the signing secret exactly once — save it now, even if you are staying on the basic variant for the moment, because without it you cannot check the signature later
4Send a test event from Heilo
The test event carries exactly the structure of a real completed call, so a mapping built on it keeps working afterwards. One caveat: the recording link in this event is an example, not a file — opening it does not prove that real audio will be reachable.
- In Heilo, click the “Test” icon next to your subscription
- The event goes through the same delivery queue as any other — if the receiver refuses it, you will see that in Heilo's delivery history
5Read the full data structure
Zapier only offers fields it has actually seen. That is why you read the structure after sending the test rather than before — otherwise half the fields never appear in the list.
- In Zapier go back to the trigger step and run “Test trigger”
- Check that you can see the call data: caller number, name, summary and the recording link
- Continue → move on to the action step (your CRM)
6Set up the save in your CRM
In your tool (Zapier or Make) add actions for your CRM — for example Pipedrive, Zoho CRM, Notion or Airtable. For sales workflows this is usually three steps: (1) find or create a contact by phone number, (2) create or update a lead/deal, (3) attach an activity, task or note.
Before you publish the automation, put a filter on the event_type field. Let the business type you chose through and reject webhook.test and the address confirmation — without that your own tests will create records in the CRM. You may let the test through while mapping; you have to cut it off before publishing. Where exactly the filter goes for each of the three recipes is in the recipes section below.
| Heilo field | Map to CRM |
|---|---|
| data.call_id | ID / reference field |
| data.customer_phone_e164 | Contact phone (“Phone” / “Primary Phone”) |
| data.transcript_processed.caller_name | Contact name (if Heilo detected it) |
| data.transcript_processed.summary | Note body or activity description |
| data.transcript_processed.service_needed | Activity subject or deal title |
| data.transcript_processed.subject | Lead/deal title (best fallback for service_needed) |
| data.transcript_processed.lead_score | Priority, label or qualification filter |
| data.transcript_processed.preferred_date | Follow-up due date or expected close date |
| data.transcript_processed.client_city | Contact city / address field |
| data.transcript_processed.client_address | Contact street address |
| data.transcript_processed.services_match | Whether the request matches your offer (true/false) — for qualification |
| data.transcript_processed.additional_details | Extra details from the call → note / description |
| data.recording_url | A URL field (e.g. “Recording link”) |
| data.duration | Activity duration (in seconds) |
The data.outbound_lifecycle field appears only in call.outbound.attempted and call.failed events — it is not present in call.completed.
7Publish the automation
Until now everything happened in edit mode and nothing reacted to real calls. Turn the automation on in your tool — in Zapier you publish the Zap, in Make you switch the scenario on. Before you do, make sure the filter from the previous step is saved: publishing without it means the next test event will create something in the CRM as well.
8Check a real call in your CRM
The last step is not a formality. A test event proves the wire is connected, not that the record landed where it belongs. Take or make one real call, wait for it to finish, and open the contact in your CRM. Check three things: the activity exists and there is exactly one of it, it carries the Heilo Call ID, and the summary matches what was actually said. Only that is the integration signed off.
Recipes for specific CRMs
Below are CRM-specific configurations. The generic webhook gives you the call data; this section shows how to turn it into a contact, lead/deal and activity in a specific CRM.
Pipedrive: contact + lead/deal + activity
Target outcome: after a completed call, Pipedrive finds or creates a person, creates a sales lead/deal and attaches an activity with the summary and recording link.
Use Lead if calls should first land in Leads Inbox for qualification. Use Deal if every qualified call should enter the pipeline immediately.
Zapier
- Trigger: Webhooks by Zapier → Catch Hook. Keep the Heilo subscription in permissive mode.
- Filter by Zapier: allow event_type = call.completed; during setup also allow webhook.test (or filter by data._test = true).
- Pipedrive → Find or Create Person. Search by Phone Numbers = data.customer_phone_e164, and set Person Name from caller_name or the phone number.
- Pipedrive → Search Activities on your Heilo Call ID field = data.call_id. This step decides whether you update or create below; without it a retry adds a second activity for the same call.
- Activity found → Update Activity; nothing found → Create Activity attached to the person. In both cases write Heilo Call ID = data.call_id plus summary, recording_url and transcript_original. No deal is created here — the rule for opportunities is in its own section below.
Make
- Trigger: Webhooks → Custom webhook. The scenario must be active and run when data arrives.
- Add a filter after the webhook: event_type = call.completed; while mapping test data, also allow webhook.test (or filter by data._test = true).
- Pipedrive CRM → Search Persons by data.customer_phone_e164. If there is no result, create a Person with that phone and caller_name or the phone number as the name.
- Pipedrive CRM → Search Activities on your Heilo Call ID field = data.call_id. Set the scenario to process one at a time, so two parallel runs do not pass each other on this search.
- Router: activity exists → Update an Activity; it does not → Create an Activity attached to the person. Write Heilo Call ID = data.call_id, summary and recording_url. This recipe creates no opportunity.
| Heilo field | Pipedrive field / action | Use |
|---|---|---|
| event_type | Filter / Router | Process call.completed; use webhook.test only for field mapping. |
| data.customer_phone_e164 | Person → Phone Numbers | Primary field for contact lookup and deduplication. |
| data.transcript_processed.caller_name | Person → Person Name | If empty, use the phone number as a safe fallback name. |
| data.transcript_processed.subject | Lead/Deal → Title | Best opportunity title; fall back to service_needed. |
| data.transcript_processed.service_needed | Lead/Deal → Title or custom field | Inquiry category; useful for pipeline routing or labels. |
| data.transcript_processed.summary | Activity/Note → Note or Public Description | Short call description visible on the opportunity. |
| data.transcript_processed.lead_score | Lead label / priority / filter | For example, create a lead/deal only from 7/10 or send lower scores to review. |
| data.transcript_processed.preferred_date | Expected close date or activity due date | Use when the customer mentioned a deadline or preferred follow-up date. |
| data.transcript_processed.client_city | Person → Address (city) or custom field | Customer city; handy for segmentation or a regional pipeline. |
| data.transcript_processed.client_address | Person/Organization → Address | Fill the person/organization address when the caller provided it. |
| data.transcript_processed.services_match | Filter / Lead label | false = request outside your offer; skip the deal or flag it for rejection. |
| data.transcript_processed.additional_details | Activity/Note → Note | Extra details from the call; append them to the activity body. |
| data.recording_url | Activity/Note → Recording link | Playback link; may stop working after the call is deleted for GDPR retention. |
| data.call_id | Custom field → Heilo Call ID | Idempotency key. Prevents a second CRM record for the same call. |
| data.duration | Activity duration | Heilo sends seconds; convert if your Pipedrive field expects a time format. |
Conditions for correct pairing
- Do not turn on strict mode when confirming the address — Zapier and Make webhooks acknowledge receipt but do not echo the challenge field back. This applies only to the one-off address confirmation, not to the signature on ordinary deliveries.
- Do not mix event types without a filter: call.outbound.attempted does not include the full transcript or recording.
- Create a Heilo Call ID custom field in Pipedrive and write data.call_id into it. That is what you search on before every write — without it there is nothing to deduplicate against.
- A withheld number, or no number at all: do not create a person from that value. Route the event to manual handling instead, or every such call lands on one fictitious contact.
HubSpot: contact + deal + note
Target outcome: after a completed call, HubSpot finds or creates a contact by phone number, creates a deal and logs a note with the summary and recording link.
Deduplication trap: Zapier's Create or Update Contact matches existing contacts by e-mail only. Heilo events carry a phone number and often no e-mail, so that action creates a duplicate contact on every call. Always search by the phone property first and create a contact only when the search finds nothing.
Zapier
- Trigger: Webhooks by Zapier → Catch Hook. Keep the Heilo subscription in permissive mode.
- Filter by Zapier: allow event_type = call.completed; during setup also allow webhook.test (or filter by data._test = true).
- HubSpot → Find Contact. Set the search property to Phone Number and map data.customer_phone_e164; enable the option to create the contact when nothing is found, with caller_name or the phone number as the name. Do not use Create or Update Contact here — it matches by e-mail.
- HubSpot → Find Engagement on your Heilo Call ID property = data.call_id. Do not enable create-if-missing here; the next step decides that.
- Note found → update it; nothing found → Create Engagement (type Note) associated with the contact. Write Heilo Call ID = data.call_id plus summary, recording_url and transcript_original. Create Deal is not part of the default recipe.
Make
- Trigger: Webhooks → Custom webhook. The scenario must be active and run when data arrives.
- Add a filter after the webhook: event_type = call.completed; while mapping test data, also allow webhook.test (or filter by data._test = true).
- HubSpot CRM → Search for Contacts by the phone property = data.customer_phone_e164. If there is no result, create the contact with Create a Contact (that phone plus caller_name or the phone number as the name).
- HubSpot CRM → Search for Engagements on the Heilo Call ID property = data.call_id. Set the scenario to process one at a time.
- Router: the note exists → update it; it does not → Create an Engagement (type Note) associated with the contact. Write Heilo Call ID = data.call_id, summary and recording_url. This recipe creates no deal.
| Heilo field | HubSpot field / action | Use |
|---|---|---|
| event_type | Filter / Router | Process call.completed; use webhook.test only for field mapping. |
| data.customer_phone_e164 | Contact → Phone Number | Primary lookup key. Store phones in HubSpot in E.164 format — the phone search matches exact values, not formatting variants. |
| data.transcript_processed.caller_name | Contact → First Name / Last Name | If empty, use the phone number as a safe fallback name. |
| data.transcript_processed.subject | Deal → Deal Name | Best opportunity title; fall back to service_needed. |
| data.transcript_processed.service_needed | Deal → Deal Name or custom property | Inquiry category; useful for pipeline routing or labels. |
| data.transcript_processed.summary | Note → Note body | Short call description visible on the opportunity. |
| data.transcript_processed.lead_score | Custom property / filter | For example, create a lead/deal only from 7/10 or send lower scores to review. |
| data.transcript_processed.preferred_date | Deal → Close Date or task due date | Use when the customer mentioned a deadline or preferred follow-up date. |
| data.transcript_processed.client_city | Contact → City | Customer city; handy for segmentation or a regional pipeline. |
| data.transcript_processed.client_address | Contact → Street Address | Fill the person/organization address when the caller provided it. |
| data.transcript_processed.services_match | Filter / deal tag | false = request outside your offer; skip the deal or flag it for rejection. |
| data.transcript_processed.additional_details | Note → Note body | Extra details from the call; append them to the activity body. |
| data.recording_url | Note → Recording link | Playback link; may stop working after the call is deleted for GDPR retention. |
| data.call_id | Custom property → Heilo Call ID | Idempotency key. Prevents a second CRM record for the same call. |
| data.duration | Custom property or note | Heilo sends seconds; convert if your HubSpot property expects a different format. |
Conditions for correct pairing
- Do not turn on strict mode when confirming the address — Zapier and Make webhooks acknowledge receipt but do not echo the challenge field back. This applies only to the one-off address confirmation, not to the signature on ordinary deliveries.
- Do not mix event types without a filter: call.outbound.attempted does not include the full transcript or recording.
- Do not rely on Create or Update Contact for matching — it deduplicates by e-mail only; always run the phone search first.
- Add a HubSpot custom property named Heilo Call ID (single-line text) and store data.call_id there to avoid duplicates.
- A withheld number, or no number at all: do not create a person from that value. Route the event to manual handling instead, or every such call lands on one fictitious contact.
Zoho CRM: contact + deal + note
Target outcome: after a completed call, Zoho CRM finds or creates a contact by phone number, creates a deal and attaches a note with the summary and recording link.
Search contacts by the Phone field (or Mobile, if that is where you store numbers). Heilo sends data.customer_phone_e164 in E.164 format — keep Zoho numbers in the same format, otherwise the search will not match.
Zapier
- Trigger: Webhooks by Zapier → Catch Hook. Keep the Heilo subscription in permissive mode.
- Filter by Zapier: allow event_type = call.completed; during setup also allow webhook.test (or filter by data._test = true).
- Zoho CRM → Find Module Entry in the Contacts module, searching the Phone field by data.customer_phone_e164; enable the option to create the entry when nothing is found. Zoho requires Last Name — use caller_name or the phone number.
- Zoho CRM → Find Module Entry in the Notes module, searching your Heilo Call ID field = data.call_id. Do not enable create-if-missing.
- Note found → update Note Content; nothing found → Create Module Entry in the Notes module, related to the contact. Write Heilo Call ID = data.call_id plus summary, recording_url and transcript_original. The default recipe does not touch the Deals module.
Make
- Trigger: Webhooks → Custom webhook. The scenario must be active and run when data arrives.
- Add a filter after the webhook: event_type = call.completed; while mapping test data, also allow webhook.test (or filter by data._test = true).
- Zoho CRM → Search Objects in the Contacts module with the criterion Phone = data.customer_phone_e164. If there is no result, create the contact with Create an Object (Last Name from caller_name or the phone number).
- Zoho CRM → Search Objects in the Notes module with the criterion Heilo Call ID = data.call_id. Set the scenario to process one at a time.
- Router: the note exists → update it; it does not → Create an Object in the Notes module, related to the contact. Write Heilo Call ID = data.call_id, summary and recording_url. This recipe creates no opportunity.
| Heilo field | Zoho CRM field / action | Use |
|---|---|---|
| event_type | Filter / Router | Process call.completed; use webhook.test only for field mapping. |
| data.customer_phone_e164 | Contact → Phone | Primary lookup key. Search the Phone field (or Mobile) and keep one number format on both sides — the search matches exact values. |
| data.transcript_processed.caller_name | Contact → Last Name | Zoho requires Last Name; if caller_name is empty, use the phone number as a safe fallback name. |
| data.transcript_processed.subject | Deal → Deal Name | Best opportunity title; fall back to service_needed. |
| data.transcript_processed.service_needed | Deal → Deal Name or custom field | Inquiry category; useful for pipeline routing or labels. |
| data.transcript_processed.summary | Note → Note Content | Short call description visible on the opportunity. |
| data.transcript_processed.lead_score | Custom field / filter | For example, create a lead/deal only from 7/10 or send lower scores to review. |
| data.transcript_processed.preferred_date | Deal → Closing Date or task due date | Use when the customer mentioned a deadline or preferred follow-up date. |
| data.transcript_processed.client_city | Contact → Mailing City | Customer city; handy for segmentation or a regional pipeline. |
| data.transcript_processed.client_address | Contact → Mailing Street | Fill the person/organization address when the caller provided it. |
| data.transcript_processed.services_match | Filter / tag | false = request outside your offer; skip the deal or flag it for rejection. |
| data.transcript_processed.additional_details | Note → Note Content | Extra details from the call; append them to the activity body. |
| data.recording_url | Note → Note Content (link) | Playback link; may stop working after the call is deleted for GDPR retention. |
| data.call_id | Custom field → Heilo Call ID | Idempotency key. Prevents a second CRM record for the same call. |
| data.duration | Custom field or note | Heilo sends seconds; convert if your Zoho CRM field expects a different format. |
Conditions for correct pairing
- Do not turn on strict mode when confirming the address — Zapier and Make webhooks acknowledge receipt but do not echo the challenge field back. This applies only to the one-off address confirmation, not to the signature on ordinary deliveries.
- Do not mix event types without a filter: call.outbound.attempted does not include the full transcript or recording.
- Add a Zoho CRM custom field named Heilo Call ID and store data.call_id there to avoid duplicates.
- Zoho rejects records with missing required fields: Contacts need Last Name and Deals need at least Deal Name and Stage (depending on your layout also Closing Date) — map safe fallbacks for them.
- A withheld number, or no number at all: do not create a person from that value. Route the event to manual handling instead, or every such call lands on one fictitious contact.
Three different keys — do not mix them
event_id identifies one specific notification. The same call can send several notifications, and the same notification can arrive twice on a retry. Store event_id to recognise a repeat.
call_id identifies the conversation, which is the activity in your CRM. One call is one activity, no matter how many notifications arrived about it. This is the key you search on before writing.
The phone number, or the contact id in your CRM, identifies the person. Watch three cases: a withheld number arrives as a literal value rather than a number; there may be no number at all; and two contacts can match one number. In none of them should you create a contact from that value — route the event to manual handling instead, or every withheld-number call will collapse into one fictitious contact.
A sales opportunity — a deliberate addition, not a default step
The default recipe does not create a sales opportunity, because not every call is one and an unconditional "create deal" buries the pipeline under one deal per call. If you do want them, make it a rule: search for an open opportunity for that contact first and attach the activity to it; create a new one only when there is none. When several open opportunities match, do not guess — attach the activity to the contact and leave the choice to a person.
Repeats, partial failures and unknown outcomes
Heilo retries a failed delivery five times, spaced 2 minutes, 5 minutes, 30 minutes and 2 hours apart. We do not guarantee ordering: a recording event can arrive before the call-completed event. The recipe has to survive that rather than assume the first sent arrived first.
Deduplication has to cover every call that creates a record, not only the first one. The "check a flag, create, write the flag" layout is not enough: two parallel runs both read the flag before either writes it, and you get two records. If your CRM has a unique key or an upsert on Heilo Call ID, use it. If it does not, set the scenario to process one at a time and search before every write.
A timeout after sending a request that creates a record does not mean the record was not created — it means you do not know. Do not retry the creation. Read the state back by Heilo Call ID first and decide from that. The same applies to a failure halfway through: if the contact was created and the note was not, the retry should add the note, not a second contact.
Full data structure (reference)
Every notification has the same structure. The fields inside `data` depend on the event type — a completed call, for instance, carries the processed transcript. Heilo signs every delivery with the Heilo-Signature header (HMAC-SHA256 over the raw request body), but a signature does nothing until the receiver checks it. Zapier's Catch Hook and Make's Custom webhook accept the request without checking it — what that means in practice is in the section on the two security variants.
{
"api_version": "2026-06-15",
"event_id": "<uuid>",
"event_type": "call.completed",
"resource_id": "<call_id>",
"created_at": "2026-06-03T12:34:56Z",
"data": {
"call_id": "...",
"direction": "inbound",
"caller_phone": "+48600100200",
"customer_phone_e164": "+48600100200",
"customer_phone_national": "600 100 200",
"company_phone_e164": "+48222630000",
"call_created_at": "2026-06-03T12:30:11Z",
"app_url": "https://www.heilo.io/calls/3fa85f64-5717-4562-b3fc-2c963f66afa6",
"duration": 87,
"recording_url": "https://heilo.io/api/v1/calls/.../recording.mp3?token=...",
"transcript_processed": {
"caller_name": "Jan Kowalski",
"client_address": "ul. Przykladowa 10",
"client_city": "Warszawa",
"summary": "...",
"service_needed": "Tynki gipsowe w mieszkaniu 65m2",
"subject": "Wycena tynkow - Warszawa, 65m2",
"lead_score": 7,
"preferred_date": "2026-06-10",
"services_match": true,
"additional_details": "Klient wspomnial o terminie do konca czerwca."
},
"transcript_original": "..."
}
}Common issues
- Verification (handshake) failed after adding the subscription
- Your address didn’t confirm within 10 seconds. In Zapier this happens automatically; in Make the scenario must be switched on (and in strict mode you also need a “Webhook response” module). Also check the address has no spaces or “&” characters, fix it, then click “Reverify” on the paused subscription.
- Duplicate contacts are being created in my CRM
- Connector tools don’t normalise phone formats — “+48 600 100 200” and “+48600100200” look like two different numbers. Heilo already sends the number in E.164 format (the customer_phone_e164 field), so use that one to look up the contact. In Zapier you can also add a “Formatter → Phone Number → E.164” step before the lookup.
- The recording link stops working after a while
- A recording link is valid for 7 days from the moment it was generated, and it can stop working sooner. An expired token answers 403; a deleted recording answers 410. Re-sending the same event does not renew the link, because it carries the same address. If you need the recording later, open the call in Heilo after signing in. If you copy the file into your CRM, handle the call.deletion_scheduled and call.recording.deleted events too — the link expiring does not delete the copy you already hold.
- A message about exceeding the operation limit
- One Heilo call is usually a handful of steps in the tool: find the contact, create or update an activity. Do not convert that into a universal number of calls — after the first real event, open your Zap or scenario history and read how many tasks or credits that one run spent. That is what every further call will cost in your setup.
Roadmap (v1.1+)
What we're planning for upcoming releases. This list isn't a guarantee — priorities follow your feedback.
- Dedicated recipes for more CRMs: Salesforce, Bitrix24, Livespace
- One-click templates (Zapier/Make) for the most popular CRMs
- Copy-paste deduplication and field-mapping rules
- More event types to map (e.g. deal and task events, sms.received)
Missing your CRM or scenario? Email support@heilo.io — we prioritize by real-world need.
Manage keys and webhooks in the panel
Generate API keys, add webhook subscriptions and watch the delivery log after signing in.