Custom Channel V2

💡
We introduced Version 2 of the Custom Channel API with an enhanced feature set in May 2023. Version 1 of the Custom Channel API is deprecated and will only be supported until August 31, 2023. Please make sure your integrations are compatible with the changes introduced in V2
With our custom channel you can easily connect various message-based systems to Userlike and complement the channels we already support. For example this could be your own email server.
Messages coming in through the custom channel appear in the Message Center and senders are identified as contacts through a unique contact identifier on your side, for example their email address or your customer ID. When your operators respond in Userlike, the contacts receive the reply on the message system you connect.
Your custom channel will use two JSON API endpoints. The endpoint on Userlike’s side is where you relay messages to and it’s set up automatically at channel creation. The second one lies on your message system’s end, it’s where you receive messages from your contacts. The custom channel supports text, voice and media messages, along with live preview for media links.

API versioning

Our APIs are constantly evolving, but we are striving to implement any new features in a backwards compatible way. If breaking changes are necessary, we will introduce a new API version. Examples for breaking changes are:
  • Removing, renaming, or moving API entities.
  • Changing or removing functionality.
  • Making optional parameters or properties mandatory.
Non-breaking changes, like new endpoints or new optional properties, can happen anytime. When we deprecate older versions of the API, we will provide at least 90 days of notice.
The current version of our Custom Channel API is Version 2, which was released May 04, 2022.
The previous Version 1 is deprecated and will be disabled after August 31, 2023.

Changelog

2023-07-06

  • We added the event property to the message object in outbound messages. This property is available when the type of the message is notification, and helps you distinguish between different types of notifications.

2023-05-04

  • We released version 2 of our Custom Channel API. For a detailed description of the changes, refer to the next section.

Changes in V2

💡
Please note that we did not update existing channels to V2 automatically yet and will support V1 until August 31, 2023. Once you have ensured that your endpoints are compatible with V2, you can update your channels to V2 in the Dashboard yourself.

New Features

  • Use your most suitable contact identifiers, instead of having to use email.
  • Support for starting separate conversations with the same contact.
  • Update contact information through webhook calls.
  • Prevent sending out duplicate messages by providing message UUIDs.

Inbound data changes

We overhauled the JSON packet structure to be able to add more functionality.
  • email was replaced by contact_identifier, which can be any unique string identifier for your contact. You may still use an email, however note that contact_identifier is a generic identifier. This means that emails will not be set on the contact automatically. For this, you can now provide the email inside the contact object in the payload.
  • Added conversation_identifier, which allows you to generate an optional identifier for a conversation. If you do, you are able to start new conversations with the same contact.
  • Added uuid field in message body, which enables you to prevent sending duplicated messages by accident.
  • Added optional contact object, which allows setting certain information on the contact.
  • Changed the attachment object structure, removing the payload nesting.
  • Please note that the updated webhook URL now includes the version number. After updating your channel, you can find the correct URL ending with: api/um/channel/custom/v2/webhook/.
For examples and explanations please refer to the section on inbound messages below.

Outbound data changes

With V2 we reduced the information contained inside the outbound messages we send to your API endpoint:
  • conversation was removed completely, you can use message.conversation_id instead or conversation_identifier if given (also see inbound documentation).
  • message was reduced a lot and now only contains these keys: bodyconversation_idmsgidsent_at, type and event (when type is notification).
  • operator was reduced a lot and now only contains these keys: about_medisplay_nameidjob_title and url_image.
  • contact did not change.
  • contact_identifier was added.
  • optional conversation_identifier was added.
For examples please refer to the section on outbound messages below.

Channel setup

Start by creating a custom channel in the settings. There you fill out the following fields:
  • Channel name: Name of your custom channel
  • Outbound URL: URL of your API endpoint
  • Outbound auth token (provided by Userlike): Authentication token to access your API endpoint
  • Inbound auth token (provided by Userlike): Authentication token to access the Userlike API endpoint
  • Inbound URL: URL of the Userlike API endpoint. It will be generated and appear in the channel settings after the channel setup
  • Widget: Widget that will handle messages sent to the channel. The Widget’s settings apply to assign the right operator, display the correct language and use the right privacy settings.
After filling out the form, click Create channel.
Image without caption

API documentation

In the following sections you learn how to set up your API endpoint and how to access Userlike’s API endpoint. This should give you a straightforward understanding of how to get your custom channel up and running with your use case.

