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

# Microsoft Dynamics 365 Integration

> Overview of the integration between Avina and Microsoft Dynamics 365

## Overview

The Microsoft Dynamics 365 integration writes Avina signals, accounts, and contacts into Dataverse, and reads back just enough CRM context (accounts, contacts, and opportunities) to keep records de-duplicated and to filter signals on deal state.

Unlike the HubSpot and Salesforce integrations, Dynamics is a **write-first** integration. Avina does not perform a historical back-fill of your Dynamics data, and Dynamics data does not feed signal generation, account scoring, or AI drafting. Everything Avina reads from Dynamics is an on-demand lookup made at the moment a signal is pushed or filtered.

***

## How it works

**Write**

* **Accounts** → created when Avina fires a signal for a company that isn't already in Dynamics
* **Contacts** → created or matched for the people attached to a signal, and linked to their account
* **Leads** (optional) → created directly instead of, or alongside, contacts, linked to the account
* **Signals** → written as records in a custom **`avina_signals`** activity table, linked to the account and to each contact

**Read**

* **Accounts** → looked up by website domain to find the record a signal belongs to
* **Contacts** → looked up by email address before any contact is created
* **Opportunities** → read on demand so signals can be filtered on deal state (open, won, lost, amount, stage)

Avina talks to your environment through the **Dataverse Web API (v9.2)** over an OAuth connection you authorize once.

***

## Setup

### 1. Create the Avina Signals table

Avina writes each signal to a **custom activity table** in Dataverse. This table must exist before you enable the integration — Avina does not create it for you, and signal pushes will fail while it's missing.

