How to connect to Microsoft Dynamics 365 as Application User in Dialogue Studio
Accessing Dynamics 365 OData requires an access token. This guide will tell you how to achieve this. In this example we will be looking up a contact.
Prerequisites
-
Microsoft Entra ID Formerly known as Azure Active Directory (or Azure AD, or AAD) App Registration:
-
Ensure the app registration includes the Dynamics CRM Customer Relationship Management, or CRM, is (usually) a software-based, data management method to deal with interactions with customers and potential customers. > user_impersonation API permissions.
-
Obtain the ClientID and ClientSecret for authentication.
-
-
Application User Setup in Microsoft Dynamics 365 CRM:
-
Navigate to Settings > Security > Users in your Dynamics 365 environment.
-
Switch to Application Users.
-
Select New and choose the app registration.
-
-
Role Assignment:
-
Assign a role to the application user. In this example, we use the SalesPerson role for managing contacts.
-
Preview
Configure
-
Login to your Dialogue Studio environment
-
Open or Create a Tab where you want to add the IVR Interactive Voice Response, or IVR, is a telephone application to take orders via telephone keypad or voice through a computer. By choosing menu options the caller receives information, without the intervention of a human operator, or will be forwarded to the appropriate Agent.
-
From the menu in the top right, select “Import” and add the following JSON.
CopyJSON[{"id":"2a332fa75d9f8d6f","type":"inject","z":"304ee00dbb1736a7","name":"Search contact","props":[{"p":"q","v":"tel:+312345678","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":260,"y":440,"wires":[["39d4eb2670a34c1b"]]},{"id":"39d4eb2670a34c1b","type":"change","z":"304ee00dbb1736a7","name":"Configure Dynamics 365 configurations","rules":[{"t":"set","p":"client_id","pt":"msg","to":"","tot":"str"},{"t":"set","p":"client_secret","pt":"msg","to":"","tot":"str"},{"t":"set","p":"resource","pt":"msg","to":"","tot":"str"},{"t":"set","p":"tenant_id","pt":"msg","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":440,"wires":[["2ec7fc42f0c43d66"]]},{"id":"d8bab703db26dbde","type":"comment","z":"304ee00dbb1736a7","name":"Todo: Enter clientId, clientSecret, tenantId and resource","info":"","x":560,"y":400,"wires":[]},{"id":"ab73eb0faf38091f","type":"group","z":"304ee00dbb1736a7","name":"Dynamics 365 Access Token","style":{"label":true},"nodes":["2ec7fc42f0c43d66","fc310660292a7f22","b9346b1ccb47ceab","0ff7bfeb87c03d55","d4a4c5921226c0c7"],"x":694,"y":419,"w":712,"h":162},{"id":"2ec7fc42f0c43d66","type":"function","z":"304ee00dbb1736a7","g":"ab73eb0faf38091f","name":"Check Token Validity","func":"const currentTime = Math.floor(Date.now() / 1000);\nconst dynamics365 = flow.get('dynamics365') || {};\nconst tokenExpiry = dynamics365.tokenExpiry || 0;\nif (currentTime < tokenExpiry) {\n msg.token = dynamics365.accessToken;\n return [msg, null];\n} else {\n return [null, msg];\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":460,"wires":[["fc310660292a7f22"],["b9346b1ccb47ceab"]]},{"id":"fc310660292a7f22","type":"function","z":"304ee00dbb1736a7","g":"ab73eb0faf38091f","name":"Use Existing Token","func":"msg.headers = {\n 'Authorization': 'Bearer ' + msg.token,\n 'Content-Type': 'application/json'\n};\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1290,"y":460,"wires":[["d898aaa71ac7f314"]]},{"id":"b9346b1ccb47ceab","type":"function","z":"304ee00dbb1736a7","g":"ab73eb0faf38091f","name":"Prepare Token Request","func":"msg.payload = {\n grant_type: 'client_credentials',\n client_id: msg.client_id,\n client_secret: msg.client_secret,\n resource: msg.resource\n};\nmsg.headers = {'Content-Type': 'application/x-www-form-urlencoded'};\nmsg.url = 'https://login.microsoftonline.com/' + msg.tenant_id + '/oauth2/token';\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":540,"wires":[["0ff7bfeb87c03d55"]]},{"id":"0ff7bfeb87c03d55","type":"http request","z":"304ee00dbb1736a7","g":"ab73eb0faf38091f","name":"Get Token","method":"POST","ret":"obj","paytoqs":"body","url":"","tls":"","persist":false,"proxy":"","authType":"","x":1030,"y":540,"wires":[["d4a4c5921226c0c7"]]},{"id":"d4a4c5921226c0c7","type":"function","z":"304ee00dbb1736a7","g":"ab73eb0faf38091f","name":"Store Token and use New Token","func":"const dynamics365 = {\n accessToken: msg.payload.access_token,\n tokenExpiry: parseInt(msg.payload.expires_on)\n};\nflow.set('dynamics365', dynamics365);\n\nmsg.token = msg.payload.access_token;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1250,"y":540,"wires":[["d898aaa71ac7f314"]]},{"id":"ee24c59e4685f1f6","type":"group","z":"304ee00dbb1736a7","name":"Dynamics365 Contact Search","style":{"label":true},"nodes":["d898aaa71ac7f314","9d623753bce29c79"],"x":1414,"y":499,"w":432,"h":82},{"id":"d898aaa71ac7f314","type":"function","z":"304ee00dbb1736a7","g":"ee24c59e4685f1f6","name":"Prepare Contact Request","func":"const d365_url = msg.resource;\nconst token = msg.token;\nlet q = msg.q;\nlet d365_odata = '';\n\nif (q.match(/^(sip:\\/+)|^(tel:)|(user=phone)$/)) {\n let startindex = q.indexOf(\"+\") + 1;\n let endindex = q.lastIndexOf(\"@\");\n if (endindex === -1) {\n q = q.substring(startindex);\n } else {\n q = q.substring(startindex, endindex);\n }\n q = \"%\" + q.slice(-9).split('').join('%') + \"%\";\n d365_odata = `${d365_url}/api/data/v9.1/contacts?$filter=contains(mobilephone,'%${q}%') or contains(telephone1,'%${q}%')`;\n} else if (q.match(/^sip:/)) {\n let startindex = q.indexOf(\":\") + 1;\n let endindex = q.lastIndexOf(\";\");\n if (endindex === -1) {\n q = q.substring(startindex);\n } else {\n q = q.substring(startindex, endindex);\n }\n d365_odata = `${d365_url}/api/data/v9.1/contacts?$filter=emailaddress1 eq '${q}'`;\n} else {\n d365_odata = '';\n}\n\nmsg.url = d365_odata;\nmsg.headers = {\n \"Authorization\": `Bearer ${token}`,\n \"Accept\": \"application/json\",\n \"OData-MaxVersion\": \"4.0\",\n \"OData-Version\": \"4.0\"\n};\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1550,"y":540,"wires":[["9d623753bce29c79"]]},{"id":"9d623753bce29c79","type":"http request","z":"304ee00dbb1736a7","g":"ee24c59e4685f1f6","name":"Get Contact","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1750,"y":540,"wires":[["3b89414774e84815"]]},{"id":"11409e384fa77721","type":"group","z":"304ee00dbb1736a7","name":"Validate Output","style":{"label":true},"nodes":["e1258e7717e1aa3c","3d5fa796dfabbc6c","71df25f36f704ba9","3b89414774e84815","147ad7a88b7f72ef","aacf71e21e51c215","d15b384d02edeeb9"],"x":1854,"y":379,"w":972,"h":242},{"id":"e1258e7717e1aa3c","type":"switch","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"Check if any results","property":"msg.payload.value[0]","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":2190,"y":480,"wires":[["aacf71e21e51c215"],["3d5fa796dfabbc6c"]]},{"id":"3d5fa796dfabbc6c","type":"debug","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"no results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2620,"y":500,"wires":[]},{"id":"71df25f36f704ba9","type":"debug","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"Returned single contact","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.value[0]","targetType":"msg","statusVal":"","statusType":"auto","x":2670,"y":460,"wires":[]},{"id":"3b89414774e84815","type":"switch","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"Check statusCode","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1970,"y":540,"wires":[["e1258e7717e1aa3c"],["147ad7a88b7f72ef"]]},{"id":"147ad7a88b7f72ef","type":"debug","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"Failed to retreive information","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2220,"y":580,"wires":[]},{"id":"aacf71e21e51c215","type":"switch","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"Check if multiple results","property":"msg.payload.value[1]","propertyType":"msg","rules":[{"t":"nnull"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":2410,"y":460,"wires":[["d15b384d02edeeb9"],["71df25f36f704ba9"]]},{"id":"d15b384d02edeeb9","type":"debug","z":"304ee00dbb1736a7","g":"11409e384fa77721","name":"Multiple results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.value","targetType":"msg","statusVal":"","statusType":"auto","x":2640,"y":420,"wires":[]}]
-
Open Change node (labeled Configure Dynamics 365 configurations) and fill in:
-
client_id = Can be copied from the App Registration > Overview page.
-
client_secret = Can be created and copied from the App Registration > Manage > Certificate & secrets
-
resource = The url of your Dynamics 365 environment, for example: https://tenant.crm.dynamics.com
-
tenant_id =Can be copied from the App Registration > Overview page.
-
-
Open the Inject node (labeled Search contact) and enter either the email or phonenumber of the contact you want to search.
-
To test your API, click on the blue box before Inject node
-
You can use the debug menu to see the output and which debug node it entered.
Explanation
Trigger Flow
Action:
The flow is triggered by an inject node labeled “Search contact” which initiates the process with a predefined query.
What it does:
This node starts the flow by injecting a message with a property q set to the phone number tel:+31621933407. This is the initial step to search for a contact in Dynamics 365.
Configure Dynamics 365 Variables
Action:
The “Configure Dynamics 365 configurations” change node sets up the necessary configuration variables for Dynamics 365.
What it does:
This node assigns values to client_id
, client_secret
, resource
, and tenant_id
in the message object. These variables are essential for authenticating and accessing Dynamics 365 services.
Dynamics 365 Access Token
Action:
This group of nodes handles the process of checking for an existing access token and requesting a new one if necessary.
What it does:
-
Check Token Validity: Checks if the current token is still valid. If valid, it uses the existing token; otherwise, it proceeds to request a new one.
-
Use Existing Token: If the token is valid, it sets the authorization header with the existing token.
-
Prepare Token Request: If the token is not valid, it prepares the payload and headers for a token request.
-
Get Token: Sends an HTTP POST request to obtain a new access token.
-
Store Token and use New Token: Stores the new token and its expiry time in the flow context and sets the authorization header with the new token.
Dynamics 365 Contact Search
Action:
This group of nodes prepares and sends a request to search for a contact in Dynamics 365.
What it does:
-
Prepare Contact Request: Constructs the URL for the Dynamics 365 API to search for contacts based on the query. It formats the query to match the expected format for phone numbers or email addresses.
-
Get Contact: Sends an HTTP GET request to the constructed URL to retrieve contact information.
Validate Output
Action:
This group of nodes validates the response from the Dynamics 365 API and processes the results.
What it does:
-
Check statusCode: Verifies if the HTTP response status code is 200 (OK). If not, it logs an error.
-
Check if any results: Checks if the response contains any contact data.
-
Check if multiple results: Determines if the response contains multiple contacts.
-
Debug nodes: These nodes log the results to the debug sidebar for inspection. They handle scenarios where no results, a single result, or multiple results are returned.