Create integrations and widgets

Introduction

Gorgias is lot more useful when connected to your back-office and 3rd party applications, allowing you to leverage the power of all your services from within your helpdesk, all in one platform.

This is where Integrations and Widgets come in.

Integrations help you connect to those additional services using custom HTTP API calls. Widgets are containers that can be used to display customized customer data coming from these integrations on the right-hand sidebar of the ticket or customer page.

There are native Gorgias integrations, developed and maintained by the Gorgias team, that can be found in the Integrations page in the helpdesk and there are custom HTTP integrations that can be built by any account admins using the settings' UI or the Gorgias REST API.

In this tutorial we will focus on the latter:

  • a custom HTTP integration that connects Gorgias and Loop Returns,
  • and a widget to display data coming from Loop Returns into your helpdesk.

Requirements

3rd Party Application

This tutorial was built using example data from an existing Gorgias partner, Loop Returns. If you are a 3rd party developer, this tutorial is applicable to you but you will need to fulfill certain requirements to build an integration with Gorgias.

To build an HTTP integration with Gorgias you will need to provide the following:

  • Ability to generate valid API Keys for 3rd party tools to authenticate to your service
  • API endpoint from which Gorgias can fetch data given customers' email address

See an example below of a compatible API endpoint with a screenshot of the HTTP integration configured using Loop Return's API endpoint.

https://{{YOUR_GORGIAS_BASE_API_URL}}.com/api/users?email={{ticket.customer.email}}
3584

Example Loop Returns HTTP integration

Gorgias REST API access

Remember that in order to make API requests to the Gorgias REST API you need the access details available at the REST API page of your Settings section in your helpdesk.

In there you will find:

  • Your Gorgias Base API URL
    (e.g. http://{{YOUR_GORGIAS_BASE_API_URL}}.gorgias.com/api)
  • Your Gorgias API username (email address)
  • Your Gorgias API key (password)
3584

REST API details of a Gorgias account

Example: Working with Loop Returns

In order to create the Loop Returns integration and widget inside Gorgias, you will need first to have a valid API key from Loop Returns. Similarly, if you are trying to build an integration for another service make sure you are provided the right level access in the tool.

Moving forward, all further examples will be provided using Loop Returns' service.

You can get a valid API key from the Developers page in your Loop Returns Admin panel. Double-check that the API key you intend to use has all scopes enabled (Cart, Order, Return, Report).

πŸ“˜

Why Loop Returns?

Gorgias and Loop Returns have recently announced a new integration. With real-time access to every return or exchange in Gorgias, your agents can finally deliver an amazing customer returns experience all in one platform. We've published a step-by-step article that explains how to set-up the integration using the Gorgias' interface.

The purpose of this article is to use the exact same steps and examples to reproduce a programmatic guide. Naturally, these steps are applicable to any 3rd party application which offers an API endpoint with customer information. You will be able to create a seamless support experience by placing additional data inside your customer's Gorgias admin.

Creating the integration

To create an integration with Loop Returns, you will create a HTTP integration that uses GET to retrieve data (in JSON format) whenever a ticket is created, updated or has a new message in Gorgias.

The main details you need is the destination URL of the external service, the HTTP data required by it (HTTP method, headers, content type of the request and response, and payload) and the Gorgias events that will trigger the process.

In the case of Loop Returns integrations, the URL will be static:
https://api.loopreturns.com/api/v2/returns?from=2010-01-01&to=2050-01-01&customer_email={{ticket.customer.email}}.

Your request will be authenticated using a custom HTTP header expected by the Loop Returns API.

Remember to replace the variables in the following snippet with the actual values (i.e. your Loop Returns API key and your Gorgias REST API access details):

# Creating the integration 
curl -X "POST" "{{YOUR_GORGIAS_BASE_API_URL}}/api/integrations" \
     -H 'Content-Type: application/json;' \
     -u '{{YOUR_GORGIAS_API_USER}}:{{YOUR_GORGIAS_API_KEY}}' \
     -d '{
  "name": "Loop Returns",
  "description": "Integration to handle returns via Loop Returns",
  "type": "http",
  "http": {
    "url": "https://api.loopreturns.com/api/v2/returns?from=2010-01-01&to=2050-01-01&customer_email={{ticket.customer.email}}",
    "method": "GET",
    "headers": {
      "X-Authorization": "{{YOUR_LOOP_RETURNS_API_KEY}}"
    },
    "triggers": {
      "ticket-created": true,
      "ticket-message-created": true,
      "ticket-updated": true
    },
    "request_content_type": "application/json",
    "response_content_type": "application/json"
  }
}'

