Skip to main content
All CollectionsFor developers
App development for a Marketplace using External Lines
App development for a Marketplace using External Lines

Here is described the process of creating a marketplace app, including registration, backend development, and working with external lines

Uspacy Support Team avatar
Written by Uspacy Support Team
Updated over a month ago


App Registration

To register your app in our system, prepare and provide the following information ⬇️

{

"code": "my_custom_app_code",// unique code for your integration

"callback_url": "https://my_custom_app_host", // URL where requests for app install and uninstall will be sent (we add /portals/install and /portals/uinstall to the callback_url for these actions)

"price": "true", // whether your integration will be paid

"sns_subscription_url": null, // URL where messages from your external lines will be sent

"localized_data": [

{

"locale": "en-US",

"name": "App name",

"logo": "https://...", // link to the logo. We can store the logo on our side, its size should be 240x240 px

"short_description": "Short app description to be displayed on the marketplace page",

"description": "<div class="app-description"><div id="slider"><div class="slides"><img src="https://uspacy.github.io/static/unitalk-slider1.png" width="100%" /></div><div class="slides"><img src="https://uspacy.github.io/static/unitalk-slider2.png" width="100%" /></div><div class="slides"><img src="https://uspacy.github.io/static/binotel-slider3.png" width="100%" /></div><div class="slides"><img src="https://uspacy.github.io/static/unitalk-slider4.png" width="100%" /></div><div class="slides"><img src="https://uspacy.github.io/static/unitalk-slider5.png" width="100%" /> </div> </div>

<h5 class="title">

Expand the capabilities of your CRM by connecting it to the UniTalk IP-telephony.

</h5>

<p class="paragraph">

One of the keys to successful sales is meeting the needs of your clients. And using IP-telephony allows you to establish communication with them and, as a result, significantly improve the level of service.

</p>

<br />

<p class="paragraph">

To make calls to clients, you don't need to install additional third-party applications on your devices. Thanks to a special extension in Google Chrome, you can make incoming and outgoing calls on any browser pages, taking into account your Uspacy Space.

</p>

<img src="https://uspacy.github.io/static/unitalk-slider3.png" alt="unitalk" />

<p class="paragraph">

That is, you don't need to minimize open entity cards in the CRM to dial a client's number or launch a call campaign. The extension can be placed on top of the page and work in parallel with the client or deal database.

</p>

</div>"

}

],

"developer_name": "Uspacy", // Name of your company

"developer_link": "https://uspacy.com/" // Link to your company's website

}


💡Note: You can provide descriptions and names in multiple languages for better localization. Currently, we support the following languages ⬇️

Locale

Language

uk

Ukraine

en-US

English US

pt-BR

Portuguese

pl

Polish

es-ES

Spanish

After providing this information, you will receive a client_id and secret_id for further development.

Backend Development

We have prepared a Laravel PHP template with basic settings. Review the link

🔍 Review the link ➡️ HERE

App Installation

When a user installs the app, you will receive the following request:

POST {callback_url}/portals/install

Content-Type: application/json

{

"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",

"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",

"expiry_date": 1727072924

}


❗️We use a JWT token. To determine the portal that is installing the app, you need to parse the token and verify its authenticity. An example implementation of this logic can be found ➡️ HERE

You need to save:

  • token – valid for 1 day

  • refresh_token – valid for 30 days

  • expiry_date – to check the token's validity

It is recommended to create a cron job to update portal tokens.

🔍 An example can be found ➡️ HERE

App Uninstallation

When the app is uninstalled, you will receive:

DELETE {callback_url}/portals/uninstall

Authorization: Bearer JWT_TOKEN


💡 The token is in the Authorization header. You need to parse the token, get the domain value, and verify the token's authenticity.

Working with External Lines (EL)

Creating external line

To send messages to external lines (EL), you first need to create them. Typically, one connection has one EL. For example, in the Instagram app, the user can add multiple accounts (connections), and we create a separate EL for each of them.

POST https://{domain}.uspacy.ua/messenger/v1/external-lines

Content-Type: application/json

{

"name": "...", // unique name

"icon": "https://...", // link to the icon, this image will be displayed in the bottom right corner of the EL tab

"phoneNumber": "...", // if possible, add a number, if not, you can duplicate the value from the name field

"externalId": "..." // unique EL identifier

}

Save the identifier of the created EL, as you will need it later.

🔍 More information about EL interaction methods can be found ➡️ HERE


Creating a Chat

Next, you need to create a chat. If, for example, webhooks will be receiving events about messages, then having the identifier of the user sending these messages, you can create a chat. Then, in subsequent similar events, you will know which chat to send the processed messages from your service to the EL.

POST https://{domain}.uspacy.ua/messenger/v1/chats

Content-Type: application/json