How to send inbound contact messages to the Userlike API endpoint

Safety

It is crucial to note that any provided identifier needs to be verified on your side, since we will interpret all incoming information as coming from this user. If e.g. you are using an email, make sure the user really owns this email and is authenticated with it on your end.

Contact Identifier

In v2, we have introduced a significant change involving the identifier used for contacts within the custom channel scope. The previous identifier, email within the message object, has been replaced with a new immutable identifier called contact_identifier. This contact_identifier is required and cannot be modified once it has been created.

Conversation Identifier

You can assign any string value to the conversation_identifier within the payload object. The conversation_identifier serves as an identifier for a conversation associated with a specific contact_identifier. If a conversation cannot be found using the provided value, a new conversation will be initiated with it.

Channel Scope

Similar to the v1 version, contacts are scoped to a specific custom channel. In this context, each contact_identifier and conversation_identifier is exclusively associated with a particular custom channel. As a result, sending the same contact_identifier or conversation_identifierto two different channels will create two separate contacts or conversations for a contact, each specifically linked to their respective channels.

Requests

To send a message to the custom channel you need to send a JSON packet as POST request to the Inbound URL endpoint, which you find in the channel’s settings.
Include an API-SECURITY-TOKEN header in the POST request, with the value of the Inbound auth token, which you also find in the channel’s settings.
Sending inbound messages to the Userlike API endpoint
javascript
{ "attachments": [ { "description": "Test Image", "url": "https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg" } ], "contact": { "email": "jsmith@example.com", "name": "Jane Smith" }, "contact_identifier": "j_smith_1234", "conversation_identifier": "cff47d61-6d02-4f04-b596-ece293ab4719", "message": { "body": "Hello", "uuid": "a223420c-8fe6-4aed-bb21-3099fceff095" } }
Name
Description
contact_identifier
required A string used to uniquely identify the contact within the custom channel scope.
message
required Message object, containing body and uuid field.
conversation_identifier
Optional Field. Set this value if you plan to handle multiply conversation with one contact_identifier simultaneously. Can be a maximum of 255 characters.
contact
An object containing create or update fields for the contact. For a full reference please see the JSON API documentation for contacts.
attachments
A list of attachment objects. Each attachment objects must have a body field and can have an optional description. Userlike will try to fetch the asset and convert it to a preview that can be displayed in the Message Center. A copy of the asset will be stored for later access. The preview supports all common media types.

Sending outbound messages to your API endpoint

For every message sent by an operator in your Message Center, Userlike sends an HTTP POST request to your API endpoint, which is defined by your Outbound URL.
Include an API-SECURITY-TOKEN header in the POST request and make sure to check the value against the value of your Outbound auth token.
javascript
{ "contact": { "city": null, "company": null, "contact_by_email": true, "contact_by_phone": false, "contact_by_userlike_channels": true, "contact_by_userlike_messenger": true, "country": null, "created_at": "2020-05-18T12:03:06.443Z", "email": "jsmith@example.com", "email_verified": false, "external_customer_id": null, "first_message_sent_at": "2020-05-18T12:03:06.752Z", "gender": null, "id": 36, "is_blocked": false, "is_mobile_number_verified": false, "last_message_sent_at": "2020-05-18T12:38:39.405Z", "loc_lat": null, "loc_lon": null, "locale": "en_US", "mobile_number": null, "name": "Contact jsmith@example.com", "needs_register": false, "phone_number": null, "position": null, "salutation": null, "street": null, "url_facebook": null, "url_linkedin": null, "url_profile_picture": null, "url_twitter": null, "verified": false }, "contact_identifier": "custom_id_1234", "conversation_identifier": "9d67e219-14d2-4bcc-8562-be61b41b9f43", "message": { "body": "Hello there!", "conversation_id": 1, "msgid": "1.2.1", "sent_at": "2023-03-17T21:06:26.518Z", "type": "message" }, "operator": { "about_me": "string", "display_name": "David", "id": 1, "job_title": "string", "url_image": "https://userlike-cdn-operators.s3-eu-west-1.amazonaws.com/image.jpg" } }

Sending outbound messages with media attachments to your API endpoint

