Introduction
Here we will cover some tips & tricks to help you write your own routines.
Is it complicated?
Not at all. 🙂
Routines are written as scripts in python
or node.js
and managed in the Solvemate Web App. A routine expects one
or more input parameters, which the customer can enter. The script then can use these inputs to
request APIs from third-party systems.
Typically routines needs to make only 1 to 3 calls to API endpoints and they are ready to automate your service flows!
Who can code routines?
Anyone who can answer yes to the following:
- I have basic experience with
pyhton
ornode.js
- I know what HTTP methods, especially
GET
andPOST
are - I know what status codes like
200
,201
and404
mean - I know how to work with JSON objects
However, if the routine needs to handle complex business logic or the API is not REST but e.g. SOAP, the routine code will be the best handled by an experienced developer.
Environment
Available coding environments
Available routine runtimes are:
- Python 3.6
- Node.js 12.x
Routine runtime environments can be set in the Solvemate Web App in the “Edit” mode of the respective routine component.

Available libraries
Routines are executed in an isolated environment and all standard libraries are available.
In addition, all newly created routine components (created after September 2020) have more libraries available.
Currently available modules:
- Coming soon!
To use libraries in the routine code simply import them:
import requests
def handler(event, context):
response = requests.get('https://solvemate.com')
return response.status_code
// Coming soon!
Debugging
Depending on the complexity of the routine, it can be easier to develop and debug the code locally in an IDE by your preference.
To simulate user input in the form fields simply add a code that collects user input at the end of the routine.py
file and pass it to the handler
function as the event
parameter. You can specify default values after or
for
faster development.
def handler(event, context):
order_id = event['order_id']
user_email = event['email']
return user_email
# "event" simulates user input from the Widget.
if __name__ == "__main__":
order_id = input("Enter order_id:") or 'RX123456'
email = input("Enter your email:") or 'example@solvemate.com'
event = {'order_id': order_id, 'email': email}
print(handler(event, {}))
// Coming soon!
You will not be able to simulate use of Secrets locally. In case you develop routine locally and will use Secrets in the Web App the code will need to be slightly adjusted once pasted in the Solvemate Web App.
Testing
There are few things to note when it comes to testing routine code from within the Solvemate Web App:
- The input fields in Test Routine section in the routine “Edit” mode will not validate input,
- To test user input validation e.g. RegEx use the Widget,
- Routine code in the Web App can not be edited simultaneously from 2 or more accounts,
- From within Web App (either Test routine section or Widget) you will always be testing the latest changes
- To test latest changes from another page (e.g. your website or expert page) always Publish the bot
Routine basics
The routine result visible in the Widget to the customer is the returned value of handler()
. This function must always
return a result.
Make requests to API
GET
import requests
token_url = 'my.api.endpoint.com/get_token'
order_url = 'my.api.endpoint.com/orders/{order_id}'
def get_token():
...
...
return token
def handler(event, context):
order_id = event['order_id']
token = get_token()
headers = {'Authorization': token}
url = order_url.format(order_id=order_id)
# GET request with extra headers
response = requests.get(url, headers=headers)
print(response.status_code)
print(response.headers['content-type'])
print(response.text)
print(response.json())
data = response.json()
return data['best_bot']['ever']
// Coming soon!
POST
import requests
username = 'my-username'
password_cipher = b64decode(os.environ['password'])
password = boto3.client('kms').decrypt(CiphertextBlob=password_cipher)['Plaintext'].decode('utf-8')
token_url = 'my.api.endpoint.com/get_token'
def get_token():
payload = {
'username': username,
'pw': password
}
# POST with payload
response = requests.post(token_url, data=payload)
return response['access_token']
def handler(event, context):
order_id = event['order_id']
token = get_token()
..
return "Hello!"
// Coming soon!
Recommended structure
To make the routines end-user friendly, editable by non-developer and readable you might want to:
- Catch the errors and return prepared texts to the enduser
- Define all texts that the routine will return before the code in a structured way so that it can easily be edited by Chatbot manager without needing a developer
- You can define other methods outside of the
handler()
method and use them when necessary
# Define all texts before the code so that it can be easily edited by the Chatbot manager.
# Access in handler as e.g. texts['errors']['order_not_found']
texts = {
'success': {
'order_processed': "Text to display in bot for order {order_id} processed. **Formatting** can be applied. 🥳",
'order_lost': "Text to display in bot for order seems to be lost. **Formatting** can be applied."
},
'errors': {
'order_not_found': "🥺 Text to display in bot for order not found. **Formatting** can be applied.",
'could_not_obtain_token': "Can have different error texts for different cases.",
'generic_error': "Text to display in bot for generic error. **Formatting** can be applied."
}
}
import logging
import requests
my_endpoint = 'my.api.com/v2/order/{order_id}/'
# Exceptions can be specified to display more detailed texts for user where relevant
class LoginException(Exception):
pass
# For cleaner or repetitive code functions outside handler can be defined and called in handler
def perform_some_complicated_logic(var1, var2, var3):
# logic here
return 'something or nothing'
# Main function - must be defined like this. Handler must always return a result (text displayed in chatbot)
def handler(event, context):
# 'order_id' and 'post_code' matches Field Keys for customer input fields in WebApp
order_id = event['order_id']
post_code = event['post_code']
url = my_endpoint.format(order_id=order_id)
try:
# Call function outside handler
perform_some_complicated_logic(1, 2, 3)
try:
...
login_token = requests.post(token_url, data=payload)
except requests.HTTPError:
raise LoginException
return texts['success']['order_processed'].format(order_id=order_id)
except LoginException:
return texts['errors']['could_not_obtain_token']
except Exception:
return texts['errors']['generic_error']
// Coming soon!
Text formatting
The text returned by the routine visible to the customer in the Widget can be formatted the same way as throughout the Web App:
**text**
for bold*text*
for italic[link_text](url)
for a linktext\ntext
for linebreak- Insert emojis 🥳 with Emoji keyboard
* el1``* el2
for a list
Interaction with form fields
Interaction with input fields from Widget
Form fields that will appear in the Widget and collect information from the customer are set up and managed in the respective routine component.
Placeholder text, Field type (and respective validation) and Required? are relevant to display and apply user input validation in the Widget.
The respective user input in the routine code can be accessed as event['field_key']
as defined in Field Key.

