---
id: saas/uso-do-produto-no-crm
industry: SaaS and technology
usage: common
effort: medium
grain: Account (company or workspace) and contact, split into create and update
---

# Product usage in the CRM

Each account's plan, payment status and usage written into the CRM where sales and CS work, only when they change, without duplicating contacts or erasing what the team filled in.

## Executive summary

**For:** SaaS companies whose sales and CS teams work in the CRM, especially with a trial or self-service.

**What it solves**

- Reps and CSMs see usage, plan and payment without leaving the CRM.
- CRM lists and workflows start firing on real product behavior.
- The sync does not duplicate contacts or overwrite what the team filled in.

**Why it matters:** Without usage in the CRM, reps call people who never opened the trial and CS learns about a cancellation from billing. With the data there, lists, tasks and alerts come out of the CRM itself, with no extra dashboard. But a badly built sync destroys trust in the CRM overnight.

**Business questions it answers**

- Which trial accounts are using the product and have not talked to sales yet?
- Which paying customers have not logged in for more than 30 days?
- Which accounts are behind on payment, and who owns them?
- How many active users does each customer in my book have?
- Which self-service workspaces look ready for an upsell?

**What you need**

- Product database or events and billing connected to Nekt
- The single-customer map with each record's CRM id
- The list of properties the routine is allowed to write

**What you get**

- Usage, plan and payment properties kept current in the CRM
- Contact, company and deal associations to the product workspace
- A send log per record, with result and error
- CRM lists, tasks and alerts fed by product data

## Detailed

### The problem

Sales and CS live in the CRM, but usage, plan and payment status sit in the product database and in billing. The first sync tends to create duplicate contacts, overwrite fields the rep filled in and count internal users' activity as customer usage. Sometimes it brings down the CRM source itself. And the destination writes properties but does not create associations between objects.

### Business rules