For every message with a media attachement sent by an operator in your Message Center, Userlike sends an HTTP POST request to your API endpoint, which is defined by your Outbound URL.
Include an API-SECURITY-TOKEN header in the POST request and make sure to check the value against the value of your Outbound auth token.
javascript
{ "attachment": { "payload": { "url": "https://userlike-store-media-files.s3.amazonaws.com/3-8fef4c92a8264af1819454684c1ed4c7.jpg" }, "type": "image" }, "contact": { "city": null, "company": null, "contact_by_email": true, "contact_by_phone": false, "contact_by_userlike_channels": true, "contact_by_userlike_messenger": true, "country": null, "created_at": "2020-05-18T12:03:06.443Z", "email": "jsmith@example.com", "email_verified": false, "external_customer_id": null, "first_message_sent_at": "2020-05-18T12:03:06.752Z", "gender": null, "id": 36, "is_blocked": false, "is_mobile_number_verified": false, "last_message_sent_at": "2020-05-18T12:38:39.405Z", "loc_lat": null, "loc_lon": null, "locale": "en_US", "mobile_number": null, "name": "Contact jsmith@example.com", "needs_register": false, "phone_number": null, "position": null, "salutation": null, "street": null, "url_facebook": null, "url_linkedin": null, "url_profile_picture": null, "url_twitter": null, "verified": false }, "contact_identifier": "custom_id_1234", "conversation_identifier": "9d67e219-14d2-4bcc-8562-be61b41b9f43", "message": { "body": "uploaded image", "conversation_id": 71, "msgid": "71.106.347", "sent_at": "2020-05-18T12:53:15.456Z", "title": "20200514_122927.jpg", "type": "upload" }, "operator": { "about_me": "string", "display_name": "David", "id": 1, "job_title": "string", "url_image": "https://userlike-cdn-operators.s3-eu-west-1.amazonaws.com/image.jpg" } }

Python Server Example

We prepared a Python sample server that implements a sample POST request handler and a console-based input client to test the custom channel setup.
javascript
import threading import requests import urllib3 import json import pprint import sys import os import socketserver from http.server import SimpleHTTPRequestHandler urllib3.disable_warnings() CONFIG = { "contact_identifier": "customer_id_1234", "conversation_identifier": "a3238751-9b6f-41f5-b806-5a9dd793d552", "message_uuid": "92fe883c-692e-4e3e-bca0-b71b972c100f", "outbound_url": "http://localhost:8000", "outbound_auth_token": "cub8ulqcdkptd5398hm6r", "inbound_url": "https://api.userlike.com/channel/custom/v2/webhook/?uid=72cf4f88f48b3a7a8bf4d6dc5dbb979129d08c1af34d98a6f181823c486ec99a", "inbound_auth_token": "jtc0l3p2ekj5w5wwm6iuj", } HOST, PORT = CONFIG["outbound_url"].replace("http://", "").split(":") class InboundSimpleHTTPRequestHandler(SimpleHTTPRequestHandler): def do_POST(self): content_length = int(self.headers["Content-Length"]) token = self.headers["API-SECURITY-TOKEN"] body = self.rfile.read(content_length) if token != CONFIG["outbound_auth_token"]: self.send_response(401) self.end_headers() self.wfile.write("Unauthorized") else: self.send_response(200) self.end_headers() self.wfile.write("Ok") data = json.loads(body) print("Output: %s" % pprint.pformat(data)) class ThreadedHTTPServer(object): def __init__(self, host, port, request_handler=SimpleHTTPRequestHandler): socketserver.TCPServer.allow_reuse_address = True self.server = socketserver.TCPServer((host, port), request_handler) self.server_thread = threading.Thread(target=self.server.serve_forever) self.server_thread.daemon = True def __enter__(self): self.start() return self def __exit__(self, type, value, traceback): self.stop() def start(self): self.server_thread.start() def stop(self): self.server.shutdown() self.server.server_close() if __name__ == "__main__": handler = InboundSimpleHTTPRequestHandler with ThreadedHTTPServer(HOST, int(PORT), request_handler=handler) as server: while True: body = input("Input: ") data = { "message": { "body": body, "uuid": CONFIG["message_uuid"], }, "conversation_identifier": CONFIG["conversation_identifier"], "contact_identifier": CONFIG["contact_identifier"], } resp = requests.post( CONFIG["inbound_url"], json=data, headers={"API-SECURITY-TOKEN": CONFIG["inbound_auth_token"]}, verify=False, )
Custom Channel V1Custom Channel V1