---
title: "Microsoft 365 Exchange Message Tracing collector"
canonical: "https://docs.devo.com/space/latest/94656748/Microsoft%20365%20Exchange%20Message%20Tracing%20collector"
format: markdown
---
> Macro (excerpt-include)



> Macro (toc)

## Purpose

An analyst wants to detect data exfiltration in [Microsoft 365 Exchange](https://www.microsoft.com/en-us/microsoft-365/exchange/exchange-online). Using the Microsoft 365 Exchange Message Tracing collector to send email flow logs to Devo, the analyst will find suspicious message patterns between senders and recipients. As a result, the analyst will block unauthorized sharing of sensitive information, preventing data loss through email channels.

## Example tables

| **Table** | **Description** |
| --- | --- |
| `cloud.office365.messagetracing` | Reports for every email sent and received through Exchange Online |

## Authorize It

It will take several hours to configure the Graph collector.  Administrator permissions are required.  Microsoft credentials are confusing.  Before beginning, be aware of the <u>[different credential fields](https://devodocs.atlassian.net/wiki/spaces/latest/pages/1086816286)</u>.

1. Go to <u>[Azure Portal](https://portal.azure.com/)</u> and search for App registration. Then click on **App registrations**.

![image-20250314-193523.png](media://e96a57e1-d060-4b71-8340-82be8e904f2b)

2. Open **New registration**.

![image-20250314-193615.png](media://893b7a2e-8a12-467a-9e66-952e3d296dc4)

3. On the **Register and Application** page:
  1. Name the application.
  2. Select *Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox) *in **Supported Accounts type**.
  3. Click **Register**.

![image-20250314-195326.png](media://07a34aa7-5878-40da-91a3-fe11067a9c65)

4. App registration will open. Click on** your app** to configure it and give it permissions.

![image-20250314-194922.png](media://87bb9f67-6fd8-4179-abcd-daa3451cc8ac)


5. Search for **Authentication** on the left-menu side, then choose **Add a platform** and select **Mobile and desktop application**.

![image-20250314-195551.png](media://64d90c7c-c649-4ea5-beb0-5fa979b6abe6)

6. Select the redirect URI *[https://login.microsoftonline.com/common/oauth2/nativeclient](https://login.microsoftonline.com/common/oauth2/nativeclient)* and click **Configure**.

![image-20250320-173228.png](media://8d9aea27-cfd3-4b7e-9108-532274875544)

7. Open [API permissions](https://learn.microsoft.com/en-us/exchange/monitoring/trace-an-email-message/graph-api-message-trace) and add

- `ExchangeMessageTrace.Read.All`

8. Select Grant admin consent.  After consent, the application permission will have green check marks.
9. Go to Certificates & Secrets and select **New client secret**.
10. Add the secret.

![image-20250320-174440.png](media://39f4f977-7108-4b6a-b91b-b7ae095faa57)

> ❌ **Data loss** warning: At the secret expiration time, the collector will stop working until the secret is replaced.

11. Copy the secret value.

> ⚠️ **Save** the client secret **value**. It will only be shown once.
> ⚠️ 
> ⚠️  
> ⚠️ 
> ⚠️ The **value** <u>[contains a tilde ~ character.](https://devodocs.atlassian.net/wiki/spaces/latest/pages/1086816286)</u>

![image-20250320-174815.png](media://4c8672b8-4f8a-415d-a04f-2f1ad61b5fd0)

12. In the App registration’s overview, copy the **Application (client) ID** and the **Directory (tenant) ID**.

![image-20250320-174953.png](media://1737de3a-7bc9-4dff-bb72-f290acfc45b6)

13. **Create a ****[service principal](https://learn.microsoft.com/en-us/exchange/monitoring/trace-an-email-message/graph-api-message-trace)** for `8bd644d1-64a1-4d4b-ae52-2e0cbf64e373` as specified by Microsoft.
14. Wait several hours for the creation of the service principal to complete.
15. Validate the credentials.

## Validate the credentials

Before running the collector, check the credentials are valid.

```
curl -s -X POST "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&scope=https://graph.microsoft.com/.default&grant_type=client_credentials"
```

If the Tenant ID, Client ID, and Client Secret are correctly entered into this request, the response will be a JSON object including an `access_token`.  The token must be validated.

```
curl -s -X GET "https://graph.microsoft.com/v1.0/admin/exchange/tracing/messageTraces?\$top=1" \
  -H "Authorization: Bearer TOKEN"
```


If the response includes 

> … the service principal for App ID 8bd644d1-64a1-4d4b-ae52-2e0cbf64e373 was not found. Please create a service principal for this app in your tenant. Provisioning may take several hours to complete…

then the credentials are not valid.  Check to see if the service principal creation step was completed.


If the credentials are valid, the response will be a JSON object resembling 

```
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#admin/exchange/tracing/messageTraces",
  "@odata.nextLink": "https://graph.microsoft.com/v1.0/admin/exchange/tracing/messageTraces?$...",
  "value": [
    {
      "id": "...",
      "messageId": "...",
      "status": "...",
      "receivedDateTime": "...",
      "recipientAddress": "...",
      "senderAddress": "...",
      "subject": "...",
      "size": ...,
      "fromIP": "...",
      "toIP": "..."
    }
  ]
}
```

## Run it

In the Cloud Collector App, [create](https://devodocs.atlassian.net/wiki/spaces/latest/pages/409305131) an **Office 365 Exchange Message Tracing **collector instance.  Insert this parameters template, replacing the values enclosed in `< >`.  The `id` must be a unique [five digit number](https://www.random.org/integers/?num=1&min=10000&max=99999&col=5&base=10&format=html&rnd=new).

```
{
  "inputs": {
    "office365_exchange_message": {
      "credentials": {
        "client_id": "<CLIENT_ID>",
        "client_secret": "<CLIENT_SECRET>",
        "tenant_id": "<TENANT_ID>"
      },
      "enabled": true,
      "id": "<FIVE_UNIQUE_DIGITS>",
      "services": {
        "office365_exchange_message_service": {
          "override_time_delay_in_seconds": 300
        }
      }
    }
  }
}
```

The secret must be a [secret value](https://devodocs.atlassian.net/wiki/spaces/latest/pages/1086816286) containing a tilde.

## Secure it

Devo Exchange provides these resources:

## Monitor it

Create an [inactivity alert](https://devodocs.atlassian.net/wiki/spaces/latest/pages/95126785) to detect interruptions of transfer of data from the source  using the query

```
from cloud.office365.messagetracing 
where toktains(hostchain,"collector-") 
select split(hostchain,"-",1) as collector_id
```

Set the inactivity alert to keep track of the `collector_id`.

## Parameters

### Example

```
{
   "global_overrides": {
      "debug": <enable_debug_logs>
   },
   "inputs": {
      "office365_exchange_message": {
         "id": <short_unique_id>,
         "enabled": <input_status>,
         "max_historic_date_cutoff_days_back": "<max_historic_date_cutoff_days_back_value>",
         "max_historic_date_margin_seconds": "<max_historic_date_margin_seconds_value>",
         "credentials": {
            "tenant_id": "<tenant_id_value>",
            "client_id": "<client_id_value>",
            "client_secret": "<client_secret_value>"
         },
         "services": {
            "office365_exchange_message_service": {
               "requests_timeout_in_seconds": <requests_timeout_in_seconds_value>,
               "reset_persistence_auth": "<reset_persistence_auth_value>",
               "override_time_delay_in_seconds": <override_time_delay_in_seconds_value>,
               "override_base_resource_url": "<override_base_resource_url_value>",
               "override_base_auth_url": "<override_base_auth_url_value>",
               "override_base_url": "<override_base_url_value>"
            }
         }
      }
   }
}
```


### Details

| **Parameter** | **Data Type** | **Necessity** | **Details** |
| --- | --- | --- | --- |
| `enable_debug_logs` | `boolean` | Mandatory | This will make the collector generate `true` (or not `false`) log messages with the DEBUG level. |
| `id` | `integer` | Mandatory | Short, unique ID for input service, used in persistence addressing. Avoid duplicates to prevent collisions. |
| `input` | `boolean` | Mandatory | Use this param to enable or disable the given input logic when running the collector. If the value is `true`, the input will be run. If the value is `false`, it will be ignored. |
| `max_historic_date_cutoff_days_back` | `integer` | Optional | When the collector is initially run, it will perform a historical pulling from the current date minus the value of this parameter. The default value is 10 (the API does not allow querying more than 10 days back). **This parameter should be removed if it is not used**. |
| `max_historic_date_margin_seconds` | `integer` | Optional | It happens that between when this date is calculated and when it is used, the difference might be larger than 10 days, so the request will fail. To avoid it, we establish a margin that can be configured using this parameter. Its default value is 60. **This parameter should be removed if it is not used.** |
| `tenant_id` | `string` | Mandatory | This is the **Tenant ID** you copied during the **Obtain the client credentials** step from the Vendor setup section. |
| `client_id` | `string` | Mandatory | This is the **Application (client) ID** you copied during the **Obtain the client credentials** step from the Vendor setup section. |
| `client_secret` | `string` | Mandatory | This is the created secret's **Value** field you copied during the **Obtain the client credentials** step from the Vendor setup section. |
| `requests_timeout_in_seconds` | `float` | Optional | The amount (in seconds) in which the service's collection is scheduled. **This parameter should be removed if it is not used.** |
| `reset_persistence_auth` | `string` | Optional | This parameter allows you to clear the persistence of the collector and restart the download pipeline. **Updating this value will produce the lost of all persisted data and current pipelines.** **This parameter should be removed if it is not used.**<br>Format: `YYYY-MM-DD` |
| `override_delay_in_seconds` | `integer` | Optional | Because it can take up to 24 hours for an event to be available through the API, this collector requests data with a 25 hour delay. This parameter allows you to customize this delay. **A value less than 25 hours could cause the loss of events in large infrastructures.** **This parameter should be removed if it is not used.** |
| `override_base_resource_url` | `string` | Optional | This parameter allows overriding the base resource URL that is included in the payload when getting the token from Microsoft, including `https` |
| `override_base_auth_url` | `string` | Optional | This parameter allows overriding the base authorization URL that is used to get a token, including `https` |
| `override_base_url` | `string` | Optional | This parameter allows overriding the base URL that is used to get the data, including `https` |

## Troubleshooting

This collector has different security layers that detect both an invalid configuration and abnormal operation. This table will help you detect and resolve the most common errors.

| **Error type** | **Error ID** | **Error message** | **Cause** | **Solution** |
| --- | --- | --- | --- | --- |
| SetupError | 100 | `Authentication failed: [...]` | When asking for a token, we received an Unauthorized (401) HTTP response code. | The error details will be provided in the error message, but make sure you registered App has the right API permissions and the corect role assigned. |
| 101 | `Unexpected status code when fetching Office365ExchangeMessageTrace JWT: [...]` | When asking for a token, we received an unsuccesful response. | The error details will be provided in the error message. |
| 102 | `Error occurred while accessing the access_token: [...]` | An unexpected error occurred. | The error details will be provided in the error message. |
| InitVariablesError | 0 | `module_properties section does not exist on the configuration file or is empty. Contact Devo.` | This is an internal error. | Reach Devo Support. |
| 0 | `credentials section does not exist on the configuration file or is empty` | The `credentials` section is missing from the config | Make sure it exists. |
| 1 | `module_properties section is not a valid <dict> object. Contact Devo.` | This is an internal error. | Reach Devo Support. |
| 1 | `credentials section is not a valid <dict> object` | The `credentials` section does not have any settings. | Make sure it has the required properties. |
| 2 | `base_auth_url doesn't exist on the configuration file or is empty` | The `base_auth_url` is missing. | Make sure it exists and has a correct value. |
| 2 | `base_resource_url doesn't exist on the configuration file or is empty` | The `base_resource_url` is missing. | Make sure it exists and has a correct value. |
| 2 | `tenant_id doesn't exist on the configuration file or is empty` | The `tenant_id` is missing. | Make sure it exists and has a correct value. |
| 3 | `base_auth_url must be an string` | The `base_auth_url` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 3 | `base_resource_url must be an string` | The `base_resource_url` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 3 | `tenant_id must be an string` | The `tenant_id` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 4 | `client_id doesn't exist on the configuration file or is empty` | The `client_id` is missing. | Make sure it exists and has a correct value. |
| 4 | `client_secret doesn't exist on the configuration file or is empty` | The `client_secret` is missing. | Make sure it exists and has a correct value. |
| 5 | `client_id must be an string` | The `client_id` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 5 | `client_secret must be an string` | The `client_secret` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 6 | `base_tag doesn't exist on the configuration file or is empty` | The `base_tag` is missing. | Make sure it exists and has a correct value. |
| 7 | `base_tag must be an string` | The `base_tag` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 8 | `base_url doesn't exist on the configuration file or is empty` | The `base_url` is missing. | Make sure it exists and has a correct value. |
| 9 | `base_url must be an string` | The `base_url` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 10 | `time_delay_in_seconds doesn't exist on the configuration file or is empty` | The `time_delay_in_seconds` is missing. | Make sure it exists and has a correct value. |
| 11 | `time_delay_in_seconds must be an string` | The `time_delay_in_seconds` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 12 | `requests_timeout_in_seconds doesn't exist on the configuration file or is empty` | The `requests_timeout_in_seconds` is missing. | Make sure it exists and has a correct value. |
| 13 | `requests_timeout_in_seconds is not a float instance` | The `requests_timeout_in_seconds` paramater has a type other than float. | Make sure it is a float and has a correct value. |
| 14 | `requests_retries_before_error_in_units doesn't exist on the configuration file or is empty` | The `requests_retries_before_error_in_units` is missing. | Make sure it exists and has a correct value. |
| 15 | `requests_retries_before_error_in_units is not an int instance` | The `requests_retries_before_error_in_units` paramater has a type other than int. | Make sure it is a int and has a correct value. |
| 16 | `requests_wait_time_between_retries_in_seconds doesn't exist on the configuration file or is empty` | The `requests_wait_time_between_retries_in_seconds` is missing. | Make sure it exists and has a correct value. |
| 17 | `requests_wait_time_between_retries_in_seconds is not an int instance` | The `requests_wait_time_between_retries_in_seconds` paramater has a type other than int. | Make sure it is a int and has a correct value. |
| 18 | `Optional setting: <reset_persistence_auth> not of expected type: str` | The `time_delay_in_seconds` paramater has a type other than string. | Make sure it is a string and has a correct value. |
| 19 | `Optional setting: <override_time_delay_in_seconds> not of expected type: int` | The `time_delay_in_seconds` paramater has a type other than integer. | Make sure it is an integer and has a correct value. |
| 20 | `Optional setting: <override_time_delay_in_seconds> is out of range (1 to 90000)` | The `time_delay_in_seconds` paramater is out of the mentioned range. | Make sure it is inside the indicated range. |

## Devo collector features

| **Feature** | **Details** |
| --- | --- |
| Allow parallel downloading (`multipod`) | `Not allowed` |
| Running environments | `Collector server` |
| Populated Devo events | `Table` |
| Flattening preprocessing | `No` |

## Release Notes

- **Version 2.6.0** (2026-05-07) The collector uses the Graph API.  Since Microsoft is discontinuing the Reporting Web Service API, it has been removed from the collector.  This version is not backwards compatible.
- **Version 2.3.2** (2024-10-22)
  - Updated DC SDK from `v1.12.4` to `v1.13.1`
  - Fix parameters for credential requests
- **Version 2.3.1 **(2024-10-17)
  - Add override for the default URL connection parameters `override_base_resource_url`,`override_base_auth_url` and `override_base_url`
- **Version 2.3.0** (2024-08-20)
  - Upgraded DCSDK from 1.9.2 to 1.12.4
  - Implemented pull_pause method to fix issues with collector restart.
- **Version 2.2.0** (2023-09-11)
  - Upgraded DCSDK from 1.9.0 to 1.9.2
- **Version 2.1.0** (2023-07-21)
  - Upgraded DCSDK from 1.3.0 to 1.8.0
  - Processing of special characters that the collector was not capable of doing. It has been fixed by adding the processing of non-ascii characters.
- **Version 2.0.0** (2022-12-23)
  - New OAuth2 authentication method; basic authentication deprecated.
  - New `max_historic_date_cutoff_days_back` and `max_historic_date_margins_second` parameters to customize historic pulling.
  - Puller setup is now handled in its own class.