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

# Contact deduplication default

> Set, verify and recover the CRM Contact deduplication default for first installations and installed Locations.

Funding Machine's default for a CRM Location's **Contact Deduplication
Preferences** is:

* **Allow Duplicate Contact**: off
* **Find existing contacts based on**: Phone
* **Second preference for search**: None

In the CRM's Location settings these are `allowDuplicateContact: false` and
`contactUniqueIdentifiers: ["phone"]`. From 2026-10-07 (PR #1890) first
installations turned Allow Duplicate Contact on instead; that changed on
2026-10-14.

Setup preserves every other Location setting, including **Allow Duplicate
Opportunity**. **Sticky Contact** forms can still reuse a Contact on a shared
browser; see the
[CRM's Sticky Contact guidance](https://help.gohighlevel.com/support/solutions/articles/48000979919-sticky-contact-in-forms-surveys-and-calendars).

## First installations

Setup requires a verified marketplace INSTALL notification and the Location's
credentials. Either may arrive first. A first OAuth authorization can wait for
the notification; lazy credential discovery during an app open cannot start
this setup. Existing installation history excludes older accounts, even after
uninstall or credential cleanup.

Successful setup records verification once. Later app opens, token refreshes,
reauthorization, duplicate notifications and reinstalls do not reapply it.
Uninstall cancels pending work. Setup still pending from before 2026-10-14
(which would have turned duplicates on) is cancelled when it next runs and
writes nothing; the batch below covers those Locations.

The setup reads current settings, sends `companyId` plus all returned settings
with the default applied, then reads again. A successful PUT without confirmed
readback is not configured. Before the first PUT, setup keeps the original
settings in installation history (`settingsBeforeWriteJson`); retries must
confirm those other settings. A mismatch stays a visible failure until staff
reconcile it in the CRM.

## Installed Locations

Run `scripts/contact-deduplication-batch.ts` with an admin session
(`FM_ADMIN_SESSION_TOKEN`) and a Convex CLI login to production. This applies
the default everywhere, including Locations where the Operator chose otherwise.

1. Pilot one Location first: run with `--locations <id>`, apply it, and check
   that Location's Contact settings in the CRM.
2. Run without `--apply`. It is read only: it checks every installed Location
   and writes `contact-deduplication-report/check-<time>.json`, listing each as
   **default**, **differs** (with its current values) or **unavailable** (with
   the reason).
3. Review that report. Only **differs** Locations will change.
4. Run again with `--apply <that report>`. For each **differs** Location the
   server reads the settings again, sends them back with only the two
   deduplication settings changed, and reads once more to verify. Results go to
   `contact-deduplication-report/apply-<time>.json`.

Only admins can apply: the endpoint requires an admin session and Convex
checks the admin again before writing. The Location must still be installed
under the same agency right before the write.

Every apply is recorded in the `contactDeduplicationChanges` table: a
**started** row with the two settings it replaces (`beforeJson`), saved before
the write, then the outcome with the admin and time. So any Location can be put
back, even if the CRM's response was lost. Re-running is safe. If your admin
session expires mid-run, the report still lists every Location, with the ones
not reached marked **not\_run**.

**Unavailable** means the owning agency hasn't authorized `locations.readonly`
and `locations.write`, or the Location is no longer installed. Reconnect the
owning agency and re-run, or change the setting by hand in that Location's CRM
Contact settings. **Failed** means the CRM didn't confirm the change or another
setting changed at the same time; check that Location by hand.

## Permissions

The [official scope table](https://marketplace.gohighlevel.com/docs/Authorization/Scopes/index.html)
requires **agency access** and `locations.write` for
[PUT /locations/{locationId}](https://marketplace.gohighlevel.com/docs/ghl/locations/put-location/index.html).
Reading settings also requires `locations.readonly`. A Location token cannot
substitute for the owning agency's token. Requests use the pinned SDK's
`locations.getLocation` and `locations.putLocation` with that agency token.

## Verify and recover

Open **Admin → Locations**. Under the Location name, **Contact deduplication
setup verified** means the first-install setup confirmed the default by
readback. It records installation verification, not a live monitor of later
changes. **Duplicate Contact turned on at install (before Oct 14); apply the
new default** marks a Location whose earlier setup turned duplicates on; the
batch applies the default there.

**Contact deduplication setup needs attention; retry scheduled** includes the
failure and required action. Missing agency authorization or scope requires a
maintainer to correct marketplace permissions and reconnect the owning agency.
Do not mark setup complete manually or change another Location to work around
the error.

Recovery retries after one minute, five minutes, 15 minutes and one hour, then
every six hours. Each attempt reads current settings first, so a write whose
response was lost is confirmed without another PUT. Concurrent attempts use one
durable claim; an interrupted attempt is recovered after five minutes.
Failure details never include credentials or provider request objects.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.