Query live contact data from Microsoft Dynamics 365 into a WhatsApp chatbot using the Dataverse Web API v9.2 and OData queries. When a customer messages you, look up their CRM record by mobile number and reply with their account context.
Before following this guide, read the External API Request step foundation guide. It covers every field in the step interface so this guide can focus on Dynamics 365-specific values.
Connecting Dynamics 365 to any external system requires registering an app in Microsoft Entra ID (Azure AD). This needs Global Administrator or Application Administrator permission in your Microsoft tenant. If you are not the IT admin, share this guide with them. The app registration is a one-time task, after which the WA.Expert steps are self-serve.
Dynamics 365 uses Microsoft Entra ID (formerly Azure AD) for authentication. Unlike other CRMs with a simple API key, Dynamics uses the OAuth 2.0 client credentials flow, in two steps each time the token expires:
| Step | What happens | Result |
|---|---|---|
| 1. Register app | Create an app registration in Azure portal with Dynamics CRM permission | Get Tenant ID, Client ID, Client Secret |
| 2. Get token | POST to Microsoft identity platform token endpoint | Get access_token (valid ~1 hour) |
| 3. Call API | GET to your Dynamics org Web API endpoint with Bearer token + OData headers | Get contact data in JSON |
Access tokens expire after approximately 1 hour. For a production WhatsApp bot, set up a refresh step that re-requests the token before it expires, similar to the Zoho CRM approach in our Zoho guide.
This is the one-time IT admin task. All steps happen in the Azure portal.
Go to Dynamics 365 Settings > Customisation > Developer Resources. Your Web API URL is shown there, for example https://YOUR_ORG.api.crm.dynamics.com/api/data/v9.2. Note the exact URL including the region suffix (crm, crm4, crm7, etc.).
With the Tenant ID, Client ID, and Client Secret from Step 1, request an access token. This is an External API Request step in WA.Expert, set up as a separate step before the contact lookup:
POST https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
Body (form fields):
grant_type = client_credentials
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
scope = https://YOUR_ORG.crm.dynamics.com/.default
Response:
{
"token_type": "Bearer",
"expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6..."
}
Store access_token as {{dynamics_token}}.
It is valid for ~1 hour. Refresh before it expires.With the token stored as {{dynamics_token}} and the customer's phone as {{customer_phone}}, add a second External API Request step for the contact lookup:
| Field | Value | Notes |
|---|---|---|
| Select Method | GET | Querying contact records. |
| Request URL | https://YOUR_ORG.api.crm.dynamics.com/api/data/v9.2/contacts?$select=...&$filter=mobilephone eq '{{{customer_phone}}}' | Replace YOUR_ORG with your Dynamics organisation name. $select lists the fields to return. $filter applies the OData condition. |
| Authorization | Bearer {{{dynamics_token}}} | The token from Step 2. Include 'Bearer ' prefix. The token is stored in the chatbot variable {{dynamics_token}}. |
| OData-MaxVersion | 4.0 | Required by Dynamics 365 Web API. Without this, the request may fail. |
| OData-Version | 4.0 | Required pair with OData-MaxVersion. Both headers are mandatory. |
| Accept | application/json | Request JSON response format. |
| Select Body Type | None | GET requests carry no body. |
| Choose Response Type | JSON | Dynamics returns structured JSON. |
Microsoft's Dataverse Web API requires OData-MaxVersion: 4.0 and OData-Version: 4.0 on every request. No other CRM in this series requires these headers. Omitting them may return a 400 error or unpredictable behaviour.
| What you want | OData $filter value |
|---|---|
| Contact by mobile phone | mobilephone eq '9820000001' |
| Contact by business phone | telephone1 eq '9820000001' |
| Contact by email | emailaddress1 eq 'priya@example.com' |
| Contact by full name | fullname eq 'Priya Sharma' |
| Phone or mobile | mobilephone eq '9820000001' or telephone1 eq '9820000001' |
Dynamics 365 stores phone numbers in three fields: mobilephone (Mobile Phone), telephone1 (Business Phone), telephone2 (Home Phone). Match the field where your sales team stores WhatsApp numbers.
A successful Dynamics 365 contact response looks like this:
{
"@odata.context": "https://YOUR_ORG.api.crm.dynamics.com/api/data/v9.2/$metadata#contacts(...)",
"value": [
{
"fullname": "Priya Sharma",
"firstname": "Priya",
"lastname": "Sharma",
"emailaddress1": "priya@example.com",
"mobilephone": "9820000001",
"jobtitle": "Procurement Manager",
"contactid": "81716234-9628-ed11-9db1-000d3a320482",
"@odata.etag": "W/\"1146759\""
}
]
}Dynamics wraps all results in a 'value' array, even for a single match. Map variables using value[0].fieldname. If the value array is empty, no contact matched the phone number. Add a conditions branch for that case.
Map these response paths to variables in the External API Request step:
| Variable name | Response path | Example value |
|---|---|---|
| full_name | value[0].fullname | Priya Sharma |
| first_name | value[0].firstname | Priya |
| value[0].emailaddress1 | priya@example.com | |
| job_title | value[0].jobtitle | Procurement Manager |
| contact_id | value[0].contactid | 81716234-9628-ed11-9db1-000d3a320482 |
@odata.context and @odata.etag are Dynamics metadata fields. Ignore them. Your contact data is in the named fields alongside them.
If the contact messaged you first within the last 24 hours, your reply is a free service conversation. For proactive outbound messages from Dynamics data, use an approved Utility or Marketing template.
Customer messages your WhatsApp number.
WA.Expert captures their phone as {{customer_phone}} = "9820000001".
Step 1 — Get token:
POST https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token
Body: grant_type=client_credentials&client_id=...&client_secret=...
&scope=https://YOUR_ORG.crm.dynamics.com/.default
Store: dynamics_token = "eyJ0eXAiOiJKV1Qi..."
Step 2 — Look up contact:
GET https://YOUR_ORG.api.crm.dynamics.com/api/data/v9.2/contacts
?$select=fullname,jobtitle,emailaddress1
&$filter=mobilephone eq '9820000001'
Authorization: Bearer {{dynamics_token}}
OData-MaxVersion: 4.0
OData-Version: 4.0
Response mapped:
full_name = "Priya Sharma"
job_title = "Procurement Manager"
email = "priya@example.com"
Conditions: if value is empty → "We could not find your record."
Bot replies (if found):
"Hi Priya Sharma, welcome.
Role: Procurement Manager
Account manager will be in touch shortly.
Reply URGENT for immediate attention."| Symptom | Likely cause | Fix |
|---|---|---|
| AADSTS error on token request | Wrong tenant ID, client ID, or client secret | Copy values exactly from the Azure portal App registrations page. Check for trailing spaces or line breaks in the client secret. |
| 401 Unauthorized on API call | Token expired or Application User not set up | Re-request the token (Step 2). Also ensure you created an Application User in Dynamics 365 Settings > Security > Users linked to the same Client ID. |
| 403 Forbidden | Application User lacks the required security role | In Dynamics 365 admin, assign a security role to the Application User that grants read access to the Contact entity. |
| Empty value array | Phone number format mismatch | Check exactly how phone numbers are stored in Dynamics (with country code, with spaces, etc.). Try telephone1 instead of mobilephone. Or use contains(mobilephone,'9820000001'). |
| OData headers error | Missing OData-MaxVersion or OData-Version headers | Add both OData-MaxVersion: 4.0 and OData-Version: 4.0 to the header parameters. Both are required by the Dynamics Web API. |
| scope parameter wrong | Scope must end with /.default | The scope must be the full Dynamics org URL plus /.default, for example https://YOUR_ORG.crm.dynamics.com/.default. Do not use other scope values. |
Salesforce: two-step auth with login.salesforce.com, SOQL queries.
Read guide →HubSpot CRM with Private App Bearer token: simpler single-step auth.
Read guide →Master every field in WA.Expert's HTTP action step.
Read foundation guide →Free trial, no credit card required. And if you ever get stuck, we are the only platform in India that answers you live on WhatsApp.