Display Insight Cards to your agents
Learn how to use your own data to give agents context around a customer.

The tutorial shows you how to use Aircall's Public API to display contextual insights in the call view of agents who are making or receiving calls.

We're going to subscribe to call.created events, gather contextual information in your database and display it as insight cards in the phone app.

Getting started

Before starting to code, you will need to have:

  1. An Aircall account - sign up here if you don't have one yet
  2. An API ID and an API token. You can generate them in the Company section of the Aircall Dashboard
  3. A server running your application with a valid webhook endpoint (See the Create a Webhook integration tutorial to learn more about it)
  4. Your own database where customer data is stored

Step by step

Now we are ready, here are the steps we are going to follow:

  1. Listen for call.created events
  2. Create an insight card
  3. Display the insight card during a call

1. Listen for call.created events

Aircall sends the event type in the body of the requests made to the route /aircall/calls of your application, set as the event attribute.

The call.created Webhook event is sent by Aircall when a call has begun on any of your Aircall numbers, regardless if it is an inbound or an outbound call. You may want to create a demo account to avoid being spammed by your production environment.

json
{ "resource": "call", "event": "call.created", "timestamp": 1512588391, "token": "123ASECRETTOKEN456", "data": { "id": 123, ... "direction": "inbound", "raw_digits": "+19171234567" ... } }

If you need more info on the Call object, follow this link

We want to handle both newly created incoming and outgoing calls. To provide different insights, you can check the direction of a call by parsing the direction attribute.

Aircall will send the event type in the body of the request, set as the event attribute. We will update our /aircall/calls route to filter it:

