Get real-time notifications about missed Voys calls in Slack using n8n

Written by

Jannick Nijholt

Published on

17/12/2024

Last update

17/12/2024
BlogUncategorized

Getting notified about missed calls is crucial for any business. While Voys, a popular VoIP provider in The Netherlands, offers a basic webhook functionality through their ‘Gespreksnotificaties’ feature, there’s no built-in way to get these notifications directly into Slack.

That’s where automation comes in handy.

In this blog, I’ll show you how I built a workflow using n8n to get missed call notifications from Voys into Slack. We’ll be using Cloudflare Workers KV as our temporary storage solution to track call statuses, and I’ll explain why this is necessary for proper call tracking.

Quick note before we dive in: This setup is using a self-hosted n8n instance in my case. Why? Because every call status change triggers a webhook, which means your workflow will execute frequently. With a cloud-hosted n8n instance, I run into execution limits really quick.

This blog will also be using Slack as an example platform to receive notifications. Slack can be replaced with almost any other platform (Teams, Discord, WhatsApp and many more).

Tools & Integrations that are being used

Before we start with the n8n flow, let’s discuss the tools and integrations I use more in-depth first.

Self-hosted instance of n8n

For this workflow, I’m using a self-hosted instance of n8n. While you could technically use one of the cloud hosted plans of n8n, I strongly recommend going the self-hosted route for this specific use case. Here’s why: The Voys ‘Gespreksnotificaties’ integration sends a webhook for every status of every call. This means that for a single phone call, you might receive 3-4 webhooks:

  • When the call is created
  • When it starts ringing
  • When it’s answered (or not)
  • When it ends

If your company receives 50 calls per day, you’re looking at 150-200 workflow executions daily. With n8n’s cloud hosted plan execution limits, you’d quickly run into the execution limit. By self-hosting n8n, the main benefit is that you have unlimited executions, a drawback is that you have to host and maintain the n8n instance yourself.

Gespreksnotificaties by Voys

To be able to send notifications about missed calls we use the Voys integration called ‘Gespreknotificaties’. You can find the integration in the settings of your Voys envoirnment.

Integration options: Call, Webhooks, CRM, Teams.

Upon opening the ‘Gespreksnotificaties’ settings you won’t find any entries. By using the ‘Toevoegen’ button you can add your own webhook URL (from n8n in our case).

Webhook notification settings interface with URLs and options

After adding your n8n webhook(s) the URL(s) will receive a webhook for every call, at every status of the call. Statuses can be:

  • Created
  • Ringing
  • In Progress
  • Ended
  • Warm Transfer
  • Cold Transfer

Within these status you will find reasons for them. For the status ‘Ended’ this can be for example:

  • Completed Call
  • Busy
  • No Answer
  • Failed
  • Cancelled
  • Abandon

The other statuses will also have their own reasons. To learn about them read the documentation about gespreksnotificaties by Voys.

Gespreksnotificaties is quite a basic integration. The only thing it does is send webhooks at every status of a call. A big limitation, in my opinion, is that it’s not possible to only receive notificiates at certain stages or/and only for certain numbers. This integration is all or nothing.

Cloudflare Workers KV

Another important element in the workflow is the Cloudflare Workers KV. This is a free key-value store from Cloudflare that can be used to (as you can guess) store key-value pairs.

We use the key-value store because at the moment of writing Voys lacks an API. This means we cannot GET information about the call directly from Voys and we have to have our own mini database.

In the case of Voys I’m storing the following data that comes back from the ‘Gespreksnotificaties’ webhooks:

  • Key: {Call ID}
  • Value: {Status}
Database table with key-value pairs and view option.
How the key-value store looks like in the Cloudflare interface

The n8n flow will create or update a Call ID value when a webhook with status other than ‘Ended’ comes in. We do this to keep track of the last known status of the call.

If a webhook comes in with the status ‘Ended’ we don’t update the value. In that case we lookup the last known value for the Call ID key.

The n8n flow

Now, let’s take a look at the n8n flow that I’ve build to get notifications in Slack about missed calls when using Voys as your VoIP provider.

Flowchart showing webhook integration and data processing.
Screenshot of the n8n workflow

1. Incoming Webhook

We start with the incoming webhook from Voys. As said earlier in my blog, the ‘gespreksnotificatie’ integration in Voys will send a webhook to our n8n flow at (almost) every stage of every call. Most important data we get back is: Call ID, Status and Reason.