The API will give you a response similar to:

HTTP/1.1 201 CREATED
Content-Type: application/json

{
    "created_datetime": "2021-01-19T20:36:21.742013+00:00",
    "deactivated_datetime": null,
    "decoration": null,
    "deleted_datetime": null,
    "description": "Integration to handle returns via Loop Returns",
    "http": {
        "execution_order": 99,
        "form": null,
        "headers": {
            "X-Authorization": "{{YOUR_LOOP_RETURNS_API_KEY}}"
        },
        "id": 1,
        "method": "GET",
        "request_content_type": "application/json",
        "response_content_type": "application/json",
        "triggers": {
            "ticket-created": true,
            "ticket-message-created": true,
            "ticket-updated": true
        },
        "url": "https://api.loopreturns.com/api/v2/returns?from=2010-01-01&to=2050-01-01&customer_email={{ticket.customer.email}}"
    },
    "id": 123,
    "locked_datetime": null,
    "mappings": [],
    "meta": {},
    "name": "Loop Returns",
    "type": "http",
    "updated_datetime": "2021-01-19T20:36:21.742028+00:00",
    "uri": "/api/integrations/123/",
    "user": {
        "id": 1
    }
}

Take note of the ID of the newly created integration object (in our example, 123) and go to the next step to create the widget.

πŸ“˜

Integrations API

You can find details about the Integration resource in the documentation.

Creating the widget

Having created the integration successfully, and having the ID of that integration ready, let's proceed and create the widget.

In this case, we want to create a widget that appears next to a ticket (context=ticket), at the right-hand sidebar.

Formatting the widget

The trickiest part is setting up a valid, coherent value for the widget template. This template determines how and where the data from the integration is displayed. In general, a template is a JSON object following this structure:

{
    "type": "TYPE_OF_ELEMENT (e.g. wrapper, text, boolean, date, etc)",
    "title": "My widget element",
    "path": "PATH_IN_JSON_RESPONSE_FROM_INTEGRATION (e.g. ticket.id)",
    "meta": {"INTERNAL_SETUP": "..."},
    "widgets": [
        {"type": "", "title": "", "path": "", "meta": {}, "widgets": ["..."]},
        {"type": "", "title": "", "path": "", "meta": {}, "widgets": ["..."]},
        ...
        {"type": "", "title": "", "path": "", "meta": {}, "widgets": ["..."]},
    ]
}

As you can see in the JSON above, widgets have a recursive structure –widgets can include nested widgets. In the Gorgias interface this shows as separate section in the widget. This also means that each section have the same formatting capabilities as the main widget itself.

2992

Example Widget structure in the Loop Returns widget

At the moment, you have the following formatting and customization options for each widget:

  • Title: customize the title with dynamic variables, add link to the title, set maximum number of characters to display and / or hide title
  • Icon & border color: add your brand icon and brand color to the widget. Note: a border color can only be added to the parent container.
  • Order: decide the display order of widgets nested at the same level (for instance: Orders list, Returns list, etc.)
  • Fields: customize a field's title (defaults to JSON key name) and type (each type has a proper display option)

πŸ“˜

Using dynamic variables

Similarly to macros, you can leverage the data provided by the integration as dynamic variables to customize your widget. You can specify variables by using double curly brackets and the name of the field such as {{name}}. If you want to use nested information make sure to provide the downward path such as {{order.id}}

For example:

{
    "type": "card", 
    "title": "Loop Returns", 
    "path": "", 
    "meta": {
        "link": "https://STORENAME.loopreturns.com/#/",
        "displayCard": true
    }, 
    "widgets": [
        {
            "type": "text", 
            "title": "Status", 
            "path": "status"
        },
        {
            "type": "card", 
            "title": "Return Items", 
            "path": "",
            "widgets": ["... NESTED WIDGET'S FIELDS ..."]
        }
    ]
}

From there we can extract the following:

  • type is card: this means this is defined as a section –in this case, the main widget
  • The title of this main widget is Loop Returns and will be rendered as a link to meta.link
  • The main widget has one inner field called Status that obtains its value from path
  • The main widget has also a inner section called Return Items

πŸ“˜

What is path?

Path is a string of where to find the target value, relative to the JSON response of the integration. For example, if a HTTP integration returns a JSON object like {"data": {"id": 123}}, then setting path to data in the first card widget and then id in a text widget immediately below will render 123.

