Display insights to your agents
Learn how to use your own data to give agents context around a customer

Select a language:

    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.

    {
      "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:

    // 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);
    });
    
    def calls
      if params[:event] == 'call.created'
        # Code
      end
      head :ok
    end
    
    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:
    {
      "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:
    {
      "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 add an UTC timestamp in a creationDate attribute and encapsulate the lines of your card in a contents array:

    // 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 {
        creationDate: new Date().getTime(),
        contents: lines
      };
    };
    
    # 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)
      {
        creationDate: Time.now.utc.to_i
        contents: lines
      }.to_json
    end
    
    import (
        [...]
        "encoding/json"
        "time"
    )
    
    type Card struct {
        CreationDate int64      `json:"creationDate"`
        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{
            CreationDate: time.Now().UTC().Unix(),
            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:

    // 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);
    });
    
    [...]
    
    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
    
    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:

    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);
                     }
                   }
                  );
    };
    
    [...]
    
    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
    
    import (
        "bytes"
        "net/http"
        "fmt"
        [...]
    )
    
    func sendInsightCard(callId int, payload string) {
        apiId := "<your_api_id>"
        apiToken := "<your_api_token"
    
        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!