{

"name": "...",

"type": "EXTERNAL",

"id": "...",

"externalLines": [], // add the EL ID you created earlier here

"pictureUrl": "https://...",

"meta": [ // used to link the chat to the CRM

{

"type": "phone", // options: phone/widgetUser/username/facebook/instagram/telegram/twitter/viber/whatsapp

"value": "..." // you can add a phone number or ID

}

]

}

Save the identifier of the created chat.

These are the basic required fields.

🔍 More information about chat interaction can be found ➡️ HERE

Sending Messages

If your message will contain attachments, you need to upload them first.

  1. Uploading files (if needed):

POST https://{domain}.uspacy.ua/files/v1/files

Content-Type: multipart/form-data

{

"entityType": "chat_message",

"entity_id": "...", // message identifier

"files": []

}


🔍 More information about file storage can be found ➡️ ТУТ

2. Sending a message:

POST https://{domain}.uspacy.ua/messenger/v1/messages

Content-Type: application/json

{

"id": "...",

"externalLine": "",

"chatId": "",

"type": "MESSAGE", // more about types in the documentation

"message": "Message ...",

"externalId": "...",

"externalAuthorId": "...", // ID of the message sender

"parentMessage": "...", // ID of the parent messag

"attachedFiles": [ // data of the uploaded files from the previous step

{

"id": "...",

"entityId": "...",

"lastModified": 3434434343,

"originalFilename": "...",

"size": "...",

"url": "..."

}

]

}


💡 You can use Markdown to format the message text.


🔍 More information about message manipulation can be found ➡️ HERE

3. Receiving a message event:

When you registered the app, you provided the sns_subscription_url field. We will send a webhook about the message in the EL to this URL. You need to check the event that comes in and extract only the payload. For example, you can use the nipwaayoni/laravel-aws-sns package for PHP and add the following code to the controller:

$message = Message::fromJsonString($request->getContent());

$snsMessage = new SnsMessage($message);

$messageBody = json_decode($snsMessage->content(), true);

Here is an example of a received message event with an image:

{

"data": {

"action": "create",

"domain": "demo.uspacy.com",

"entity": {

"attachedFiles": [

{

"creatorId": 3,

"entityId": "37741d88-4903-4cee-b363-6404156c6d82",

"entityType": "chat_message",

"id": 467,

"lastModified": 1727256812,

"originalFilename": "Untitled.jpg",

"size": 5422,

"uploadId": "e3eb7705-8cb8-....",

"url": "https://..."

}

],

"authorId": 3937,

"chatId": "89cc8fc3-ec6c-42ca-9310...",

"externalLine": {

"externalId": "8b64d20c-e616-4e2f...",

"icon": "https://...",

"id": "66eaf2c703bd0...",

"name": "Антон Чернов",

"phoneNumber": "Антон Чернов",

"portal": "test.uspacy.com",

"timestamp": 1726673607915

},

"id": "37741d88-4903-4cee-b363-...",

"mentioned": null,

"message": "Тестове повідомлення",

"parent": {

"active": true,

"externalLines": [

{

"externalId": "8b64d20c-e616-4e2f-b841-...",

"icon": "https://...",

"id": "66eaf2c703bd0d42...",

"name": "Антон Чернов",

"phoneNumber": "Антон Чернов",

"portal": "test.uspacy.com",

"timestamp": 1726673607915

}

],

"id": "89cc8fc3-ec6c-42ca-9310-...",

"lastMessage": {

"attachedFiles": [

{

"creatorId": 3,

"entityId": "37741d88-4903-4cee-b363-...",

"entityType": "chat_message",

"id": 467,

"lastModified": 1727256812,

"originalFilename": "Untitled.jpg",

"size": 5422,

"uploadId": "e3eb7705-8cb8-46e2-900b-...",

"url": "https://..."

}

],

"authorId": 3937,

"chatId": "89cc8fc3-ec6c-42ca-9310-...",

"externalLine": "66eaf2c703bd0d420...",

"id": "37741d88-4903-4cee-b363-...",

"mentioned": null,

"message": "Тестове повідомлення",

"readBy": null,

"relations": null,

"timestamp": 1727256825340,

"type": "MESSAGE"

},

"members": [

3937

],

"meta": [

{

"type": "instagram",

"value": "416955767636055"

}

],

"name": "Антон Чернов",

"ownerId": 3934,

"pictureUrl": "https://...",

"portals": [

"test.uspacy.com"

],

"timestamp": 1727256825340,

"type": "EXTERNAL"

},

"readBy": null,

"relations": null,

"timestamp": 1727256825340,

"type": "MESSAGE"

},

"service": "messenger",

"timestamp": "2024-09-25T09:33:45.390Z",

"user_id": 3

},

"env": "com",

"metadata": null,

"request_id": "c627affe-7a73-449c-baaf-...",

"topic": "Notifications",

"type": "message"

}

After that, we recommend checking that it is a message, and that it is from the EL:

if($messageBody['type'] === 'message' &&
isset($message['data']['entity']['externalLine']))

Next, you can find the corresponding chat in your system by the chatId identifier.

Did this answer your question?