javascript
// POST /aircall/calls app.post('/aircall/calls', (req, res) => { if (req.body.event === 'call.created') { // Do something with this event } else { console.log('Event non-handled:', req.body.event); } res.sendStatus(200); });
ruby
def calls if params[:event] == 'call.created' # Code end head :ok end
go
type Call struct { Event string `json:"event" binding:"required"` } [...] router.POST("/aircall/calls", func(c *gin.Context) { var body Call c.bindJSON(&body) if body.Event == "call.created" { // Do something with this event } else { log.Println("Event type non-handled: " + body.event) } c.JSON(http.StatusOK, gin.H{}) })

2. Create an insight card

An insight card is a piece of information displayed in the call view that should give contextual information to help the agent better assist their customer:

  • Is the caller an important prospect?
  • Who is the caller's account owner?
  • What was the last support ticket the caller submitted?
  • ...

img img img

What type of content can an insight card contain?

A card is composed of one or many different types of data:

  • The title line, that can be clicked if a link is provided:
json
{ "type": "title", "text": "Last support ticket", "link": "https://my-custom-crm.com/12345" }
  • The shortText line, text displayed on one line that can be preceded by a label if one is provided. It can also be clicked if a link is provided:
json
{ "type": "shortText", "text": "John Doe", "label": "Account owner", "link": "https://my-custom-crm.com/6789" }

What payload structure do we need?

To send the card to Aircall's Public API, you will need to encapsulate the lines of your card in a contents array:

javascript
// POST /aircall/calls app.post('/aircall/calls', (req, res) => { if (req.body.event === 'call.created') { const cardContent = getInsightCardContent(); const payload = createInsightCardPayload(cardContent); } else { console.log('Event non-handled:', req.body.event); } res.sendStatus(200); }); const getInsightCardContent = () => { let lines = []; // Write your business logic here to populate the lines of the card return lines; }; const createInsightCardPayload = (lines) => { return { contents: lines }; };
ruby
# POST /aircall/calls def calls if params[:event] == 'call.created' card_content = get_insight_card_content create_insight_card_payload(card_content) end head :ok end private def get_insight_card_content lines = [] # Write your business logic here to populate the lines of the card lines end def create_insight_card_payload(lines) { contents: lines }.to_json end
go
import ( [...] "encoding/json" "time" ) type Card struct { Contents []CardLine `json:"contents"` } type CardLine interface{} type CardLineTitle struct { Type string `json:"type"` Title string `json:"title"` Link string `json:"link"` } type CardLineShortText struct { Type string `json:"type"` Title string `json:"title"` Label string `json:"label"` } func getInsightCardContent() []CardLine { var lines []CardLine // Write your business logic in here to fill in the lines slice return lines } func createInsightCardPayload(lines []CardLine) string { card := Card{ Contents: lines, } marshalled, err := json.Marshal(card) if err != nil { log.Println("Failed to marshal card:", err.Error()) return "" } return string(marshalled) } [...] router.POST("/aircall/calls", func(c *gin.Context) { var body Call c.bindJSON(&body) if body.Event == "call.created" { cardContent := getInsightCardContent() payload := createInsightCardPayload(cardContent) } else { log.Println("Event type non-handled: " + body.event) } c.JSON(http.StatusOK, gin.H{}) })

3. Display the insight card during a call

Now that have created the structure of the card we want to display, we are going to send it to Aircall with a POST request to /calls/:id/insight_card.

The id in the URL represents the ID of the call concerned. Aircall will take care of dispatching the information to all of the agents receiving this call. You can retrieve it in the data sent with the webhook event:

javascript
// POST /aircall/calls app.post('/aircall/calls', (req, res) => { if (req.body.event === 'call.created') { const callId = req.body.data.id; const cardContent = getInsightCardContent(); const payload = createInsightCardPayload(cardContent); } else { console.log('Event non-handled:', req.body.event); } res.sendStatus(200); }); [...]
ruby
def calls if params[:event] == 'call.created' card_content = get_insight_card_content create_insight_card_payload(card_content) call_id = params[:data][:id] end head :ok end
go
type Call struct { Event string `json:"event" binding:"required"` Data CallData `json:"data" binding:"required"` } type CallData struct { ID int `json:"id" binding:"required"` } [...] router.POST("/aircall/calls", func(c *gin.Context) { var body Call c.bindJSON(&body) if (body.Event == "call.created") { callId := body.Data.ID cardContent := getInsightCardContent() payload := createInsightCardPayload(cardContent) } else { log.Println("Event type non-handled: " + body.event) } c.JSON(http.StatusOK, gin.H{}) })

At this point, you will need to fill in your API_ID and API_TOKEN to make the call:

javascript
const request = require('request'); // POST /aircall/calls app.post('/aircall/calls', (req, res) => { if (req.body.event === 'call.created') { const callId = requ.body.data.id; const cardContent = getInsightCardContent(); const payload = createInsightCardPayload(cardContent); sendInsightCard(callId, payload); } else { console.log('Event non-handled:', req.body.event); } res.sendStatus(200); }); const sendInsightCard = (callId, payload) => { const API_ID = 'your_api_id'; const API_TOKEN = 'your_api_token'; const uri = `https://${API_ID}:${API_TOKEN}@api.aircall.io/v1/calls/${callId}/insight_cards`; request.post(uri, { json: payload }, (error, response, body) => { if (!!error) { console.error('Error while sending insight card:', error); } else { console.log('HTTP status code:', response && response.statusCode); console.log('HTTP body:', body); } } ); }; [...]
ruby
require 'net/http' require 'uri' API_ID = 'your_api_id'.freeze API_TOKEN = 'your_api_token'.freeze HTTP_POTENTIAL_ERRORS = [ EOFError, Errno::ECONNRESET, Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Timeout::Error ].freeze # POST /aircall/calls def calls if params[:event] == 'call.created' card_content = get_insight_card_content payload = create_insight_card_payload(card_content) call_id = params[:data][:id] send_insight_card(call_id, payload) end head :ok end private def send_insight_card(call_id, payload) uri = URI.parse("https://api.aircall.io/v1/calls/#{call_id}/insight_cards") req.basic_auth(API_ID, API_TOKEN) req = Net::HTTP::Post.new(uri) res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } rescue *HTTP_POTENTIAL_ERRORS => e end
go
import ( "bytes" "net/http" "fmt" [...] ) func sendInsightCard(callId int, payload string) { apiId := "" apiToken := "" url := fmt.Sprintf("https://%s:%s@api.aircall.io/v1/calls/%d/insight_cards", apiId, apiToken, callId); req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(payload))) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() if (resp.StatusCode != 201) { log.Println("Error while sending insight card:", resp.Status) } } [...] router.POST("/aircall/calls", func(c *gin.Context) { var body Call c.bindJSON(&body) if (body.Event == "call.created") { callId := body.Data.ID cardContent := getInsightCardContent() payload := createInsightCardPayload(cardContent) sendInsightCard(callId, payload) } else { log.Println("Event type non-handled: " + body.event) } c.JSON(http.StatusOK, gin.H{}) })

If the call has ended during the time you received the webhook event and sent the card, nothing will be displayed in the agents' apps

Now you are done! Open your Aircall Phone, try to make an outbound call on a number you are associated with and watch the insight card appear!

We wrote other tutorials to help you out
building awesome integrations

Discover them now