Audit Phone Calls with Event Sourcing in .NET

Published November 10, 2020 by James Hickey

blog spotlight banner

Events are everywhere! The software development world has realized the benefits of modeling our business processes and application logic as a log of events. Event sourcing has been growing in popularity as a way to build systems that protect against data loss, model complex business scenarios more clearly, and provide flexibility in how they can be extended.

When we speak to subject matter experts, we naturally use events to describe business scenarios. For example, here’s a brief discussion that frames the context of this tutorial:

Business Expert: “When a customer places a phone call, we want to audit whether the call was answered and when the call was completed.”

Developer: “Ok. So we need to track when phone calls are started, answered, and completed?”

In this brief requirements gathering discussion, we’ve uncovered three different events that we’ll need to capture: call started, call answered, and call completed.

Luckily for us, Vonage has a fantastic API for tracking phone calls!

We’ll use the Vonage API and build a .NET Core application that stores and displays this information by using event sourcing.


If you wish to skip ahead, you can view the code for this tutorial on GitHub.

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.


To get started, you’ll need:

Event Sourcing Basics

Event sourcing is quite different from what we’re used to doing in the software industry.

Normally, we tend to store the current state of our system into database tables or documents. Any historical data is stored separately.

Event sourcing, on the other hand, stores the entire history of everything that happens in our applications. We display the current state of our system by “re-playing” and transforming all those events into view-models:

Event Sourcing

The entire “log” of events is called a stream. Events are associated with a primary stream that represents the entity or process it belongs to—a customer, order, shipment, etc.

Without going into the nitty-gritty and getting off-track, we’ll model our stream to represent an individual phone conversation, which the Vonage API makes super easy.

Database Configuration

We’ll need to first configure our PostgreSQL database. The easiest tool to do this is pgAdmin.

After downloading and installing, run pgAdmin.

Create a new database called call_audit.

Create database

Next, we’ll create some credentials so our .NET application can communicate with the new database. Right click Login/Group Roles > Create > Login/Group Role.

Create user

Name your user call_audit.

Click on the tabs at the top of the modal window and click Definition. Type call_audit as the password.

Finally, click on the tab Privileges and enable Can login? and Superuser.

Note: Don’t enable “Superuser” for production! We’re just building a demo application.

Building Our .NET Application

Let’s start building the .NET web application that will handle and display our phone conversations!

Create A New Application

To create a new .NET Core web application, run dotnet new mvc -n CallAudit --no-https in your terminal.

Next, run the following to install a couple packages you’ll need:

About Marten

We’re going to use the Marten .NET library to give us event sourcing superpowers.

Marten gives us the ability to easily use Postgres as a document DB and event store. It will also handle updating our read-side view-models whenever any new events are appended to our event streams.


In your Startup.cs file, replace the ConfigureServices method with the following:

Creating Our Events

We’ll need to create some C# objects to represent the domain events in our application. While these events will look very similar to the events we receive from the Vonage API (later on), we want to be specific about what we save to our event store and have total control.

Note: When building event sourced systems, you don’t want to store “events” that come from external systems. You always want to convert them into events specific to your domain/system. This keeps the core of your system decoupled and unaffected by changes to those external events or systems.

Next, create a new directory CallAudit/Events. Here are the events to create:

Creating Our Read-Side

Whenever we append events to our event streams, Marten will automatically create/update a document in Postgres as a cached version of our stream’s current state. Somewhere else in our application, we’ll be able to query the document DB using Marten’s document store capabilities and display our current state.

The Conversation type is the view-model that will represent the cached current state of our stream. See the Marten documentation for more about this.

Let’s create the Conversation class under the folder CallAudit/Projections:

Finally, we’ll need to tell Marten that we want our stream to create/update the Conversation projection automatically for us.

Inside Startup.cs in the ConfigureServices() method, add the following:

Command Handler

In event sourcing, events are typically created by commands. We’ll create one C# class that will expose the three different handlers we want to trigger when receiving a message from the Vonage call-tracking API.

We’ll create a class CallAudit/Handlers/CallAuditHandlers:

UI To Display Phone Conversations

At some point, you’ll want to view the data from your system. Replace the contents of Views/Home/Index.cshtml with the following:

Next, create a C# class at Models/IndexModel:

Next, replace the home controller at Controllers/HomeController.cs with the following:

This page will display all phone conversations audited in our system.

Webhooks Endpoint

The last part of our web application is to create the endpoints that will be used by Vonage to send phone tracking events.

Create an MVC controller under the Controllers folder named PhoneCallWebhooksController.

Here’s the code that should go in it:

Configure Our Vonage Time Tracking Application

Let’s get started with the Vonage API and real-time call tracking!

Just remember to have your Vonage account and CLI ready.

Using ngrok

To make sure that the Vonage API can connect to the webhooks we’ve created, we need a public URL to host our site on.

After you’ve installed ngrok (a prerequisite), you’ll need to configure your auth token.

Visit this link and run the command to configure your token.

Next, run the following in a terminal and keep it open and running:

ngrok http http://localhost:5000

Creating a Vonage application

Using the public URL that ngrok gave you, open up a terminal and run the following command (filling in the URLs):

nexmo app:create --keyfile private.key callaudit

You should see “Saved application id: XXXXX”. Copy that application id—you’ll need it.

Next, visit this page to see your available Vonage numbers. If you have a free trial, you should already have a number available.

Take that phone number and the application id then run the following:

nexmo link:app [number] [application ID]

You should get the message “number updated”.

Let’s Run It!

Alright, let’s try this out!

From your .NET Core application root directory in a terminal, run dotnet run.

With ngrok still running, try calling the Vonage phone number you linked to this application.

After you finished the call, navigate to http://localhost:5000/ in your web browser and you should see your conversation(s) listed.


Now you’ve built a phone call auditing system that uses event sourcing! Try to play around with Marten’s other ways to query the event streams like transforming an event directly to a read-side database document to see what other ways you can view your phone conversations produced by the Vonage API!

Leave a Reply

Your email address will not be published.

Get the latest posts from Nexmo’s next-generation communications blog delivered to your inbox.

By signing up to our communications blog, you accept our privacy policy , which sets out how we use your data and the rights you have in respect of your data. You can opt out of receiving our updates by clicking the unsubscribe link in the email or by emailing us at