> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamics 365 troubleshooting

> Debug Dynamics 365 credential verification, incident creation, Omnichannel routing, and contact linking issues.

Before debugging, have this ready:

* The **Azure tenant ID**, **client ID**, and **environment URL** you used to save the integration.
* A specific <Tooltip tip="OpenCX's identifier for a single conversation thread. Visible in the Inbox URL and used as the trace key back to Dynamics records.">session ID</Tooltip> (from [Inbox](https://platform.open.cx/inbox)) or a Dynamics incident/ticket number where the problem shows.
* Admin access to both **Azure AD** and the **Power Platform admin center**.

<Tip>
  Re-run **Test & Save Configuration** in [Settings → Integrations](https://platform.open.cx/settings/integrations) → Dynamics 365 as your first step. It runs the token exchange plus a `GET /incidents` probe and tells you which half is failing.
</Tip>

<Info>
  **Fastest path to root cause:** open the failing OpenCX session and check `meta` for `dynamics365_incident_id` (or `dynamics365_chat_id`). If the field exists, the record was created in Dynamics — the problem is on the Dynamics side (assignment, routing). If not, the handoff itself failed — scope to credentials or the routing rule.
</Info>

## Common scenarios

Jump to the symptom that matches what you're seeing.

### **"Failed to verify credentials" on save**

<Tooltip tip="Usually a typo in the tenant ID, an expired client secret, or admin consent that was never granted on the Dynamics CRM permission.">Likely cause</Tooltip>: wrong tenant ID, expired secret, or missing admin consent.

**Fix:** paste the **Directory (tenant) ID** from the Azure app's **Overview** page (not the object ID, not the application ID). Regenerate the client secret in **Certificates & secrets** if yours is past its expiry. In **API permissions**, confirm `Dynamics CRM → user_impersonation` is added **and** shows **Granted for `<tenant>`**.

### **Test & Save fails even though the client secret is brand new**

<Tooltip tip="The token is issued against the scope `{environment_url}/.default`. Any mismatch between the environment_url you pasted and the actual Dynamics org URL breaks the audience check.">Likely cause</Tooltip>: environment URL typo or trailing slash.

**Fix:** paste the URL exactly as shown in your browser up to `.dynamics.com` — no path, no trailing slash. Example: `https://yourorg.crm.dynamics.com`.

### **Token exchange succeeds, but the Dynamics probe returns 401**

<Tooltip tip="Most common cause: Azure AD app exists and the secret works, but no Dataverse Application User is linked to it, so Dynamics has no user to authenticate as.">Likely cause</Tooltip>: <Tooltip tip="A Dataverse user that represents a non-interactive app. OpenCX authenticates as this user on every Dynamics API call.">Application User</Tooltip> not created, or not mapped to the Azure AD app.

**Fix:** open the [Power Platform admin center](https://admin.powerplatform.microsoft.com/environments) → your env → **Settings → Users + permissions → Application users**. Confirm there's a row for your Azure AD app (search by client ID). If not, create one. See [Azure & Dynamics Setup, step 4](/integrations/dynamics-365/azure-setup).

### **Token exchange succeeds, Dynamics probe returns 403**

<Tooltip tip="The Application User exists but has no security role, or the role is missing Read on the Incident entity. Without a role, Dataverse treats the user as having zero privileges.">Likely cause</Tooltip>: Application User has no security role, or the role lacks Incident privileges.

**Fix:** on the Application User row, click **Manage roles** and attach one. **System Administrator** is the simplest path; for a least-privilege setup give the role `Read` on Contact/Queue/SystemUser and `Create + Write + Append To` on Incident and Annotation. If you're using chat channels, add `Create` on Omnichannel Live Work Item.

### **Incident created but unassigned**

<Tooltip tip="OpenCX writes the incident with whatever owner/queue you configured in settings. Leave both blank and Dynamics falls back to whatever routing rules you have (if any) or leaves the record unassigned.">Likely cause</Tooltip>: `default_owner_id` and `default_queue_id` both blank, and no Dynamics routing rule catches the record.

**Fix:** set `default_queue_id` in the OpenCX Dynamics settings to the queue your reps work from, and optionally `default_owner_id` for a specific systemuser. Either one (or a Dynamics routing rule) has to claim the incident for it to show up in a rep's view.

### **Incident created but not in the expected queue**

<Tooltip tip="The Application User needs Append To privilege on the target queue. Without it, the @odata.bind to /queues(id) fails silently on the Dynamics side and the incident stays in the default queue.">Likely cause</Tooltip>: security role missing `Append To` on the queue.

**Fix:** in Dynamics, open the security role attached to the Application User and tick `Append To` on **Queue** at the required scope (usually Business Unit).

### **Contact not linked on the incident**

<Tooltip tip="OpenCX looks up the Dynamics contact by emailaddress1. When the session has no email (anonymous widget, SMS-only, phone without captured email), OpenCX can't find or create a contact and the incident lands unlinked.">Likely cause</Tooltip>: session has no email.

**Fix:** gate handoff behind user-data collection so the AI captures an email before routing to Dynamics (in the widget, set `collectUserData: true` — see [widget configuration](/widget/configuration)). For SMS/phone, collect the email in-flow. Reps can also link a contact manually after the fact from the incident view.

### **Chat handoff from web/SMS/WhatsApp lands as an incident instead of Omnichannel**

<Tooltip tip="The routing rule checks session.channel against a fixed list: ['web', 'sms', 'whatsapp']. Anything else — including custom channel strings set by third-party dispatchers — falls through to the incident path.">Likely cause</Tooltip>: the session's `channel` value isn't one of `web`, `sms`, or `whatsapp`.

**Fix:** check the OpenCX session's `channel` field. If you're dispatching through a custom integration, confirm it sets one of the three supported strings; anything else routes to the incident path by design.

### **Omnichannel live work item never appears in the Agent app**

<Tooltip tip="OpenCX creates the oc_liveworkitem record, but Omnichannel only picks it up if the environment has Omnichannel for Customer Service provisioned and a workstream with routing rules bound to oc_liveworkitem items.">Likely cause</Tooltip>: Omnichannel isn't provisioned, or the workstream bound to `default_queue_id` has no routing rules.

**Fix:** confirm **Omnichannel for Customer Service** is enabled on your environment from the Power Platform admin center. Open **Omnichannel admin center → Workstreams**, pick the workstream matching your `default_queue_id`, and verify it has **Routing rules** that route live work items to queues.

### **`ticket_properties` values rejected on incident create**

<Tooltip tip="Two common causes: option-set fields expect the integer code (not the label text), and lookup fields must be written with a full @odata.bind path like /accounts(guid).">Likely cause</Tooltip>: option-set labels used instead of codes, or lookup written as a raw GUID.

**Fix:** use integer codes for option sets (e.g. `"prioritycode": 1` for High, not `"High"`). For lookup fields, use the `@odata.bind` form, e.g. `"customerid_account@odata.bind": "/accounts(<guid>)"`. Check the Dataverse schema for the entity if unsure.

### **`POST /incidents` returns 403 at handoff time**

<Tooltip tip="Token and Application User are fine for reading, but the security role lacks Create on Incident. Verify's GET /incidents probe succeeds on Read privilege; Create is a separate tick box.">Likely cause</Tooltip>: role lacks `Create` privilege on Incident.

**Fix:** in Dynamics, open the security role → **Service** tab → find **Case** (the Incident entity) → ensure **Create** is set at the right scope (usually Business Unit or Organization).

### **Rep replies in Dynamics don't appear in the OpenCX session**

<Tooltip tip="Back-sync from Dynamics to OpenCX isn't implemented. The integration is currently one-way — OpenCX writes incidents and live work items; Dynamics rep replies stay in Dynamics.">Likely cause</Tooltip>: back-sync isn't implemented.

**Fix:** none — the current integration is one-way. Reps continue the conversation inside Dynamics; OpenCX marks the session as handed off once the record is created. If you need a two-way audit trail, link to the OpenCX session from the incident description.

## Limits & timing

| Item                  | Limit                        | Notes                                                                                                                            |
| --------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Access token          | Fetched per request          | No token cache or refresh layer today — each handoff exchanges a fresh token.                                                    |
| OAuth scope           | `{environment_url}/.default` | Must match the saved environment URL exactly, including protocol.                                                                |
| Dataverse API version | `v9.2`                       | OpenCX pins this version for every call.                                                                                         |
| Incident body length  | Dynamics-side limit          | Very long transcripts can hit the Dataverse string-field cap; older Dynamics versions limit descriptions around 100k characters. |
| Retry behavior        | None                         | A transient Azure AD or Dynamics 5xx is not retried; the handoff surfaces the error. Re-trigger the handoff to retry.            |

***

## Related Documentation

<CardGroup cols={2}>
  <Card title="Azure & Dynamics Setup" icon="microsoft" href="/integrations/dynamics-365/azure-setup">
    Re-check app registration, Application User, and security role.
  </Card>

  <Card title="Connect to OpenCX" icon="plug" href="/integrations/dynamics-365/connect">
    Re-run Test & Save after fixing credentials or roles.
  </Card>

  <Card title="Conversations in Dynamics 365" icon="diagram-project" href="/integrations/dynamics-365/conversations">
    Routing rules, defaults, observability.
  </Card>

  <Card title="Human Handoff" icon="user-group" href="/handoff/introduction">
    When OpenCX decides to hand off in the first place.
  </Card>
</CardGroup>