1. **One table per object, with the CRM id.** Build one output table per CRM object (company, contact, workspace as a custom object), with the CRM record id on every row. The id comes from the single-customer map, never from an on-the-fly name match. Contacts match by email, lowercased and trimmed. (Avoid: Matching the product company to the CRM company by name inside the send routine.)
2. **Create and update are separate routines.** Separate records that do not exist in the CRM yet (create) from those that do (update). The update routine never creates. The create routine first checks whether the email or domain already exists. Start creation with a small pilot (for example, 15 records) and check the record is still there a few seconds later: CRM anti-duplicate automations have deleted freshly created contacts. (Avoid: A single routine that creates when it does not find a match.)
3. **Send only what changed.** Compare the calculated value with the current CRM value and send only the difference. Example: set active user to No only for people who are Yes in the CRM and became inactive in the product. Sending everything every day burns API quota, fires workflows for nothing and pollutes the property history. (Avoid: Resending every property of every record on each run.)
4. **Team fields are protected.** Declare which properties the routine may write. Phone, email and tax ID are filled only when empty. Owner, stage and fields the team edits are never overwritten. Properties written by the routine get their own prefix (for example, lake_ or product_), so nobody edits them by hand. (Avoid: Overwriting phone and owner with what came from the product.)
5. **Usage belongs to the customer, not your team.** Remove internal, support and service users (by role, email domain or list), test accounts and demo accounts from the calculation. A user enabled in a company with no current package or subscription is not an active user. (Avoid: Counting support logins inside the customer's account.)
6. **Payment comes from billing, at customer grain.** Plan, subscription status (active, past_due, canceled, trialing), MRR and first payment date come from billing, consolidated per customer and not per subscription. With several subscriptions, a declared priority rule picks the status. The first payment date is the date of the first paid charge, not the billing cycle date. Send the data's reference date along with it. (Avoid: Sending the status of the most recently created subscription.)
7. **Few properties, ready for action.** Write indicators the team actually uses (active users in 7 and 30 days, days since last login, modules in use, customer tier), not one field per event or feature. Picklist values are translated and come from a closed list. HubSpot caps a property at 512,000 bytes, and the contacts source fails above 10,000 nested fields; after 3 consecutive failures, it disables itself. (Avoid: Creating one property for every product event.)
8. **Associations are a separate routine.** The Nekt destination writes properties by object id, but it does not associate contact to workspace, company to workspace or deal to workspace. Do that in a notebook that compares expected associations with existing ones and creates only the missing ones, with the association label. Run a dry run first: in a real case, two weeks of dry run showed hundreds of associations to create and more than a hundred contacts that did not even exist yet. (Avoid: Expecting the destination to create associations.)
9. **Lock before writing.** The routine checks the target portal or account before writing (sandbox in testing, the expected id in production) and stops if it does not match. It also checks volume: a catalog below the expected minimum or an unusual number of changes means broken source data, and the routine stops instead of deleting options or blanking fields. Every send goes to a log with record, value, result and error. API keys live in the secrets vault, never in code. (Avoid: Running straight in production without checking the account or the volume.)
10. **Accounts that left are sent too.** Include inactive and deleted accounts in the send, so status and exit date are updated in the CRM and in the support tool. Otherwise CS keeps seeing as active someone who already canceled. (Avoid: Filtering to active accounts only before sending.)

### What goes to the CRM, and where it comes from

Properties written by the routine, with their own prefix and a reference date.

- **Billing, consolidated per customer** → Plan and subscription status (`subscription.status`)
- **MRR table** → Customer's current MRR
- **First paid charge** → First payment date (`charge.status = succeeded`)
- **Product events, no internal users** → Active users in 7 and 30 days
- **Last login of any user** → Days since last login
- **Closed list of modules** → Modules in use
- **Cumulative volume in bands** → Customer tier
- **Custom object with the product id** → Associated workspace

### Definitions

| Metric | Definition |
|---|---|
| Active user 30d | customer user, excluding internal, support and service users, with at least one login or event in the last 30 days up to the previous day |
| Active user in the CRM | Yes if the user is active in the product and the company has a current package or subscription; No otherwise |
| Days since last login | days between the last login of any of the customer's users and the reference date |
| Payment status | status of the customer's main subscription in billing on the reference date, chosen by a declared priority rule |
| First payment date | date of the customer's first paid charge with an amount above zero |
| Reference date | day of the data that produced the value, sent along with the property |
| Send batch | records whose calculated value differs from the current CRM value, with the result stored in the log |

### Pitfalls

- **Contact created and deleted right after.** A CRM anti-duplicate automation deleted contacts the routine had just created, and the batch looked successful. How to avoid: Create a pilot and check the record still exists a few seconds later.
- **CRM source disabled.** Too many contact properties pushed past the 10,000 nested-field limit; extraction failed three times and the source disabled itself without warning. How to avoid: Few properties, a review of unused ones and a source failure alert.
- **Duplicate contacts on the first load.** Without separating create from update, every run recreated people whose email differed only in letter case. How to avoid: Lowercase email and a create routine that checks for existence.
- **Canceled showing as active.** Deleted companies were left out of the send and stayed active in the support tool. How to avoid: Send inactive and deleted accounts with the exit date.
- **The team inflated customer usage.** Users of a service run by the team itself worked inside customers' accounts and counted as their active users. How to avoid: Exclude by user role, as well as email domain and test accounts.
- **API key in the code.** CRM and other tool tokens were written inside notebooks, visible to anyone who opened or versioned the code. How to avoid: Store them in the Nekt secrets vault and read them with nekt.load_secret.

### Acceptance test

- [ ] For a sample of 20 accounts, plan, status and active users in the CRM match billing and the product on the reference date.
- [ ] A second run with no change at the source sends zero records.
- [ ] The update routine creates no contacts, and created contacts match the create batch plan.
- [ ] No internal, support or test user is counted among active users.
- [ ] Every expected association exists in the CRM, and the log has no unhandled error.

### Context document

```text
Product usage in the CRM
- Properties written by the routine have the prefix <prefix> and carry the data's reference date. The CRM value may lag the product by up to 1 day.
- Active user = customer user with a login or event in the last 30 days, excluding internal, support, service and test accounts, in a company with a current plan.
- Payment status comes from billing, consolidated per customer: active, past_due, canceled, trialing. Past_due is not canceled.
- First payment date = first paid charge with an amount above zero, not the billing cycle date.
- The routine never overwrites owner, stage or team-edited fields. Phone, email and tax ID are filled only when empty.
- Contact, company and deal associations to the workspace are created by a dedicated routine; a record without an association is not a customer without usage.
- If the CRM value differs from the product, the source of truth is the product or billing, not the CRM.
Client exceptions:
- <client>: users with role <role> are internal and do not count as active.
- <client>: customer tier by cumulative volume: <bands>.
```

### Variations

- **Spreadsheet as a bridge:** A spreadsheet fed by Nekt is the single source read by an automation (for example, n8n) that distributes to several tools.
- **Tier by volume:** Customer tier by cumulative volume (for example, by TPV) sent to the CRM and to the team's task tool.
- **Value per person:** In self-service, one row per email with total paid and the status of the latest subscription, written to the contact.
