Third-Party Messenger Developer Guide
User Account Management and Message Routing
When a user sends a message through an External System that reaches Flowable, the following checks are made:
- does a
UserAccount
exist for the providedexternalUserId
andexternalSystemId
? - is the
UserAccount
active for the providedexternalUserId
andexternalSystemId
? - is there an (only one) open conversation available for this
userAccountId
Only then is the message routed to the right conversation based on the conversation matching this userAccountId
.
Therefore, during the onboarding procedure a User Account needs to be created if it doesn't exist.
Create User Account
In case no UserAccount
is found for the given externalUserId
and externalSystemId
then the following method is executed:
InboundMessageAccountService.messageReceivedNoAccount(InboundMessage inboundMessage)
Out of the box Flowable sends an internal system event _flowableEngageMessageReceivedNoAccount
that can be
listened to and handled with the Event Registry within Cases or Processes. However, this interface can be implemented to your own needs.
In a client onboarding lifecycle case all the required checks can be done to create the required information to handle the message, such as:
- Checking if there is already a user available for this
externalUserId
or is the creation of a new user required? - Creating the user account for this user with the required information (
externalUserId
andexternalSystem
(WhatsApp, WeChat or LINE)) - Defining the type of conversation which needs to be created for this user (group conversation or private conversation with one user) (including participant selection)
- Creation of a conversation that is linked to the
UserAccount
To create a User Account, first a User Account Definition is required that is referenced by your user account.
You can configure your User Account Definition by adding a <filename>.user.account.json
file in your classpath under com/flowable/user-accounts/default
or com/flowable/user-accounts/custom
.
In this file you can configure multiple user accounts in a JSON-based format. An example of a user account could be:
[
{
"key": "default-messenger-user-account",
"name": "Default messenger user account",
"description": "A default user account for external messenger users like ones from WhatsApp, WeChat, LINE, etc",
"initialState": "inactive",
"initialSubState": "onboarding",
"initialType": "messenger"
}
]
This user account example sets the user account type to messenger, the initial status to inactive and the sub state to onboarding.
Now, a User Account can be created by executing following Java code when required:
UserAccount userAccount = userAccountService.createNewUserAccountBuilder()
.name(name)
.userId(userId)
.externalUserId(externalUserId)
.businessAccountId(businessAccountId)
.userAccountDefinitionKey("default-messenger-user-account")
.subType(externalSystem)
.save();
Next, a conversation needs to be created where the above UserAccount
is going to be linked with.
Conversation conversation = conversationService.createConversationBuilder()
.ownerId(externalUserParticipant)
.addParticipant(externalUserParticipant)
.addParticipant(participant1)
.addParticipant(participant2)
.name(conversationName)
.type(ConversationTypes.GM)
.start();
Lastly, the Conversation
needs to be linked with the UserAccount
as following:
conversationService.linkConversationWithUserAccount(conversation.getId(), userAccount.getId());
Please note, that only one UserAccount can be linked with a conversation.
Handle Inactive User Accounts
An inactive user account is determined by there being no link to a userId
or by status
flag.
It is possible to set the status
of an UserAccount
to INACTIVE
.
In this cases the following method can be executed:
InboundMessageAccountService.messageReceivedInactiveAccount(InboundMessage inboundMessage, UserAccount userAccount)
Also here, in the default implementation (LoggingInboundMessageAccountService
) this method just gives a warning that the user account is not active and the message is ignored.
Same, as above, this interface can be implemented to your own needs and, for instance, the client onboarding lifecycle case can be searched and notified (for example, by triggering a listener) that a new message has been received from an inactive user account.
Template Messages Configuration
There are two formats for messages that can be sent via the WhatsApp Business API or the WeChat Business Account:
- Free text & multimedia messages can be sent within a time window (WhatsApp: 24 hours, WeChat: 48 hours) after the external user sends a message to the business. The response messages form the business within this time window are free.
- Template messages (predefined messages) must be used when the time window (after 24 / 48 hours) has expired.
Sending Template Messages with WhatsApp
It is really important to mention that each template needs to be configured and approved by WhatsApp (see WhatsApp Template Documentation).
Once templates have been configured for the WhatsApp Business Account they can be used within your application.
Flowable supports WhatsApp templates and can be sent by following code:
whatsAppExternalConversationSystemAdapter.createTemplateMessageBuilder()
.externalUserId(externalUserId)
.templateId(whatsAppTemplateCode)
.addTemplateTextParameters(templateParameters)
.language(templateLanguage)
.messageId(externalMessageId)
.send();
The whatsAppExternalConversationSystemAdapter
is a bean that can be Autowired in your Spring Boot application.
As sending templates over WhatsApp costs money, the WhatsApp Adapter supports you to deactivate the actual sending of template messages in the WhatsApp Adapter, and only logging them by setting this property:
flowable.adapter.whatsapp.jms.template-message-logging-only=true
This is recommended only for development environments.
Sending Template Messages with WeChat
Templates also need to be pre-configured in your WeChat Business Account and they can also be used within your application.
Flowable supports WeChat templates and can be sent by following code:
WeChatOutboundTemplateMessageDto outboundMessageDto = new WeChatOutboundTemplateMessageDto();
outboundMessageDto.setExternalUserId(externalUserId);
outboundMessageDto.setTemplateId(weChatTemplateCode);
outboundMessageDto.setParameters(templateParameters);
outboundMessageDto.setMessageId(externalMessageId);
jmsMessagingOperations.convertAndSend(weChatProperties.getOutbound(), outboundMessageDto);
The jmsMessagingOperations
and the weChatProperties
are beans that can be Autowired in your Spring Boot application.
Query Templates and Expose Templates to Flowable Internal Users
Please consider that if you send such templates, they will only be visible in the external account. If you want to make the processed template content visible also to your Flowable conversation you need to:
- process the template content manually and set it as your message content
- send a Message of the type
TEMPLATE
and subtypeEXTERNAL_TEMPLATE
to the conversation.
For processing the template content it is necessary to deploy the template content as Template Variation within the Flowable Template Engine.
To process the template content now you can use the Template Engine like this:
Map<String, Object> variant = MapUtil.mapOf("templateCode", templateCode);
TemplateProcessingResult processingResult = templateService.processTemplate(templateKey, variant, payload);
The bean templateService
can also be Autowired in this case.
The template model with the key templateKey
requires a variation parameter based on templateCode
and multiple variations for the different templateCode
for each template message. Furthermore, a payload can be passed to the template engine to process the data in case of parameter-based template messages.
At this point the processed template is available and needs to be sent to the conversation for the internal Flowable Users.
Sending a message of the type TEMPLATE
and subtype EXTERNAL_TEMPLATE
ensures that this message is only forwarded to the internal users and not to the external user. Thus, the client will only receive one template message from the official external system Business API.
To send such a message this code can be implemented:
Message internalMessage = messageService.createMessageBuilder()
.mainContent(processingResult.getProcessedContent())
.type(DataTypes.TEMPLATE)
.subType(DataSubTypes.EXTERNAL_TEMPLATE)
.send(conversation.getId());
By making this template available in the template engine, these template variations can now be queried, visualised and selected within a Form.
Mapping of External Templates with Flowable Templates
The templateCodes
provided by the external system are not very human-readable. Therefore, Flowable enables a name mapping through the application properties between external system templates and internal template variations templateCode
.
flowable.external-system.whatsapp.templates.myInternalKey1=myExternalWhatsAppTemplateKey1
flowable.external-system.whatsapp.templates.myInternalKey2=myExternalWhatsAppTemplateKey2
flowable.external-system.whatsapp.templates.myInternalKeyN=myExternalWhatsAppTemplateKeyN
flowable.external-system.wechat.templates.myInternalKey1=myExternalWeChatTemplateKey1
flowable.external-system.wechat.templates.myInternalKey2=myExternalWeChatTemplateKey2
flowable.external-system.wechat.templates.myInternalKeyN=myExternalWeChatTemplateKeyN
The mapping feature comes quite handy when you have several external systems integrated at the same time with the same template content. In the above example the myInternalKey1
is the same key independent of the external system.
Now, with the beans whatsAppProperties
or weChatProperties
, the external templateCode
can be retrieved and used for the external system as described above (weChatTemplateCode
, whatsAppTemplateCode
).
String weChatTemplateId = weChatProperties.getTemplates().get(templateCode);
String whatsAppTemplateId = whatsAppProperties.getTemplates().get(templateCode);
Handle the Template-Window (24 Hours / 48 Hours)
Flowable provides the possibility to configure timeout handling processes in order to proactively show to the internal user that the free text & multimedia messages time window has expired.
flowable.external-system.whatsapp.timeout.enabled=true
flowable.external-system.whatsapp.timeout.processDefinitionKey=P98_WhatsAppTimeoutProcess
flowable.external-system.whatsapp.timeout.rescheduleDuration=PT24H
flowable.external-system.wechat.timeout.enabled=true
flowable.external-system.wechat.timeout.processDefinitionKey=P99_WeChatTimeoutProcess
flowable.external-system.wechat.timeout.rescheduleDuration=PT48H
The timeout handling can be enabled for each external system separately by the enabled
property.
This starts a process instance of the defined processDefinitionKey
(if it was not yet started) to handle the timeout.
Within the process it is necessary to model an intermediate timer event that waits for the timeout to expire. The process can then proactively handle the timeout (for example, by notifying the user that the timeout for the external user has expired). Usually, sticky action messages are instantiated to the conversation.
Every time a new message from the external user reaches Flowable, either:
- a new process instance is created when a process instance is not yet running for this
User Account
. - the timer of the currently running process instance is rescheduled by the duration configured by the
rescheduleDuration
property.
The
rescheduleDuration
needs to be a textual representation of aDuration
as described here.
Activation of Message Types
As soon as a message reaches Flowable, the platform first checks if the message type is marked as supported. Support for different message types can be activated and deactivated by setting the following properties to true and false.
In all cases where the message type is not enabled, Flowable returns a message to the external user that this message cannot be delivered.
WhatsApp Message Type Configuration
flowable.external-system.whatsapp.enabled-message-types.text=true
flowable.external-system.whatsapp.enabled-message-types.image=true
flowable.external-system.whatsapp.enabled-message-types.video=true
flowable.external-system.whatsapp.enabled-message-types.document=true
flowable.external-system.whatsapp.enabled-message-types.audio=true
flowable.external-system.whatsapp.enabled-message-types.voice=true
Handling of WhatsApp Content
Prerequisite for handling WhatsApp content is the related WhatsApp Adapter Configuration.
When sending content (such as image, video, document, audio and voice messages) the WhatsApp Business API will not send the plain content to the WhatsApp Adapter, but only an ID of the content that needs to be retrieved from the WhatsApp Business API (see WhatsApp Webhook Documentation).
Now, the WhatsApp Adapter notifies Flowable that such a message arrived. Flowable will check if it’s a supported message type and if it is, then Flowable will need to request the content data from the WhatsApp Adapter. The WhatsApp Adapter retrieves the content from the WhatsApp Client and returns it to Flowable.
WhatsApp On Premise
When receiving content the WhatsApp Adapter is notified from the WhatsApp Business API and sends a JMS message to Flowable.
Flowable will then retrieve the content item through the /whatsapp-api/content
endpoint of the Whatsapp Adapter.
WhatsApp Cloud
In cloud mode, the Flowable WhatsApp Adapter communicates directly to the WhatsApp Servers.
When receiving content the WhatsApp Adapter is notified from the WhatsApp Business API and sends a JMS message to Flowable.
Flowable will then retrieve the content item through the /whatsapp-api/content
endpoint of the Whatsapp Adapter.
WeChat Message Types Configuration
flowable.external-system.wechat.enabled-message-types.text=true
flowable.external-system.wechat.enabled-message-types.image=false
flowable.external-system.wechat.enabled-message-types.video=false
flowable.external-system.wechat.enabled-message-types.document=false
flowable.external-system.wechat.enabled-message-types.audio=false
flowable.external-system.wechat.enabled-message-types.voice=false
Currently, only text and image messages are supported for the WeChat Adapter.
LINE Message Type Configuration
flowable.external-system.line.enabled-message-types.text=true
flowable.external-system.line.enabled-message-types.image=true
flowable.external-system.line.enabled-message-types.video=true
flowable.external-system.line.enabled-message-types.audio=true
flowable.external-system.line.enabled-message-types.voice=true
Handling of LINE Content
Prerequisite for handling LINE content is the related LINE Adapter Configuration.
When sending content (such as image, video, document, audio and voice messages) the LINE API will not send the plain content to the LINE Adapter, but only an ID of the content that needs to be retrieved from the LINE API (see LINE Webhook Documentation).
Now, the LINE Adapter notifies Flowable that such a message arrived. Flowable will check if it’s a supported message type and if it is, then Flowable will need to request the content data from the LINE Adapter. The LINE Adapter retrieves the content from the LINE Rest API and returns it to Flowable.
When receiving content the LINE Adapter is notified from the LINE API and sends a JMS message to Flowable.
Flowable will then retrieve the content item through the /line-api/content
endpoint of the LINE Adapter.
The flow for receiving content from LINE to Flowable is similar as the flow when receiving content from WhatsApp to Flowable.
Handling of Flowable to LINE Content
The way content is send to LINE differs to the way it is done with WhatsApp.
The way LINE works is by sending the URLs of the location of the content and then the LINE mobile application directly accesses the content through those URLs.
This means that the request for retrieving the content is done from the user devices.
Therefore, we created a mechanism that will generate a special token that allows accessing the specific content through a publicly accessible API for a limited period.
There is a hook point (LineOutboundMediaContentProvider
) which allows implementors to provide a different way of making the content publicly accessible.
Due to this you have to explicitly enable the outbound content sending from Flowable to LINE.