You can find more details on our recommended widget format here, but the following snippet can serve as inspiration:

# Creating the widget 
curl -X "POST" "{{YOUR_GORGIAS_BASE_API_URL}}/api/widgets" \
     -H 'Content-Type: application/json;' \
     -u '{{YOUR_GORGIAS_API_USER}}:{{YOUR_GORGIAS_API_KEY}}' \
     -d '{
  "context": "ticket",
  "type": "http",
  "integration_id": {{ID_OF_THE_INTEGRATION_CREATED_ABOVE}},
  "template": {
    "type": "wrapper",
    "widgets": [
        {
            "meta": {
                "displayCard": true,
                "link": "https://STORENAME.loopreturns.com/#/"
            },
            "path": "",
            "title": "Loop Returns",
            "type": "card",
            "widgets": [
                {
                    "path": "status",
                    "title": "Status",
                    "type": "text"
                },
                {
                    "meta": {
                        "limit": "",
                        "orderBy": ""
                    },
                    "path": "data",
                    "type": "list",
                    "widgets": [
                        {
                            "meta": {
                                "displayCard": true,
                                "link": "https://admin.loopreturns.com/returns/{{id}}"
                            },
                            "title": "Return {{order_name}}: {{state}}",
                            "type": "card",
                            "widgets": [
                                {
                                    "order": 0,
                                    "path": "created_at",
                                    "title": "Created at",
                                    "type": "text"
                                },
                                {
                                    "order": 1,
                                    "path": "label_status",
                                    "title": "Label status",
                                    "type": "text"
                                },
                                {
                                    "order": 2,
                                    "path": "label_updated_at",
                                    "title": "Label updated at",
                                    "type": "text"
                                },
                                {
                                    "order": 3,
                                    "path": "carrier",
                                    "title": "Carrier",
                                    "type": "text"
                                },
                                {
                                    "order": 4,
                                    "path": "tracking_number",
                                    "title": "Tracking number",
                                    "type": "text"
                                },
                                {
                                    "order": 5,
                                    "path": "state",
                                    "title": "State",
                                    "type": "text"
                                },
                                {
                                    "order": 6,
                                    "path": "updated_at",
                                    "title": "Updated at",
                                    "type": "text"
                                },
                                {
                                    "order": 7,
                                    "path": "currency",
                                    "title": "Currency",
                                    "type": "text"
                                },
                                {
                                    "order": 8,
                                    "path": "return_product_total",
                                    "title": "Return product total",
                                    "type": "text"
                                },
                                {
                                    "order": 9,
                                    "path": "return_discount_total",
                                    "title": "Return discount total",
                                    "type": "text"
                                },
                                {
                                    "order": 10,
                                    "path": "return_tax_total",
                                    "title": "Return tax total",
                                    "type": "text"
                                },
                                {
                                    "order": 11,
                                    "path": "return_total",
                                    "title": "Return total",
                                    "type": "text"
                                },
                                {
                                    "order": 12,
                                    "path": "handling_fee",
                                    "title": "Handling fee",
                                    "type": "text"
                                },
                                {
                                    "order": 13,
                                    "path": "refund",
                                    "title": "Refund",
                                    "type": "text"
                                },
                                {
                                    "order": 14,
                                    "path": "gift_card",
                                    "title": "Gift card",
                                    "type": "text"
                                },
                                {
                                    "order": 15,
                                    "path": "exchange_product_total",
                                    "title": "Exchange product total",
                                    "type": "text"
                                },
                                {
                                    "order": 16,
                                    "path": "exchange_discount_total",
                                    "title": "Exchange discount total",
                                    "type": "text"
                                },
                                {
                                    "order": 17,
                                    "path": "exchange_tax_total",
                                    "title": "Exchange tax total",
                                    "type": "text"
                                },
                                {
                                    "order": 18,
                                    "path": "upsell",
                                    "title": "Upsell",
                                    "type": "text"
                                },
                                {
                                    "order": 19,
                                    "path": "exchange_total",
                                    "title": "Exchange total",
                                    "type": "text"
                                },
                                {
                                    "meta": {
                                        "limit": "",
                                        "orderBy": ""
                                    },
                                    "order": 21,
                                    "path": "line_items",
                                    "type": "list",
                                    "widgets": [
                                        {
                                            "meta": {
                                                "displayCard": true,
                                                "link": ""
                                            },
                                            "title": "Return Items",
                                            "type": "card",
                                            "widgets": [
                                                {
                                                    "order": 0,
                                                    "path": "title",
                                                    "title": "Title",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 1,
                                                    "path": "product_id",
                                                    "title": "Product id",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 2,
                                                    "path": "variant_id",
                                                    "title": "Variant id",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 3,
                                                    "path": "sku",
                                                    "title": "Sku",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 4,
                                                    "path": "parent_return_reason",
                                                    "title": "Parent return reason",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 5,
                                                    "path": "return_reason",
                                                    "title": "Return reason",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 6,
                                                    "path": "price",
                                                    "title": "Price",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 7,
                                                    "path": "discount",
                                                    "title": "Discount",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 8,
                                                    "path": "tax",
                                                    "title": "Tax",
                                                    "type": "text"
                                                },
                                                {
                                                    "order": 9,
                                                    "path": "refund",
                                                    "title": "Refund",
                                                    "type": "text"
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
  }
}'

This is how the widget from the request above would look like:

360

Loop Returns widget example

🚧

Please note

For all widget integrations, the widget will only be displayed if the ticket's customer has associated data in your app.

πŸ“˜

Widgets API

You can find details about the Widgets resource in the documentation.

And that's all!

In 2 easy-to-follow steps your helpdesk has been enhanced and you can create other integrations to continue to do so.

Custom actions

πŸ“˜

All you need to know about custom actions

You can read this article to know more about custom actions: https://updates.gorgias.com/publications/custom-actions-in-every-widget

The custom widget json can be set to any root card widget and must follow the structure below:

{
         "meta":{
            "link":"",
            "custom":{
               "links":[
                  {
                     "url":"https://developers.gorgias.com/docs/create-integrations-and-widgets-programmatically",
                     "label":"My first redirection link"
                  }
               ],
               "buttons":[
                  {
                     "label":"My 1st action",
                     "action":{
                        "url":"https://test-manuel.ngrok.io/posts",
                        "body":{
                           "contentType":"application/json",
                           "application/json":{
                              
                           },
                           "application/x-www-form-urlencoded":[
                              
                           ]
                        },
                        "method":"GET",
                        "params":[],
                        "headers":[
                           {
                              "key":"cryptic_key",
                              "label":"Secret key",
                              "value":"l337_5p34k",
                              "editable":true,
                              "mandatory":true
                           }
                        ]
                     }
                  },
                  {
                     "label":"My 2rd action",
                     "action":{
                        "url":"https://gorgias-rocks.ngrok.io/comments",
                        "body":{
                           "contentType":"application/x-www-form-urlencoded",
                           "application/json":{},
                           "application/x-www-form-urlencoded":[
                              {
                                 "key":"user",
                                 "label":"User name",
                                 "value":"{{ticket.customer.name}}",
                                 "editable":false,
                                 "mandatory":false
                              }
                           ]
                        },
                        "method":"POST",
                        "params":[],
                        "headers":[]
                     }
                  }
               ]
            },
            "displayCard":true
         },
         "path":"",
         "type":"card",
         "order":0,
         "title":"My integration",
         "widgets":[]
      }

Make sure that the action settings are properly set in the meta and then the custom key.

πŸ“˜

About custom action variables

You can use dynamic variables in custom actions. But there are a few differences between links and buttons though.

Relative and absolute paths

The path you set to reach the integration data in the links is relative to the widget it is in, while it is absolute in the buttons.
For example, if you have an shopify integration with this data {partner: {name: John}} in a card widget whose path is partner, then, if you want to use a dynamic variable in the custom links of this widget you will simply need to use {{name}}. However, if you set a custom action button and want to use name in a param value then you need to provide the absolute path i.e. {{partner.name}}.

Variables exclusive to custom action buttons

When creating a custom action button, you sometimes need to be aware of the widget context.
Let’s say you have a user with several products, and you want to create a custom action for each of these products. To access the current index of a list widget, you can use the syntax $listIndex. For example, {{products[$listIndex].itemId}} will access the id of the related product in a list.
The other variable you have at you disposal is $integrationId which allows you to safely refer to the current integration’s ID.

Variables exclusive to custom action links

There is one specific variable you can use only in custom action links which is {{current_user}}. It refers to the agent currently logged in. There are four available fields inside the current_user variable:

  • name
  • lastname
  • firstname
  • email

e.g. {{current_user.email}}