2. Switch (inbound or outbound)

The second step is a simple switch. We use this to filter out outbound calls and only continue our flow with inbound calls.

3. Switch (based on status)

The next switch will switch the workflow based on the status of the call. In case the status of the call is other than ‘Ended’ the flow will push the specific status to the Cloudflare Worker KV (3.1)

If the status of the call is ‘Ended’ then we will continue and check the reason for the status (4).

3.1 HTTP Request (Push Status to Cloudflare Worker KV)

This HTTP Request will push the Call ID (Key) and the status of the call (Value) to the Cloudflare Worker KV.

PUT https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key}

Headers:
Authorization: Bearer {api_token}
Content-Type: text/plain

Body:
value_to_store

If the Call ID key does not exist in the KV store, the key with it’s value will be created.

If the Call ID key does exist the value will be updated. This is default behavior by the API.

4. Switch (based on reason)

If in Switch (based on status)(3) the status was ‘Ended’ we will continue to check the reason why. In the current version of the flow it does not matter what the reason was. You can perform different actions based on different reasons.

Because we will check if the call was ‘in progress’ in the next steps, for me the ‘reason’ did not matter.

5. HTTP Request (Get Value for Caller ID Key)

This HTTP Request will get the latest value known for the Call ID in the Cloudflare Workers KV. We use the value that comes back in the next Switch.

We do this by performing a GET HTTP Request to the Cloudflare API like this:

GET https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key} 

Headers: 
Authorization: Bearer {api_token}

5.1 Extract Value

But, before we can check the value using a Switch, we have to extract the value. The Cloudflare API will give back a raw, unstructured, file with the value of the Key. Using the ‘Extract from file’ node we extract the value.

6. Switch (Check if call was ‘in progress’)

Now that we’ve extracted the value for the Call ID Key from Cloudflare we check wether the status of the call was ‘in progress’ or not.

If the value that comes back for the Call ID Key is ‘in progress’ we continue to the last step of the flow to delete the Key/Value entry from the Cloudflare Workers KV.

If the value that comes back for the Call ID Key is not ‘in progress’ then we follow the next steps in the flow.

7. Find number in Pipedrive

We go to this step in case the call was not ‘in progress’. That means the caller hasn’t talked to one of the employees and thus I consider this call Missed. To get some more data about the call we perform a search for the number in Pipedrive. This data is being used in the Slack message.

8. Switch (based on number dialed)

As talked about before, the ‘gespreksnotificaties’ integration doesn’t allow for filtering within Voys. We get webhooks for all calls, to all numbers at all statuses. I build in this switch to send specific Slack messages to specific Slack channels based on the number the caller dialed.

9. Send a Notification (to Slack)

In this node I send a Slack message to a specific Slack channel with all the details about the call, along with the latest known status of the call. We style the message using Slack Blocks.

Employees will call back the caller as soon as possible after the Slack message was posted in their channel.

Note: You can replace this notification node with any platform. Want to receive a Discord notifications? Just as easy! Microsoft Teams notification (apart from their native Teams integration)? Possible! WhatsApp? Sure 🙂 This is the freedom you have with using a no/low-code solution like n8n.

10. HTTP Request (Delete Key/Value entry)

The last step that is being performed in the flow is a DELETE request towards the Cloudflare API. This makes sure the Call ID and its latest value is being removed and the Workers KV is kept clean. A request looks like this:

DELETE https://api.cloudflare.com/client/v4/accounts/{account_id}/storage/kv/namespaces/{namespace_id}/values/{key}

Headers:
Authorization: Bearer {api_token}

This workflow has been running smoothly for quite some time now, ensuring that no important calls slip through. While it might seem like a complex setup at first, it’s actually quite straightforward once you understand how the different pieces fit together.

A few key takeaways:

  • I think self-hosting a n8n instance is crucial for handling the high volume of webhooks in this case
  • Cloudflare Workers KV provides a reliable way to track call statuses
  • The workflow can be customized based on your specific needs (like different switching based on different reasons, sending messages to different Slack channels based on phone number and a lot more!)

Want to implement this yourself? Feel free to reach out if you have any questions about setting up a similar workflow for your organization. You can find me on LinkedIn or send me an email!

Profile Picture of Jannick Nijholt

Question, comment or an idea?

Shoot me an email!
You can reach me on [email protected]