Create it in the [Power Apps maker portal](https://make.powerapps.com) under **Tables → New table**, choosing the **activity table** option so the table inherits the native `subject`, `description`, and **Regarding** columns.

Two things must match exactly, because Avina addresses the table and its columns by logical name:

* Your publisher's **customization prefix must be `avina`**
* The table's **plural name must be `Signals`**, producing the entity set `avina_signals`

Then add three custom columns:

| Column (logical name) | Data type     | Holds                                                             |
| --------------------- | ------------- | ----------------------------------------------------------------- |
| `avina_signal_name`   | Text          | The name of the signal that fired, as configured in Avina         |
| `avina_tags`          | Text          | Comma-separated signal tags (see [Tag reference](#tag-reference)) |
| `avina_signal_date`   | Date and Time | When the underlying activity happened, in UTC                     |

<Tip>
  Give `avina_tags` a generous maximum length — a single signal often carries four or five tags.
  500 characters is a comfortable default.
</Tip>

### 2. Connect Dynamics

1. Go to **Settings → Integrations** in Avina.
2. Select **Microsoft Dynamics**.
3. Authenticate against your Dynamics environment and grant write access.

### 3. Enable push preferences

In the same connector panel, turn on what you want Avina to write:

| Preference                                       | What it does                                                                               |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| **Push resolved contacts to Microsoft Dynamics** | Syncs contacts Avina has identified. Also creates the account if it isn't in Dynamics yet. |
| **Push ICP contacts to Microsoft Dynamics**      | Finds contacts matching your ICP target roles at the identified company and syncs them.    |
| **Push signals to account and contact activity** | Writes the signal itself to the `avina_signals` table.                                     |

The signals preference requires at least one of the two contact preferences to be on — Avina needs a resolved account and contact to attach the activity to, so the toggle stays disabled until one is enabled.

***

## What Avina writes

The tables below are the **default mappings**. They aren't fixed — if your org stores this data in different columns, or you want signals written to custom fields you already report on, the mappings can be adapted to your schema. Raise it during setup or email [support@avina.io](mailto:support@avina.io), and the columns Avina writes to will be configured to match.

### Accounts

Created only when no existing account matches the signal's company domain.

| Dynamics column | Value          |
| --------------- | -------------- |
| `name`          | Company name   |
| `websiteurl`    | Company domain |

### Contacts

Created only when no existing contact matches the email address, and linked to the account when one is known.

| Dynamics column                       | Value                                                          |
| ------------------------------------- | -------------------------------------------------------------- |
| `emailaddress1`                       | Contact email — **required**; contacts without one are skipped |
| `firstname`                           | First name, split from the full name                           |
| `lastname`                            | Last name, split from the full name (blank if unavailable)     |
| `jobtitle`                            | Job title, when Avina has one                                  |
| `parentcustomerid_account@odata.bind` | Lookup to the parent account                                   |

### Leads (optional)

By default Avina creates Contacts. If your team works from leads instead, Avina can create Dynamics **Lead** records directly — linked to the associated account, and to the contact when one exists — as an alternative or addition to contact creation. Raise it during setup or email [support@avina.io](mailto:support@avina.io).

One limit to know about: Avina can't route the lead through logic that sits between your contacts and leads today — a custom script or workflow you run to create or qualify leads. If that logic needs to apply, it has to run on your side after Avina creates the record.

### Signals

| Column              | Value                                                                                                              |
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `subject`           | The signal headline (e.g. *Dev Security hired a new Chief Revenue Officer*)                                        |
| `description`       | The full plain-text signal summary                                                                                 |
| `avina_signal_name` | Name of the signal that fired, as configured in Avina (blank for signals that aren't part of a named signal group) |
| `avina_signal_date` | Timestamp of the underlying activity, in UTC                                                                       |
| `avina_tags`        | Comma-separated tags classifying the signal                                                                        |
| **Regarding**       | Lookup to the related account **or** contact                                                                       |

Example record:

> **Subject:** Avina Alert: Dev Security hired Brandon Riley as their new Chief Revenue Officer.\
> **Signal name:** New Hire — VP and above\
> **Tags:** avina\_new\_hire\_signal,has\_owner\
> **Regarding:** Dev Security (Account)

#### One activity per linked record

A Dynamics activity can point at exactly one record through its polymorphic **Regarding** field. So rather than writing one activity with several associations, Avina writes **one activity per record**: one regarding the account, plus one regarding each contact attached to the signal. A signal touching an account and two contacts produces three records with identical content.

This is a deliberate trade-off — it means the signal is visible on the timeline of every related record, at the cost of repeated content. When reporting on signal volume, count distinct accounts or filter to account-regarding rows to avoid multiple-counting.

If one of those writes fails, the others still stand: Avina reports the push as successful as long as at least one activity was created.

***

## Tag reference

Every signal carries one or more lowercase tags in `avina_tags`. These are what you filter, segment, and build Power Automate flows on.

### Custom AI Signal tags

The tag you'll build most of your plays on. Every [Custom AI Signal](/signals/custom-ai-signals) writes a tag **named after the signal you defined**, so each one is individually addressable:

```
avina_{your_signal_name}_signal
```

A signal you named `funding_round` writes `avina_funding_round_signal`; one named `new_office_opening` writes `avina_new_office_opening_signal`. Because the name is yours, a view or flow built on it targets exactly that signal and nothing else — which is what makes these tags far more precise to segment on than the type tags below.

Custom AI Signals also carry the generic `avina_custom_ai_signal` type tag, so you can match one specific signal or every AI signal at once.

### Signal type tags

Every signal also carries a tag identifying **what kind** of signal fired. Use these to segment broadly — all hiring activity, all web activity — rather than one specific signal.

| Signal type              | Tag                              |
| ------------------------ | -------------------------------- |
| **Custom AI Signal**     | `avina_custom_ai_signal`         |
| **Web Visit**            | `avina_web_visit_signal`         |
| **New Hire**             | `avina_new_hire_signal`          |
| **Job Listing**          | `avina_job_listing_signal`       |
| **Champion Movement**    | `avina_champion_movement_signal` |
| **Social Mention**       | `avina_social_post_signal`       |
| **Tech Stack**           | `avina_technographic_signal`     |
| **Ad View**              | `avina_linkedin_ad_signal`       |
| **Ad Click**             | `avina_ad_engagement_signal`     |
| **Marketing Email**      | `avina_marketing_email_signal`   |
| **Organic Search**       | `avina_search_signal`            |
| **Organic Social Click** | `avina_social_signal`            |
| **Outbound Engagement**  | `avina_outbound_signal`          |
| **Re-engagement**        | `avina_reengagement_signal`      |
| **Third Party Signal**   | `avina_third_party_slack_signal` |

### Context tags

Avina also adds tags describing the account at the moment the signal fired:

| Context tag   | Meaning                                       |
| ------------- | --------------------------------------------- |
| `open_deals`  | The account has at least one open deal        |
| `closed_won`  | The account has at least one closed-won deal  |
| `closed_lost` | The account has at least one closed-lost deal |
| `has_owner`   | The account has an owner assigned             |

### Automation tags

When an [automation](/features/automations) triggers the push, Avina appends `avina_{automation_name}_automation` and adds a `Triggered automation: {name}` line to the description.

<Tip>
  Signals routinely carry several tags at once — a champion visiting your pricing page writes both
  `avina_web_visit_signal` and `avina_champion_movement_signal`. Match with **contains**, not
  **equals**, when you build views or flows.
</Tip>

***

## De-duplication

Avina never blindly creates records. Accounts and contacts are matched against your existing Dynamics data first, and only created when nothing matches.

What follows is the **default matching logic**, which suits most orgs. Messier data usually needs more than domain-and-email matching — subsidiaries and regional entities sharing one web domain, accounts keyed on a company registration number, contacts duplicated across business units, or existing duplicate-detection rules you want Avina to respect. Avina can apply custom matching logic scaled to that complexity: additional or different match keys, tie-breakers when several records match, and rules for which record wins. Walk through your data model during setup or email [support@avina.io](mailto:support@avina.io).

### Accounts are matched on domain

1. Avina normalizes the company's domain (stripping protocol, `www.`, and paths).
2. It searches the five most recently modified accounts whose `websiteurl` contains that domain.
3. Each candidate is then compared on **domain equivalence**, not raw text: two domains match when they share the same registrable domain. Subdomains count as the same company (`mail.example.com` matches `example.com`), while lookalikes do not — this second pass is what stops `notexample.com` from absorbing a signal meant for `example.com`, which a substring search alone would allow.
4. The first genuine match wins. If none of the candidates are equivalent, Avina creates a new account and logs the rejected candidates.

Once an account is matched or created, its Dynamics ID is cached against the account in Avina, so later signals for the same company resolve without another lookup.

### Contacts are matched on email

Contacts are matched by an exact match on `emailaddress1`. If a contact exists, Avina uses it as-is and writes nothing to it. If not — and contact pushing is enabled — Avina creates one and links it to the account.

A contact with no email address is skipped entirely. Email is the only matching key, so a contact without one can't be de-duplicated safely.

### Concurrent signals can't create duplicates

Several signals for the same company often fire at once. Two safeguards keep that from producing duplicate accounts:

* Account creation takes a **lock per company**, so parallel workers queue rather than race. The first re-checks for an existing record after acquiring the lock.
* After creating an account, Avina **pauses briefly** before continuing, because a freshly created record isn't immediately returned by search.

Within a single signal, contacts are also de-duplicated by lowercased email before any writes, so a person appearing as both a resolved contact and an ICP contact receives one activity, not two.

### What Avina does not do by default

* **No updates to existing records.** Once matched, your accounts and contacts are read-only to Avina — it never overwrites a name, title, or website on a record it didn't create.
* **No upserts or alternate keys.** Matching is always an explicit lookup followed by a create, so your own alternate keys and duplicate detection rules are unaffected.
* **No merging.** If duplicates already exist in Dynamics, Avina picks the most recently modified equivalent match rather than merging them.

Each of these is a safe default rather than a hard limit — if your org needs Avina to match on an alternate key or enrich records it didn't create, that can be configured.

***

## Filtering signals on deal state

Avina can filter signals on live opportunity data — for example, suppressing signals for accounts with an open deal, or surfacing only closed-lost accounts for a revival play.

When a signal filter references deal data, Avina resolves the account by domain and reads its opportunities on the spot. Results are cached for an hour.

| Avina deal field  | Dynamics opportunity source                                   |
| ----------------- | ------------------------------------------------------------- |
| Name              | `name`                                                        |
| Stage             | `stepname`                                                    |
| Open / won / lost | `statecode`                                                   |
| Amount            | `actualvalue` when won, otherwise `estimatedvalue`            |
| Close date        | `actualclosedate` when closed, otherwise `estimatedclosedate` |
| Days to close     | Derived from `createdon` and the close date                   |

Opportunities are matched to an account through the opportunity's parent account. Deals are **read only** — Avina never creates or edits opportunities.

***

## Use cases

### Signal visibility in the CRM

Every signal lands on the timeline of the related account and contact, so sellers working in Dynamics see buying intent without switching tools.

### Views and segments off tags

Because tags are stored in a single text column, you can build views and personal segments with a **contains** filter on `avina_tags` — one view per play, e.g. all accounts with `avina_champion_movement_signal` in the last 30 days.

### Power Automate flows

Trigger a flow on the creation of an `avina_signals` row, branch on `avina_tags`, and route to Teams, an email alert, or a task assigned to the account owner.

### Automated record creation

When a signal surfaces a company or person you don't have yet, the account and contact are created and linked automatically — de-duplicated against what's already there.

***

## Troubleshooting

| Symptom                                       | Likely cause                                                                                                                          |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| No signal records appear at all               | The `avina_signals` table is missing, or its prefix or plural name doesn't match. Confirm the entity set resolves as `avina_signals`. |
| Accounts and contacts sync, but signals don't | **Push signals to account and contact activity** is off, or was auto-disabled when both contact preferences were turned off.          |
| A signal landed on the wrong account          | Multiple accounts share a website domain. Avina picks the most recently modified equivalent match — de-duplicate them in Dynamics.    |
| Contacts are missing from a signal            | Those contacts have no email address, so they can't be matched or created.                                                            |
| Duplicate-looking signal records              | Expected: one activity per linked record. See [One activity per linked record](#one-activity-per-linked-record).                      |
| Signal headline looks truncated               | Dataverse enforces a maximum length on the native `subject` column. The full text is always in `description`.                         |
| Deal-based filters aren't matching            | The account couldn't be resolved by domain, or opportunities aren't linked to the parent account. Deal reads are cached for an hour.  |

Still stuck? Email [support@avina.io](mailto:support@avina.io).
