With the M8Com webhook it is possible for an external provider to create an independent middleware between M8Com and any kind of CRM system(s). The middleware must be able to handle the request and response formats.
There are two main features:
- Number lookup
- Automatic call logging (optional)
When the middleware is up and running, and thoroughly tested, its request endpoints (URLs) can be added to the customer’s Insights settings as a webhook integration (contact Customer Care to set this up for the customer).
Once the webhook is activated for the customer, M8Com will automatically call the request endpoint for number lookup when:
- the customer receives an incoming call
- a number search is made (as a contact search or in the dialer)
- presenting items in a call log
If the request gives a hit in the connected CRM and the data is properly returned in the response, M8Com will automatically present the data for the user as “Caller Insights” that can be accessed from the dialer or a call log item or a contact page.
M8Com will automatically call the request endpoint for automatic call logging, if it’s activated, when:
- a call log item is created for the customer
How it works
Overview - number Lookup
See the illustration below for an overview of the flow of events when a customer receives a call and the webhook for number lookup is called by the M8Com app.
Overview - Automatic Call Logging
See the illustration below for an overview of the flow of events for automatic call logging. There is an ongoing call between a contact and a user, which is then hung up (by either part), which triggers a call to the webhook end point for automatic call logging, which is then handled by the middleware.
Webhook Setup for customers
Any customer with the Insights add-on can have a Webhook integration setup. The actual setup requires Superadmin permission and is handled by Customer Care. Once the Webhook is set up, the customer can see it and manage it in the Insights settings (Organisation Settings / Insights).
There are separate request end points to be set up for number lookup and automatic call logging, with the latter being optional.
Looks like this, from the Superadmin’s perspective, when set up:
A Webhook integration can be sorted in terms of priority for number lookup in the same way as any other kind of Insights integration.
Number lookup
M8Com calls the webhook automatically in the following cases:
- when the customer receives an incoming call
- when a phone number search is made (as a contact search or in the dialer)
- when presenting items in a call log
- when entering a phone number in number input fields (e.g. for redirects in IVRs)
Look-up data is cached by M8Com for 2 hours, so any changes made in the integrated system will not be reflected in M8Com. The caching is made to prevent request-flooding of the middleware.
When a response is received from the Webhook integration, the data is presented by M8Com in the same way as it would be for any other Insights integration (e.g. HubSpot or Upsales). The Webhook icon will be used as badge to indicate that the number lookup data comes from a Webhook integration.
In the example above, we see look-up information for a contact received via a Webhook integration named “Freshdesk” set up for a M8Com customer. The webhook was called by the M8Com app on an incoming call to the M8Com customer from this contact’s number. The Webhook response from the middleware contained information about:
- The contact’s name, company, and email address
- A link to the contact’s page in the integrated CRM system (Freshdesk in this case)
- Shortcut links for creating activities for the contact in Freshdesk
- A list of recent calls involving the contact and the customer
Automatic Call Logging
If automatic call logging is activated by the customer, the corresponding request end point is called by M8Com each time a relevant call is terminated and a call log item is created. See the specification below for the parameters being sent in the request. It is then up to the middleware to sync this call information with the integreated CRM system.
All phone calls for the customer trigger a webhook request when the call log is created, whether or not a corresponding number lookup request triggered a successful response or not. It is up to the middleware to decide if and how the call is logged in the connected CRM system(s).
Details & limitations
- The correctness of data and response times for a Webhook integration has nothing to do with M8Com, as M8Com do not control the Webhook implementation.
- Looked-up data is cached and re-used by the M8Com app for 2 hours.
- A webhook request times out after 3 seconds.
Implementation specification
This specification is for the implementer of a middleware, and describes the request and response formats that must be supported by the middleware.
Notes
It’s the responsibility of the Middleware implementer to make sure that the middleware is up and running at all times, and that the middleware is kept up to date regarding any relevant changes to the integrated CRM system(s) (e.g. API changes).
If a middleware is meant to serve several M8Com customers, the webhook request end point (the URL entered when setting up the Webhook for the customer’s M8Com account) set up for a specific customer must contain some kind of ID that can be used by the middleware to identify the customer and route look-up requests to the correct customer account in the integrated system. For example:
https://lynes-middleware.provider.com/unique-customer-id
Number Lookup
Request
When a number lookup occurs we’ll send a HTTP GET request to your request end point. The following will be provided as part of the query string:
import express from 'express';
...
// Route matching the URL provided to M8Com
app.get('/M8Com/middleware/lookup', (req, res) => {
// Read query string parameters
const { fromNumber, toNumber } = req.query;
// Call your database or API to get the contact information if any
const res = await queryForContact(fromNumber, toNumber);
return res.status(200).json(res);
});
...
The request will always contain a “fromNumber”, “toNumber” will be supplied when it’s applicable. The request will timeout after 3 seconds.
Response
{
"displayName": "",
"firstName": "",
"lastName": "",
"avatarUrl": "",
"coverPhotoUrl": "",
"emails": [{
"id": "",
"comment": ""
}],
"phoneNumbers": [{
"id": "",
"comment": ""
}],
"companyInfo": {
"name": "",
"title": "",
},
"fields": [{
"comment": "",
"id": "",
}],
"moreInfoUrl": "",
"activities": [{
"type": "",
...
}],
"createActivityUrl": [{
"name": "",
"url": ""
}]
}
Parameter description
"displayName": "Maria Andersson"
"firstName": "Maria"
"lastName": "Andersson"
"avatarUrl": "https://loremIpsum.com/xyz.jpeg"
"coverPhotoUrl": "https://loremIpsum.com/abc.jpeg"
id must to be a valid email address.
{
"id": "maria.andersson@anderssoninc.com",
"comment": "Work"
}
{
"id": "+46700000000",
"comment": "Work"
}
{
"name": "Andersson Inc",
"title": "CEO"
}
A list of up to 10 fields.
{
"comment": "Hobby",
"id": "Soccer"
}
A URL leading to a web page with more information about the contact.
"moreInfoUrl": "https://loremIpsum.com/contact/maria-andersson"
activities
A list of up to 10 activities per type, type has to be one of NOTE, TASK, CALL, TICKET, DEAL.
Activities are sorted on timestamp, newest first.
- NOTE
{
"type": "NOTE",
"body": "Sample text about the note",
"timestamp": 1606490711729,
"associatedUser": "developer@easyteams.com"
"link": "https://link.to.note"
}
- TASK
Status is only shown if task is not marked as “done”
{
"type": "TASK",
"done": true,
"status": "Backlog"
"header": "Call Support",
"body": "Sample text about the task",
"timestamp": 1606490711729,
"dueDate": 1606490711729,
"assignedUser": "developer@easyteams.com",
"link": "https://link.to.task",
}
- CALL
{
"type": "CALL",
"body": "Sample text about the call",
"timestamp": 1606490711729,
"toNumber": "+46708888888",
"fromNumber": "+462111113",
"duration": 23000,
"startTime": 1606490711729,
"link": "https://link.to.call"
}
- TICKET
{
"type": "TICKET",
"priority": "LOW",
"header": "Sample Name",
"body": "Sample Description",
"createdDate": 1606490711729,
"timestamp": 1606490711729,
"pipeline": "Support Pipeline",
"pipelineStage": "Waiting on us",
"ticketLink": "https://link.to.ticket"
}
- DEAL
{
"type": "DEAL",
"header": "Sample Deal",
"done": false,
"body": "Sample info"
"amount": "100",
"currency": "USD",
"closeDate": 1606490711729,
"timestamp": 1606490711729,
"pipeline": "Sales Pipeline",
"pipelineStage": "Appointment Scheduled",
"dealLink": "https://link.to.deal"
}
- APPOINTMENT
{
"type": "APPOINTMENT",
"header": "New Meeting",
"venue": "Sample Ave #55 Sioux Falls, SD, 55555, United States"
"status": "Accepted",
"startDate": 1683842400,
"endDate": 1683928800,
"timestamp": 1683842400,
"allDay": true,
"body": "Sample body",
"link": "https://link.to.appointment"
}
createActivityUrl
A shortcut to the integrated system so that a click on the link leads directly to the Create function for an activity of the specified type (e.g. a NOTE), for the contact which the looked-up number belonged to.
{
"name": "Note",
"url": "https://loremIpsum.com/contact/maria-andersson/create/note"
}
Markdown
It is possible to style the activity body using markdown. Follow this link for examples on what you can do and how to structure the body. We support most of the features listed, images are not supported and HTML will not be processed only displayed as text.
Example
================
This is a simple markdown document to illustrate different markdown features.
> #### The quarterly results look great!
> - Revenue was ***off*** the chart.
>> *Everything* is going according to **plan**.
#### More info
{
type: 'NOTE',
body: MARKDOWN,
markdown: true,
}
Custom headers & descriptions
Each activity type comes with a default header text and some also include a description, it is possible to override these by sending a custom text to show instead of the default value.
{
"type": 'TICKET',
"priority": 'LOW',
"customHeader": 'Custom Header',
"customDescription": 'Custom Description',
"body": 'Sample description',
"pipeline": 'Support pipeline',
"pipelineStage": 'Waiting on us',
"ticketLink": "https://link.to.ticket"
}
{
"type": "TICKET",
"priority": "LOW",
"header": "Sample Name",
"body": "Sample Description",
"pipeline": "Support Pipeline",
"pipelineStage": "Waiting on us",
"ticketLink": "https://link.to.ticket"
Automatic Call Logging
Request
If auto log calls is enabled a copy with some of the information contained in the call log will be posted to the designated URL.
We’ll send a HTTP POST request to your request end point. The event will be in the Content-Type: application/json format:
{
"toNumber": "+46XXXXXXXX",
"fromNumber": "+46YYYYYYYY",
"duration": 600,
"startTime": 1676305851000,
"callType": "Inbound",
"body": "Call to: +46XXXXXXXX\nCall from user: olle.bengtsson@acme.com (+46YYYYYYYY)",
}
Parameters
"toNumber": "+46XXXXXXXX"
"fromNumber": "+46YYYYYYYY"
"duration": 600
"startTime": 1676305851000
Call direction “Inbound” or “Outbound”, set from the perspective of the contact in the integrated CRM system.
"callType": "Inbound"
Free text field with data about the call, can include some of the following: Call to and call from description. Name of answer group and agent in the case of an answer group call. Ref to & ref by in the case that the call was transferred. Each instance will be new line separated and may or may not be included, and the text structure can not be guaranteed and is used by the middleware provider at it’s own risk.
"body": "Call to: +46XXXXXXXX\nCall from user: olle.bengtssong@acme.com (+46YYYYYYYY)"
V2
Secret
With version 2 of the API a secret is now necessary to setup a webhook integration. The secret will be sent in the authorization header on all requests M8Com make, contact lookup and call log posting if enabled.
import express from 'express';
...
// Route matching the URL provided to lynes
app.get('/lynes/middleware/lookup', (req, res) => {
// Validate against the authorization header
const { authorization } = req.headers;
if (!authorization) {
return res.status(401).json({ message: 'No authorization header provided' });
}
if (authorization !== 'Bearer YOUR_SECRET_HERE') {
return res.status(403).json({ message: 'Invalid secret' });
}
// Read query string parameters
const { fromNumber, toNumber } = req.query;
// Call your database or API to get the contact information if any
const res = await queryForContact(fromNumber, toNumber);
return res.status(200).json(res);
});
...