def handler(event, context):
order_id = event['order_id']
user_email = event['email']
key = event['any_field_key_defined_in_input_fields']
return user_email
// Coming soon!
Optional fields
Even though typically all fields in the routine collected from the customer are mandatory there can be use cases where it makes sense to leave the field optional meaning user can leave it empty.
To avoid KeyError
for optional fields you might want to use Python Dictionary get()
method.
def handler(event, context):
# default value - None
zip = event.get('zip')
# define default value
zip_2 = event.get('zip', '12345')
return "I ❤️ Solvemate!"
// Coming soon!
Recommendations
- Always apply RegEx for user input fields where possible. It will help to not get unexpected input that the routine can’t handle correctly
- You might want to clean up and standardize user input once it’s received in the routine, e.g. make lowercase or uppercase, capitalize, remove whitespaces etc.
def handler(event, context):
order_id = event['order_id'].upper()
user_email = event['email'].lower()
key = event['any_field_key_defined_in_input_fields'].strip()
return user_email
// Coming soon!
Interaction with Secrets in Solvemate Web App
Any sensitive access information e.g. API tokens or login data for authentication with a third-party system can be stored securely in the Solvemate Web App Secrets section of the respective routine component. This prevents this information from being visible in plain text in code. After storing a secret, it cannot be read by any user.

To access and use securely stored values in the routine code they need to be decoded.
import os
import boto3
from base64 import b64decode
# os.environ['auth_token'] matches the Secret Key in Solvemate Web App
auth_token_cipher = b64decode(os.environ['auth_token'])
auth_token = boto3.client('kms').decrypt(CiphertextBlob=auth_token_cipher)['Plaintext'].decode('utf-8')
def handler(event, context):
...
headers = {'Authorization': 'Bearer %s' % auth_token,
'Content-Type': 'application/json'}
...
return "Bots are cool! 🤖"
// Coming soon!