# Optimizing your email outreach
Source: https://docs.unifygtm.com/best-practices/deliverability
Best practices for writing email copy and optimizing deliverability.
## Sequence copy
1. **Keep an individual sequence to 4 email touches max -** Within a four-touch sequence, we recommend alternating the structure between 2 new threads and 2 follow-up replies. The copy of these follow-up replies can be short and limited to 1-2 sentences. Example: "Any thoughts on my previous note?"
2. **Alternate case studies and value props between new threads -** You should vary the copy and angle you're approaching to pitch your product in email steps 1 and 3. For example, you can highlight different customer case studies or statistics across both emails.
3. **Shorten subject line and use personalization -** Keep subject titles concise and add custom variables to increase variability. This approach will also help improve deliverability. Example: "Unify x (Your Company Name)"
4. **Include statistics and case studies for social proof -** Incorporating numbers-driven, concise impact statements can catch your prospect's attention. We recommend adding concise blurbs from customer case studies into your sequence copy.
5. **Add personalization with snippets -** You can use Unify's smart snippets to personalize copy based on relevant value prop, job title, work description, industry, and more. This will make your sequence copy more compelling with targeted pain points, use cases, or case studies. More variance in email copy will also help with deliverability.
6. **Keep it concise, focus on a single product or pain point -** Don't try to fit too many points into your email - you want to be decisive and mention the most relevant product or pain point to that individual to capture their attention! Keep the language simple and avoid business jargon. We recommend keeping each email between 50-200 words.
7. **End with a compelling call to action (CTA) -** Conclude each email with a clear, specific, and actionable request. This could include scheduling a call, booking a demo, or asking the prospect to reply with a specific piece of information relevant to their needs.
## Deliverability
1. **Limit the links -** Try not to overload your emails with multiple or duplicate links. Too many links can set off spam alarms - we recommend limiting the email touch to include 1-2 links maximum.
2. **Check link safety -** Ensure the links you include are secure and no warnings are displayed by the browser when visiting them.
3. **Mix up subject lines -** Don't stick with the same old subject lines, use template variables to keep them dynamic and interesting.
4. **No all caps -** Using all caps anywhere in your email can make it look like spam, so stick to normal capitalization.
5. **Easy on the exclamation -** Too many exclamation points can trigger spam filters, especially in the subject line. Use them sparingly!
6. **Send from multiple email addresses -** Sending emails from multiple mailboxes improves deliverability by distributing volume across different IP addresses and domains. This approach reduces the risk of being flagged as spam and increases overall sending capacity.
7. **Proofread for Typos**: Typos can make your email look unprofessional, especially when you're emailing multiple contacts at a company. Make sure to proofread and catch any errors.
# Booking meetings with Plays
Source: https://docs.unifygtm.com/best-practices/plays
Best practices for using Unify Plays to book meetings from intent signals.
## Best practices
Unify is a sales engagement platform designed to help you generate pipeline. To ensure that you see the best results, we recommend a few general principles:
* **Act Quickly:** Act on intent signals within 24-48 hours to catch prospects before they make their decision. Intent gets stale over time.
* **Engage with Every Lead:** Once you define your ICP in Unify, avoid selective outreach. Avoid introducing bias into how you prospect.
* **Multiple Touchpoints:** If you’ve found that a channel works best for your team, use Unify to lean into that. Unify excels at email - if that’s your preferred channel, great. If not, use email in combination with social or phone outreach. See section 4 below to see our recommendations for email and social outreach.
## Act on intent
There are two primary ways to act on intent data in Unify:
### Automatic Plays
* **When to use:** Both for low and high volume signals. Plays automate prospecting, qualifying, sequencing, and syncing leads to your CRM.
* **Benefits:** Reduce friction and improve intent to meeting booked conversion by immediately taking action on leads.
* Learn more about how to build Plays in [How to create a Play](/tutorials/how-to-create-a-play).
### Manual alerts
* **When to use:** Ideal for low volume, high value signals. It's important to ensure the sales team is accountable for acting on alerts to ensure hot leads don’t slip through the cracks.
* **Benefits:** Immediate and contextual information delivered to where your team is working.
* Most reps use Slack alerts to trigger manual workflows. Learn how to set these up in our [Slack integration guide](/reference/integrations/slack#slack-integration-guide)
## Intent-based Plays
Intent signals are indicators that there’s an opportunity to sell your product or book an intro meeting. Our customers have seen success with these intent-based Plays:
1. **Pricing Page Visits:** Checking out pricing pages indicates that prospects are considering a purchase decision.
2. **[Repeat Buyers](https://www.unifygtm.com/plays/champion-tracking---outbound-to-past-customers-who-have-moved-jobs):** When someone you’ve sold to before is back on the website demonstrating high intent. This works well for businesses that have more transactional sales.
3. **[Inbound Form Submissions](https://www.unifygtm.com/plays/outbound-to-website-form-submitters):** A form submission is one of the strongest signs of intent. Convert those leads immediately by automating outreach as soon as they submit.
4. **[New Hires](https://www.unifygtm.com/plays/outbound-to-people-who-have-started-jobs-at-a-new-company):** People are more likely to purchase new tools in their first few months on a job. Targeting ICP fits that have just changed jobs can convert better.
5. **[Support Docs](https://www.unifygtm.com/plays/outbound-to-contacts-viewing-your-technical-docs):** Interest in support docs (e.g., integration details), suggests prospects are trying to understand how your product works and if it would work for them.
6. **[Product Tour Visits](https://www.unifygtm.com/plays/outbound-to-people-who-view-your-product-demo):** Interest in demo videos or product tours indicates a desire to understand specific product offerings in more granularity.
7. **[Retarget Paid Traffic](https://www.unifygtm.com/plays/retarget-paid-traffic):** Get more out of your spend on paid traffic by retargeting people based on UTM filters.
8. **[Closed-Lost Opportunities](https://www.unifygtm.com/plays/retarget-closed-lost-opportunities-when-they-revisit-website):** A revisit to your site by a closed-lost opportunities signals the prospect might be interested in re-engaging.
## Nailing email or social outreach
Nailing email and social messages is key to booking the most possible meetings from Unify. A few guiding principles:
1. **Use Soft Messaging:** Use your judgement, but we typically recommend against direct mentions of website activity. Instead, tailor messages to address the pain points related to the product or service they showed interest in.
2. **Keep It Short:** Many senior leaders at your target accounts read emails on their phones, concise messages under 100 words are more likely to be read and understood. Remember that people are skimming emails.
3. **Persona-Specific Messaging:** Address specific pain points and perspectives of the different personas you sell to (e.g., sales and marketing for us). Avoid generic messages that try to cater to multiple personas, as they may resonate with none.
4. **Focus on One Product:** If you sell multiple products, pitch one per email. This approach minimizes the chances that your prospect gets confused about what you solve.
5. **Highlight Relevant Pain Points:** Rather than pitching your product, focus on speaking to a specific challenge or pain point that your prospect has. Educate the prospect on your product once you’re on the phone with them.
# Writing AI prompts
Source: https://docs.unifygtm.com/best-practices/prompting
Best practices for writing prompts for AI in Unify.
## General tips
Unify has multiple AI features that can be used to improve qualification and outreach outcomes. To set yourself up for success, we recommend a few general principles for prompting when using these features:
* **Clear Instructions:** Be explicit about what you want the AI to do. Providing a clear set of instructions will help the AI understand what you want to accomplish and what steps to follow to complete the tasks.
* **Length ≠ Quality:** Writing an overly detailed or essay length prompt does not mean that the AI will provide a better response. Excessive information can be detrimental unless it is well-structured and explained, as it can lead to confusion and misinterpretation of the prompt.
* **Provide Context:** Assume AI does not know your industry, company, or product as well as you do. Provide relevant context as you reference information about your company or your goals. For example, if you are referencing a niche keyword, provide a brief explanation of what it means, how it relates to your business, and the task you are giving the AI.
## AI agents
There are two primary ways that you can provide information and instruction to Unify's AI Agents.
### Questions
* **Questions:** Questions are the minimum required input to run an AI Agent. You can ask any question about a Company or Person and select the type of response you want the Agent to provide (e.g., True/False, Text, Multi-Select, Number).
* **What information to provide:** In the question, only include the research question you want the Agent to answer. Avoid adding clarifying information or instructions to keep it focused.
### Guidance
* **When to use:** Guidance is an optional field for AI Agents that enables you to provide additional context or instructions to the AI Agent. When asking more complex questions, when you have a set of instructions or requirements that you want the AI Agent to follow, or when you want to provide clarification on the research question, guidance is the best way to provide that information.
* **How To Structure Guidance:** Similar to Prompts, Guidance should be clear, concise, and specific. Use the guidance to provide specific directions about each question that you are asking, and additional context if there is a specific way that you want the AI Agent to conduct its research. For example, if you want the AI Agent to only use information from a specific source, you can include that information in the guidance. Or, if you want to provide more direction on when the AI Agent should select a specific answer for a multi select response type, that information should also be included in the guidance.
## Smart snippets
Smart snippets are a powerful way to personalize messaging to your prospects.
* **Provide Context (Again!):** The model can only act on what you provide. This means you should specify the information you want to create copy from, select template variables to reference, or add Agent template variables to the smart snippet. Additionally, provide context around the template variable to create a coherent prompt. Think of template variables as placeholders for information and write sentences or blurbs surrounding those variables.
* **Adding References:** A common mistake is referencing information that you haven't provided to the model, which can lead to incorrect responses. To prevent this, ensure that if you're referencing data provided through a template variable or Agent output, you include that template variable in the Snippet.
### Agent outputs in Smart snippets
Using Agents to conduct research and then incorporating that research into a smart snippet is a great way to hyper-personalize your messaging. To make effective use of this feature:
* **Separation of Concerns:** Use the Agent to conduct research only, and use the Snippet to generate the copy. Each feature is optimized for its respective task.
* **Structure the Snippet prompt around the Agent variables:** When writing a smart snippet prompt with Agent output template variables, provide context and labels about what each Agent output contains so the Snippet can handle it accordingly. For example, if you have an Agent Question that asks for a True/False or Number answer, write your prompt using the Agent template variable as a placeholder in your sentences to provide context to the Snippet.
* For example, if your Agent question was "What is the most recent product launch from this company?" then your Snippet prompt should be something like: "Research indicates that this company just launched `{{Agent Question}}`. Write a sentence to congratulate them on the launch."
## AI research
AI Research is a powerful way to automate and customize research that you want to conduct about any company that you are engaging. AI Research is powered by Unify's Observation Model, which is the system that Unify uses to run completely customized research for your company on your prospects.
### Prompting AI research
Writing prompts for AI Research is slightly different from writing prompts for AI Agents. AI Research is designed to read as much relevant information as possible before generating a report for you. We recommend following the style of the Observation prompts we generated for you as a starting point. To prompt for AI Research, follow these guidelines:
* **Pick a General Signal:** The goal with an Observation prompt is to describe a specific piece of information to the system that you'd like to monitor for any given company. For example, if you know that a company using a specific technology has a high likelihood of becoming your customer, an Observation for that could be: "Company uses an outbound sequencer (Outreach, Salesloft, Apollo, etc.) and a data/intent source (ZoomInfo, 6sense, Bombora, Clearbit, Demandbase, G2 Intent)."
* **Test and Iterate:** Once you've written your prompt, take advantage of the settings page to generate examples and see what the resulting reports look like.
# Custom events
Source: https://docs.unifygtm.com/developers/api/analytics/custom
POST /track
Send a "track" event.
# Identify events
Source: https://docs.unifygtm.com/developers/api/analytics/identify
POST /identify
Send an "identify" event.
# Unify Analytics API
Source: https://docs.unifygtm.com/developers/api/analytics/overview
Send website and product analytics data into the Unify platform.
## Overview
The Analytics API allows you to send both client-side and server-side events
into Unify. There are three types of events you can send:
* [Page events](/developers/api/analytics/page) capture page visits on your website or web application
* [Custom events](/developers/api/analytics/custom) capture actions or other custom activities that you define
* [Identify events](/developers/api/analytics/identify) capture visitor identity information (e.g., their email address)
These event types form the building blocks to track visitor behavior and actions
on your marketing website, web application, or other custom software.
## Usage
### Request format
The base URL of all API requests to the Analytics API is:
```shell theme={null}
https://api.unifyintent.com/analytics/v1
```
Note that the base URL uses a different domain than other Unify APIs.
### Authentication
When sending requests to the Analytics API from client devices, you should use
your **write key** rather than an API key. Write keys are safe to expose
publicly whereas API keys are not.
To prevent exposure of sensitive API keys, if you send a request to
the Analytics API that contains an API key by accident, Unify will immediately
expire the key as a precautionary measure.
Write keys are used to authenticate requests to the Analytics API. You can find
your write key in Unify by navigating to
[Settings → Web & product data → Settings](https://app.unifygtm.com/dashboard/settings/data/web-intent?webEventsTab=Settings).
To authenticate a request, include the following header in your request:
```http theme={null}
X-Write-Key:
```
Replace `` with your Unify write key.
### Request origin
Requests that use a write key must include an allowed `Origin` header. This
applies to requests sent from both browsers and from server-side code. For
server-side requests, set `Origin` to the website or application origin
associated with the event source.
### Rate limits
The Analytics API is rate-limited by default to 100,000 requests per five minute
window. We are able to provide higher limits upon request if needed.
### OpenAPI specification
The OpenAPI specification can be found [here](https://api.unifyintent.com/analytics/v1/openapi.json).
# Page events
Source: https://docs.unifygtm.com/developers/api/analytics/page
POST /page
Send a "page" event.
# Create option
Source: https://docs.unifygtm.com/developers/api/data/attribute-options/create
POST /objects/{object_name}/attributes/{attribute_name}/options
# Delete option
Source: https://docs.unifygtm.com/developers/api/data/attribute-options/delete
DELETE /objects/{object_name}/attributes/{attribute_name}/options/{option_name}
# Get option
Source: https://docs.unifygtm.com/developers/api/data/attribute-options/get
GET /objects/{object_name}/attributes/{attribute_name}/options/{option_name}
# List options
Source: https://docs.unifygtm.com/developers/api/data/attribute-options/list
GET /objects/{object_name}/attributes/{attribute_name}/options
# Update option
Source: https://docs.unifygtm.com/developers/api/data/attribute-options/update
PATCH /objects/{object_name}/attributes/{attribute_name}/options/{option_name}
# Create attribute
Source: https://docs.unifygtm.com/developers/api/data/attributes/create
POST /objects/{object_name}/attributes
# Delete attribute
Source: https://docs.unifygtm.com/developers/api/data/attributes/delete
DELETE /objects/{object_name}/attributes/{attribute_name}
# Get attribute
Source: https://docs.unifygtm.com/developers/api/data/attributes/get
GET /objects/{object_name}/attributes/{attribute_name}
# List attributes
Source: https://docs.unifygtm.com/developers/api/data/attributes/list
GET /objects/{object_name}/attributes
# Update attribute
Source: https://docs.unifygtm.com/developers/api/data/attributes/update
PATCH /objects/{object_name}/attributes/{attribute_name}
# Create object
Source: https://docs.unifygtm.com/developers/api/data/objects/create
POST /objects
# Delete object
Source: https://docs.unifygtm.com/developers/api/data/objects/delete
DELETE /objects/{object_name}
# Get object
Source: https://docs.unifygtm.com/developers/api/data/objects/get
GET /objects/{object_name}
# List objects
Source: https://docs.unifygtm.com/developers/api/data/objects/list
GET /objects
# Update object
Source: https://docs.unifygtm.com/developers/api/data/objects/update
PATCH /objects/{object_name}
# Unify Data API
Source: https://docs.unifygtm.com/developers/api/data/overview
Interact with Unify objects, attributes, and records.
The Data API is currently in public beta and may change based on feedback.
Whenever possible, we will maintain backward compatibility and provide advance
notice of any breaking changes.
## Overview
Data within Unify is stored in *objects*. Objects are the building blocks that
allow you to store and interact with data in Unify. Every object has a set of
*attributes* which represent the fields that make up the object, and *records*
represent instances of the object.
The Unify Data API allows you to interact with these objects, attributes, and
associated records. The following API groups are available:
* [Object APIs](/developers/api/data/objects/create) allow you to create and manage object definitions and schemas
* [Attribute APIs](/developers/api/data/attributes/create) allow you to create and manage the attributes on objects
* [Record APIs](/developers/api/data/records/create) allow you to create and modify object records
These APIs provide the means to connect custom data sources to Unify, import
data from external tools, and export data to other systems.
## Usage
### Request format
The base URL of all API requests to the Data API is:
```shell theme={null}
https://api.unifygtm.com/data/v1
```
All APIs are built on REST principles and use standard HTTP methods to perform
CRUD operations.
### Authentication
API keys are used to authenticate requests to the Data API. You can generate an
API key in Unify by navigating to [Settings → Developers](https://app.unifygtm.com/dashboard/settings/integrations/api-keys).
To authenticate a request, include the following header in your request:
```http theme={null}
X-Api-Key:
```
Replace `` with your Unify API key.
### Rate limits
The Data API is rate-limited to 100,000 requests per five minute window. We may
introduce daily limits in the future to prevent extraordinary usage.
### OpenAPI specification
The OpenAPI specification can be found [here](https://api.unifygtm.com/data/v1/openapi.json).
# Create record
Source: https://docs.unifygtm.com/developers/api/data/records/create
POST /objects/{object_name}/records
Create a new record.
## Overview
The *create* method creates a new record in the specified object. This method
will fail if any record already exists with conflicting unique values, which may
or may not be the desired behavior depending on your use case.
If you are not sure whether a record already exists with the same unique values,
consider using the [upsert method](/developers/api/data/records/upsert) instead.
## Examples
This creates a new company record:
```http Request theme={null}
POST /objects/company/records
{
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"status": "Active"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "company",
"id": "73fcb798-9ccd-4138-8f6a-9a801123783c",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"status": "Active"
// ...
}
}
}
```
This creates a new person record and links them to an existing company if it
exists:
```http Request theme={null}
POST /objects/person/records
{
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "CEO",
"company": {
"match": {
"domain": "unifygtm.com"
}
}
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "person",
"id": "bf3f4d5e-c995-496f-8774-5a1c58d05197",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "CEO",
"company": "73fcb798-9ccd-4138-8f6a-9a801123783c"
// ...
}
}
}
```
If a company with this domain does not exist, the `company` reference on
this person will be `null`. If you want to create a company in that case
instead, you can use a nested upsert:
```http Request theme={null}
POST /objects/person/records
{
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "CEO",
"company": {
"match": {
"domain": "unifygtm.com"
},
"create": {
"name": "Unify",
"domain": "unifygtm.com",
"linkedin_url": "https://www.linkedin.com/company/unifygtm"
}
}
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "person",
"id": "bf3f4d5e-c995-496f-8774-5a1c58d05197",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "CEO",
"company": "73fcb798-9ccd-4138-8f6a-9a801123783c"
// ...
}
}
}
```
This creates a new record in a custom object called `product_user`:
```http Request theme={null}
POST /objects/product_user/records
{
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8",
"full_name": "Bob Smith",
"email_address": "bob.smith@gmail.com",
"plan": "free_tier"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "product_user",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8",
"full_name": "Bob Smith",
"email_address": "bob.smith@gmail.com",
"plan": "free_tier"
// ...
}
}
}
```
## Usage
# Delete record
Source: https://docs.unifygtm.com/developers/api/data/records/delete
DELETE /objects/{object_name}/records/{record_id}
Delete a record by ID.
## Overview
The *delete* method permanently removes a record by its unique ID from the
specified object. This operation cannot be undone.
If the record doesn't exist, the API will return a 404 error. If the record is
referenced by other records, those other records will not be deleted, but their
reference to this record will be set to `null`.
## Examples
This permanently deletes a company record:
```http Request theme={null}
DELETE /objects/company/records/349b4a49-38b7-407d-aeee-db4ead1bbae2
```
```json Response theme={null}
{
"status": "success"
}
```
This removes the company permanently. Any people associated with this
company will have their `company` attribute set to `null`.
This permanently deletes a record from a custom object:
```http Request theme={null}
DELETE /objects/product_user/records/5ff35db4-20de-4a12-9eef-ca0e9cc24818
```
```json Response theme={null}
{
"status": "success"
}
```
This removes the record from the `product_user` object permanently.
## Usage
# Find unique record
Source: https://docs.unifygtm.com/developers/api/data/records/find-unique
POST /objects/{object_name}/records/find-unique
Find a specific record based on unique values.
## Overview
The *find unique* method searches for a single record based on one or more
unique attribute values. The response will contain exactly zero or one record.
This method is useful when you need to find a record by its unique identifier
other than the record ID. For example, you can use it to find a company by its
domain or a person by their email address. If you have custom objects with
unique identifiers, this method can be used to locate records based on those
unique attributes.
## Examples
This finds a company record by its unique domain:
```http Request theme={null}
POST /objects/company/records/find-unique
{
"domain": "unifygtm.com"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "company",
"id": "73fcb798-9ccd-4138-8f6a-9a801123783c",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"status": "Active"
// ...
}
}
}
```
This returns the company for Unify if it exists, otherwise the response will
contain `null` for the record.
This finds a record in a custom object by a unique identifier:
```http Request theme={null}
POST /objects/product_user/records/find-unique
{
"user_id": "Zz9O-5O8W-s41T"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "product_user",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"user_id": "Zz9O-5O8W-s41T",
"full_name": "Alice Smith",
"email_address": "alice@example.com",
"plan": "enterprise"
// ...
}
}
}
```
This finds a custom object record using a combination of unique and
non-unique attributes:
```http Request theme={null}
POST /objects/product_user/records/find-unique
{
"user_id": "Zz9O-5O8W-s41T",
"email": "alice@example.com",
"product_code": "PRO-PLAN"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "product_user",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"user_id": "Zz9O-5O8W-s41T",
"full_name": "Alice Smith",
"email": "alice@example.com",
"product_code": "PRO-PLAN"
// ...
}
}
}
```
In this case, the custom attributes `user_id` and `email` are unique, but
`product_code` is not. Only a record which matches all of these criteria
will be returned.
## Usage
# Get record
Source: https://docs.unifygtm.com/developers/api/data/records/get
GET /objects/{object_name}/records/{record_id}
Retrieve a specific record by ID.
## Overview
The *get* method retrieves a single record by its ID from the specified object.
This is the most accurate way to fetch a specific record when you know its ID.
If the record doesn't exist, an error will be returned.
## Examples
This retrieves a specific company record:
```http Request theme={null}
GET /objects/company/records/de885595-2d9a-4fb9-ae30-25ef18b6219b
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "company",
"id": "de885595-2d9a-4fb9-ae30-25ef18b6219b",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"status": "Active"
// ...
}
}
}
```
This retrieves a record from a custom object:
```http Request theme={null}
GET /objects/product_user/records/6741cc39-b332-45a8-a439-75b69db998b1
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "product_user",
"id": "6741cc39-b332-45a8-a439-75b69db998b1",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8",
"full_name": "Bob Smith",
"email_address": "bob.smith@gmail.com",
"plan": "free_tier"
// ...
}
}
}
```
## Usage
# Update record
Source: https://docs.unifygtm.com/developers/api/data/records/update
PATCH /objects/{object_name}/records/{record_id}
Update an existing record by ID.
## Overview
The *update* method modifies an existing record by its unique ID. You can
provide values for any attributes you want to update in the request body.
Attributes not included in the request will remain unchanged.
If the record doesn't exist, the API will return a 404 error. This method will
not create a new record. If you want to create a record if it doesn't exist, use
the [upsert method](/developers/api/data/records/upsert) instead.
This method will overwrite existing values on the record, which may be
undesirable. If you want a safer alternative, use the [upsert method](/developers/api/data/records/upsert)
and pass `update_if_empty` instead.
## Examples
This updates specific fields on a company record:
```http Request theme={null}
PATCH /objects/company/records/11b41071-1ac7-4419-b3bd-3da132502826
{
"status": "Inactive",
"description": "An updated description for this company."
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "company",
"id": "11b41071-1ac7-4419-b3bd-3da132502826",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"name": "Unify",
"domain": "unifygtm.com",
"status": "Inactive",
"description": "An updated description for this company."
// ...
}
}
}
```
This updates only the `status` and `description` fields on the company. All
other fields remain unchanged.
This updates a person's job title and associated company:
```http Request theme={null}
PATCH /objects/person/records/5ce127ba-8d7e-42d0-9e60-76017a04caec
{
"title": "Senior Developer",
"company": {
"id": "11b41071-1ac7-4419-b3bd-3da132502826"
}
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "person",
"id": "5ce127ba-8d7e-42d0-9e60-76017a04caec",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "Senior Developer",
"company": "11b41071-1ac7-4419-b3bd-3da132502826"
// ...
}
}
}
```
In this case, we knew the exact ID of the company we wanted to link this
person to, but you could also use a nested `match` or upsert if desired.
This updates fields on a custom object record:
```http Request theme={null}
PATCH /objects/product_user/records/e624b659-7a50-4925-9a49-dc2dae8dcb60
{
"plan": "enterprise",
"last_login": "2024-01-15T10:30:00Z"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "product_user",
"id": "e624b659-7a50-4925-9a49-dc2dae8dcb60",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8",
"full_name": "Bob Smith",
"email_address": "bob.smith@gmail.com",
"plan": "enterprise",
"last_login": "2024-01-15T10:30:00Z"
// ...
}
}
}
```
## Usage
# Upsert record
Source: https://docs.unifygtm.com/developers/api/data/records/upsert
POST /objects/{object_name}/records/upsert
Create or update a record based on unique values.
## Overview
The *upsert* method creates or updates a record depending on whether it already
exists or not. If a matching record exists, it will be updated. Otherwise, a new
record will be created.
The request body accepts the following properties which specify how to create or
update the record:
| Property | Behavior |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `match` | Values to match against existing records. At least one of these values must be a unique attribute to avoid matching more than one record. |
| `create` | Values to use when creating a new record. |
| `update` | Values to use when updating an existing record. These values will replace any existing values on the record. |
| `update_if_empty` | Values to use when updating an existing record. These values will only be used if the attribute is currently empty. |
| `create_or_update` | Values to use when creating a new record or updating an existing record. When updating, these values will replace any existing values on the record. |
| `create_or_update_if_empty` | Values to use when creating a new record or updating an existing record. When updating, these values will only be used if the attribute is currently empty. |
You must specify `match` and one of `create`, `create_or_update`, or `create_or_update_if_empty`.
All other properties are optional.
The upsert method is the most reliable way of sending data into Unify, so if
you're not sure what method to use, this is the one we recommend.
## Examples
This creates or updates a company record:
```http Request theme={null}
POST /objects/company/records/upsert
{
"match": {
"domain": "unifygtm.com"
},
"create": {
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"status": "Active"
},
"update": {
"status": "Active"
}
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "company",
"id": "73fcb798-9ccd-4138-8f6a-9a801123783c",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"status": "Active"
// ...
}
}
}
```
If there's already a company record in Unify with the domain [unifygtm.com](https://unifygtm.com),
it will be updated with a status of "Active". Otherwise, a new record will be
created with the specified values.
This creates or updates a person record as well as their associated company
and then links them together:
```http Request theme={null}
POST /objects/company/records/upsert
{
"match": {
"email": "austinhughes@unifygtm.com"
},
"create_or_update_if_empty": {
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "CEO",
"company": {
"match": {
"domain": "unifygtm.com"
},
"create_or_update_if_empty": {
"name": "Unify",
"domain": "unifygtm.com"
}
}
}
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "person",
"id": "bf3f4d5e-c995-496f-8774-5a1c58d05197",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"first_name": "Austin",
"last_name": "Hughes",
"email": "austinhughes@unifygtm.com",
"title": "CEO",
"company": "73fcb798-9ccd-4138-8f6a-9a801123783c",
"lead_source": "LinkedIn"
// ...
}
}
}
```
This works because `company` is the name of a *reference attribute* on the
person object which references a company object record. This request will
create a new company if it doesn't already exist, update it if it does, and
then link this person to that company.
This creates or updates a record in a custom object called `product_user`:
```http Request theme={null}
POST /objects/company/records/upsert
{
"match": {
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8"
},
"create_or_update": {
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8",
"full_name": "Bob Smith",
"email_address": "bob.smith@gmail.com",
"plan": "free_tier"
}
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "product_user",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"user_id": "6b2a2761-3cbe-481f-8a1e-b24859c674f8",
"full_name": "Bob Smith",
"email_address": "bob.smith@gmail.com",
"plan": "free_tier"
// ...
}
}
}
```
This is a custom object, so all of these attributes are custom attributes
defined for the object. Since this request uses `create_or_update`, these
values will be filled in if the record is created or overridden if an
existing record is found.
## Usage
# Connecting your data
Source: https://docs.unifygtm.com/developers/connecting-data
Choose the right approach to get your data into Unify.
## Overview
There are two fundamental ways to get data into Unify:
* **Sending events** — Real-time signals from websites and products (page
visits, user actions, identity information)
* **Syncing object records** — Structured data from CRMs, warehouses, databases, APIs,
and other systems (product users, subscriptions, enrichment data)
Events and records often come from different sources, but they serve the same
overall purpose of connecting all of the data relevant to your go-to-market
operations in one place.
## Sending events
Events are the real-time behavioral signals that power intent-based workflows in
Unify. They capture what visitors and users are doing on your website or in your
product and link that activity to Companies and People.
### Example use cases
* You want to identify companies visiting your marketing website (IP reveal)
* You want to track product usage to trigger sales plays (PLG motions)
* You want to capture form fills, logins, or other user interactions
* You want real-time or near-real-time signals to power automations
### Choosing an event source
The right way to send events depends on what tools you already have deployed.
#### Marketing websites
For marketing websites, blogs, docs sites, and other public-facing pages, the
goal is typically to capture page visits and identify visitors.
| Your situation | Recommended approach |
| :------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No existing analytics tool on the site | **[Unify Website Tag](/developers/intent-client/website-tag)** — Copy and paste a script tag. Fastest way to get started. Automatically captures page visits and basic form fills. |
| Already using **Segment** on the site | **[Segment integration](/reference/integrations/segment)** — Forward existing Segment events to Unify. No additional SDK needed. |
| Already using **PostHog** on the site | **[PostHog integration](/reference/integrations/posthog)** — Forward existing PostHog events to Unify. No additional SDK needed. |
| Using another tool (e.g., Google Analytics) | **[Unify Website Tag](/developers/intent-client/website-tag)** — GA doesn't forward events to Unify. The Website Tag can run alongside GA without conflicts since they serve different purposes. |
#### Web applications (product usage)
For SaaS products and web applications, the goal is typically to track user
actions (feature usage, paywall hits, milestones) and identify logged-in users.
| Your situation | Recommended approach |
| :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Already using **PostHog** in your product | **[PostHog integration](/reference/integrations/posthog)** — Reuse your existing PostHog instrumentation. No new SDK needed. |
| Already using **Segment** in your product | **[Segment integration](/reference/integrations/segment)** — Reuse your existing Segment instrumentation. |
| Using another tool with webhook support (e.g., RudderStack, Amplitude) | **[Analytics API](/developers/api/analytics/overview)** — Configure a webhook destination in your tool pointing to Unify's Analytics API. You'll need to map events to Unify's `track` and `identify` format. |
| Using another tool without webhook support | **[JavaScript](/developers/intent-client/js-client) or [React](/developers/intent-client/react) Library** — Install the Unify Intent Client alongside your existing tool for the specific events you want to action on in Unify. |
| No analytics tool | **[JavaScript](/developers/intent-client/js-client) or [React](/developers/intent-client/react) Library** — The Unify Intent Client is the default path for in-product tracking. You get `identify()` and `track()` with direct-to-Unify delivery. |
#### Backend and server-side events
Some events originate from backend systems rather than browsers—for example,
a subscription renewal, a usage limit being hit, or a derived milestone like
"onboarding completed."
| Your situation | Recommended approach |
| :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Need to send events from backend services | **[Analytics API](/developers/api/analytics/overview)** — Send `track` and `identify` events directly to Unify from your server. Use a stable identifier like email or user ID. |
Server-side events complement client-side tracking—but they are typically not
a replacement. Client-side events provide session context, attribution data,
and browser-based identity. Server-side events cover backend-only actions and
provide resilience against ad blockers.
### Quick reference: event ingestion methods
| Method | Best for | Requires code changes? |
| :-------------------------------------------------------- | :------------------------------------------------------ | :--------------------- |
| [Website Tag](/developers/intent-client/website-tag) | Marketing websites with no existing analytics | No—copy and paste |
| [React Library](/developers/intent-client/react) | React web applications | Yes |
| [JavaScript Library](/developers/intent-client/js-client) | Non-React web applications | Yes |
| [Segment integration](/reference/integrations/segment) | Teams already using Segment | No—configuration only |
| [PostHog integration](/reference/integrations/posthog) | Teams already using PostHog | No—configuration only |
| [Analytics API](/developers/api/analytics/overview) | Server-side events, unsupported tools, custom pipelines | Yes |
## Syncing records
Records are the structured, persistent data that populates [objects](/reference/objects/overview)
in Unify. While events capture what's happening right now, records represent
what Unify knows—companies, people, CRM data, product users, subscriptions, and
more.
### Example use cases
* You want to sync CRM data from Salesforce or HubSpot
* You have product usage data modeled in a data warehouse (e.g., a `users`
table in BigQuery or Snowflake)
* You want to bring in business data like subscriptions, contracts, or usage
tiers
* You have enrichment data from a third-party tool
### Choosing a sync method
| Your situation | Recommended approach |
| :------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Data lives in a **CRM** (Salesforce, HubSpot) | **[Salesforce](/reference/integrations/salesforce)** or **[HubSpot](/reference/integrations/hubspot) native integrations** — These keep your CRM data in sync with Unify objects automatically. |
| Data lives in a **warehouse** (Snowflake, BigQuery, ClickHouse, Redshift, etc.) | **[Hightouch](/developers/guides/data-systems/hightouch-destination)** or **[Fivetran](/developers/guides/data-systems/fivetran-destination)** — Reverse ETL tools that sync warehouse tables into Unify objects on a schedule. |
| Data lives in a **database** (PostgreSQL, MySQL, MongoDB, etc.) | **[Hightouch](/developers/guides/data-systems/hightouch-destination)** or **[Fivetran](/developers/guides/data-systems/fivetran-destination)** — Both support databases as sources in addition to warehouses. |
| Data comes from **internal APIs or custom pipelines** | **[Data API](/developers/api/data/overview)** — Programmatically create objects, define attributes, and push records. |
| Data comes from a **third-party tool** (e.g., Clay) | **[Data API](/developers/api/data/overview)** — Use webhooks or scripts to push data from external tools into Unify objects. |
**Model your data as objects.** Rather than adding lots of custom attributes
directly to the Company or Person objects, create dedicated custom objects
(e.g., **Product User**) and link them to Person or Company with reference
attributes. This keeps your data organized and makes it easy to filter based
on data source.
For a step-by-step walkthrough, see
[Connect a data system](/developers/guides/data-systems/overview).
## CRM data
If you use Salesforce or HubSpot, Unify has native integrations that sync CRM
data automatically. This is managed through the Unify UI—no developer setup is
required.
}>
Connect your Salesforce instance.
}>
Connect your HubSpot CRM.
When you connect a CRM, Unify creates objects (like **Salesforce Account** or
**HubSpot Contact**) that are linked to the Company and Person objects. CRM data
flows bidirectionally and stays in sync.
## Combining data sources
Many teams use multiple data sources together. For example:
* **Website Tag** on the marketing site for anonymous visitor identification
* **PostHog integration** in the product for usage events
* **Hightouch** to sync product user records from the data warehouse
* **Salesforce integration** to keep CRM data in sync
Events and records complement each other. Events provide real-time behavioral
signals. Records provide structured context. Together, they give Unify the full
picture it needs to power targeted, timely go-to-market actions.
## Next steps
Get started with marketing website tracking.
Track in-app user behavior and product signals.
Sync structured data from warehouses and databases.
Push data programmatically from custom pipelines.
# Sync data from Fivetran
Source: https://docs.unifygtm.com/developers/guides/data-systems/fivetran-destination
Learn how to connect upstream data sources to Unify via Fivetran.
## Overview
This guide assumes that you already have a Fivetran account. If you don't, you
can sign up for a free plan [here](https://www.fivetran.com/).
Unify provides a Fivetran destination which allows you to sync data from any
Fivetran data source into Unify objects. Fivetran supports a range of data
sources, including data warehouses, databases, and more.
## Connect Fivetran to Unify
Within Fivetran, navigate to **Destinations** and select **Add a destination**.
Look for the **Unify** destination type and select it.
Fill in a name for your destination and provide an API key. You can generate
an API key within Unify by navigating to [Settings → API
Keys](https://app.unifygtm.com/dashboard/settings/integrations/api-keys).
Wait for the test to complete and then choose **Finish** to save the new
destination.
Once saved, you will see this destination listed in the **Destinations** tab.
## Sync to an object
Within Fivetran, navigate to **Syncs** and select **Create a sync**.
Select the data source you want to sync from. This can be a table in a data
warehouse, database, or any other supported data source.
Select the Unify destination you created previously.
Choose the sync behavior. You can choose to either **Insert** or **Upsert**
records into Unify. Inserting records will only add new records, while
upserting will sync changes to existing records as well.
Choose which unique key to use for the sync. This is the key that will be
used to identify records in Unify to avoid creating duplicates and to update
existing records (if applicable).
Fill in the mapping between the fields in your data source and the
attributes on the Unify object. This is how you tell Fivetran which fields
in your data source should be synced to which attributes in Unify.
In Unify, each object attribute has a value type which determines what kind
of data it can hold. For example, **Text** attributes can hold any string
value, while **Integer** and **Decimal** attributes can only hold numbers.
You will only be able to map fields from your data source to attributes in
Unify that support an equivalent value type.
One special value type is the **Reference** type, which is used to link to
other objects. When mapping fields in Fivetran, you will see the unique
attributes for each object that is referenced by the object you are
syncing to.
This allows you to link records in this object to other objects based on
the data available in your data source, which is important in order to use
the data in Unify. A common example is linking rows in a data warehouse
table to company or person records in Unify so that they can be used for
pesonalization or segmentation.
Click **Run test** to send a single record from your data source to the
object in Unify.
Choose **Next** and verify the sync configuration. You can also choose to
run the sync manually, on a trigger, or on a schedule.
Once you are happy with the configuration, choose **Create** to save the
sync. You will then be able to see it in the **Syncs** tab.
# Sync data from Hightouch
Source: https://docs.unifygtm.com/developers/guides/data-systems/hightouch-destination
Learn how to connect upstream data sources to Unify via Hightouch.
## Overview
This guide assumes that you already have a Hightouch account. If you don't,
you can sign up for a demo [here](https://hightouch.com/demo).
Hightouch provides a destination which allows you to sync data from any
Hightouch-supported data source into Unify objects. Hightouch supports a range
of data sources, including data warehouses, databases, and more.
## Connect Hightouch to Unify
Within Hightouch, navigate to **Destinations** and select **Add destination**.
Look for the **Unify** destination type and select it.
Provide an API key for your destination. You can generate an API key within
Unify by navigating to [Settings → Developers](https://app.unifygtm.com/dashboard/settings/integrations/api-keys).
You can verify that the API key is valid and working by selecting **Test connection**.
You should see a message that says "Successfully connected to Unify".
Next, configure any alerts you want to receive for this destination.
Lastly, choose a name for your destination, set the desired permissions,
and select **Finish** to save the destination.
Once saved, you will see this destination listed in the **Destinations** tab.
You can modify the destination settings and test the connection again from
here.
## Sync to an object
From the destination or syncs page, select **Add sync**. The first step is
to select a *model* which represents the data source you want to send into
Unify. If you haven't configured one yet, head over to **Models** and
create one.
Select the Unify destination you created previously. Choose "Records" for
the first option and then select the Unify object you want to sync records
to. You can select a "standard" object (e.g., Companies or People) or a
custom object.
Once the object is selected, choose whether you want to "upsert" records
(create and update) or "insert" records (create only).
Choose which unique key to use for the sync. This is the key that will be
used to identify records in Unify to avoid creating duplicates and to update
existing records (if applicable).
This must be a unique attribute on the Unify object and in the data source.
For example, the unique attribute on Companies is `domain`, and the unique
attribute on People is `email`. On custom objects, you can define your own
unique attributes.
The next step is to map fields in your data source to Unify object
attributes. This defines how record values will be copied over to Unify.
Unify objects support *reference* attributes which represent relationships
between objects. For example, the Person object has a reference attribute
for the Company that the person works at.
In the above example, we are mapping our data source to the Person object,
and we are also linking each person to a Company by mapping Company fields.
Finally, choose how you want to run the sync and then select **Finish**.
## Inspect the sync
On the sync page, you can see the newly created sync, trigger a run, and
inspect results.
Click on a sync run to view the successfully synced records and any errors
that were encountered.
Select the **Successful** tab to view synced records.
You should then see the created records in Unify!
# Connect data systems to Unify
Source: https://docs.unifygtm.com/developers/guides/data-systems/overview
Sync databases, data warehouses, data lakes, and more into Unify.
## Overview
Unify is built to support custom data models and huge volumes of data. If you
have data that lives in an external data system, you can send it over and use it
to power anything within Unify. Examples of data systems you can connect to
Unify include:
* **Data warehouses** — Snowflake, BigQuery, ClickHouse, Redshift, etc.
* **Databases** — PostgreSQL, MySQL, MongoDB, etc.
* **Data lakes** — S3, Databricks, etc.
Data within Unify is stored in *objects*. Objects are the building blocks that
allow you to store and interact with records in Unify. Every object has a set of
*attributes* which represent the fields that make up the object. You can send
external data into objects in Unify.
Unify provides a set of standard objects, such as Company and Person, that are
used throughout the platform. You can also create custom objects to store data
from external sources or represent information that doesn't fit into standard
objects.
## Modeling data
You can insert data directly into the standard Company and Person objects.
However, this loses track of where the data came from. It can also lead to lots
of custom attributes being created on those standard objects over time, many of
which are not applicable for most records.
Instead, Unify recommends defining custom objects to represent external data
sources. You can create a custom object which aligns with the data source and
then create a reference attribute to link it to the standard objects.
**Example**
Suppose you have a table of product users in your data warehouse. One thing
you could do is insert them directly into the Person object in Unify:
**Avoid**: Directly inserting external data sources into standard objects.
```mermaid theme={null}
flowchart LR
subgraph data_warehouse["Data Warehouse"]
external_product_user["Product Users Table"]
end
subgraph unify["Unify"]
person["Person"]
end
external_product_user -->|Insert| person
```
However, a better strategy is to create a new object in Unify called "Product
User" and connect it to the Person object using a reference attribute. Then,
insert the product user records into that object instead:
**Prefer**: Inserting external data sources into custom objects linked to
standard objects.
```mermaid theme={null}
flowchart LR
subgraph data_warehouse["Data Warehouse"]
external_product_user["Product Users Table"]
end
subgraph unify["Unify"]
unify_product_user["Product User"] -->|Reference| person["Person"]
end
external_product_user -->|Insert| unify_product_user
```
This is how Unify builds its integrations into Salesforce, HubSpot, and more.
This has several benefits. It allows you to connect many data sources to Unify
without adding many custom attributes to the standard objects. It also makes it
easy to filter data based on whether it came from a specific source or not.
## Getting started
The first step is to define the objects and attributes that will store your
data. This is how you declare the format of the data and how it relates to
other objects.
Create and manage objects, attributes, and records in Unify via API.
Create and manage objects, attributes, and records within the Unify app.
Once the objects are created, you can create, update, and delete records using
the API. They will also become available in any tools that connect to Unify,
such as the Hightouch and Fivetran destinations for Unify.
Once the objects are created, you can sync data into Unify using the API
directly or via a supported integration.
Sync object records from Hightouch data sources into Unify.
Sync object records from Fivetran data sources into Unify.
Create, update, and link object records using the Unify API and SDKs.
# Connect product usage data to Unify
Source: https://docs.unifygtm.com/developers/guides/product-usage-data/introduction
Send web application and software usage data into Unify.
This guide is focused on web applications and software where the goal is to
track usage and interactions of identifiable users. If you are looking to send
events from marketing or other public-facing website, [Connect website traffic to Unify](/developers/guides/website-traffic/introduction)
may be more relevant.
## Overview
One of the most powerful strategies in Unify is to identify existing customers
and users to reach out to based on their product usage and behavior. By sending
product usage data into Unify, you can instantly launch Plays to start taking
action on these users.
Some examples of the ways this data can be used include:
* **Free plan conversions** — Identify free plan users who are actively using
key features and send them targeted upgrade campaigns.
* **Upsells and cross-sells** — Identify existing customers who are using
specific features and send them relevant upsell or cross-sell offers.
* **Enterprise expansion** — Identify individual users at companies, reach out
to other contacts at those companies, and target them with enterprise offers.
Unify provides a full suite of [analytics APIs](/developers/api/analytics/overview)
for tracking user interactions and identities. All event tracking capabilities
integrate natively with the rest of the Unify data layer. This makes it easy to
link product usage data to companies or people and access that data throughout
Unify.
## Setup
Unify provides an SDK called the [Unify Intent Client](/developers/intent-client/overview)
that can be installed in your web application to start sending events. This is
the easiest and recommended way to get started.
>
}
href="/developers/guides/product-usage-data/unify-intent-client"
>
Send events using the Unify's native JavaScript or React client.
If you're already collecting events using a third-party tool, you can also send
that data into Unify using either a supported integration or the general-purpose
[Analytics API](/developers/api/analytics/overview). You can find guides for
some popular tools below.
} href="/reference/integrations/segment">
Send Segment events into Unify.
>
}
href="/reference/integrations/posthog"
>
Send Posthog events into Unify.
Lastly, if you already collect product usage data in a database, data warehouse,
or data lake, you can connect those systems directly to Unify. For details, see
[Connect data systems to Unify](/developers/guides/data-systems/overview).
Analytics events (such as page, identify, and custom events) cannot currently
be sent into Unify via reverse ETL tools like [Hightouch](https://hightouch.com/)
or [Fivetran](https://www.fivetran.com/). This capability is coming soon.
In the meantime, the recommended solution is to send data to the [Analytics API](/developers/api/analytics/overview)
directly or via one of the supported integrations listed above. Alternatively,
you can create [custom objects](/developers/guides/data-systems/overview) to
represent these events and send them via reverse ETL.
## Examples
Once you've configured events to be sent into Unify, you can start using that
data to launch Plays based on user intent. See the following guides for
examples.
Identify users when they sign in to your application.
Track in‑app user activity with the Unify Intent client.
# Set up the Unify Intent client
Source: https://docs.unifygtm.com/developers/guides/product-usage-data/unify-intent-client
Send product usage and events directly into Unify with the native SDK.
## Installation
There are multiple ways to install the Unify Intent client depending on your
setup. When installing the client into the web application for your product,
using either the JavaScript client or the React client is recommended.
Install the client in your React web application for a more native
integration.
Install the client in your non-React frontend application, such as a Single
Page App (SPA).
You should only use one installation method per website or application. If the
client is installed twice on the same site, it may not work correctly.
## Usage
### Automatic events
By default, the React library will not automatically collect any events. You
can enable automatic event collection when initializing the client. See the
full [Usage guide](/developers/intent-client/usage-guide) for more details.
### Manual events
You can manually trigger events in specific places in your web application
by calling the client directly.
#### Page events
Page events record a page visit. Typically, the easiest strategy is to
enable automatic collection of page visits, but you can also call them
manually.
```javascript theme={null}
// Enable automatic "page" event collection
unify.startAutoPage();
// Send a "page" event
unify.page();
```
#### Custom events
Custom events record specific actions taken by a visitor, such as button
clicks or form fills. You can send custom events with arbitrary
properties anywhere in your React application.
```javascript theme={null}
// Send a basic custom event
unify.track("See More Button Clicked");
// Send a custom event with additional properties
unify.track("Modal Opened", {
modalName: "Contact Sales Modal"
});
```
#### Identify events
Identify events link a visitor to a specific company or person. You can send
identify events whenever you have information about a visitor, such as their
name or email address.
```javascript theme={null}
// Send a basic "identify" event
unify.identify("user@email.com");
// Send an "identify" event with additional company or person attributes
unify.identify("first.last@acme.com", {
person: {
email: "first.last@acme.com",
first_name: "First",
last_name: "Last",
},
company: {
domain: "acme.com",
name: "Acme Corp.",
}
});
```
### Automatic events
By default, the JavaScript library will not automatically collect any
events. You can enable automatic event collection when initializing the
client. See the full [Usage guide](/developers/intent-client/usage-guide)
for more details.
### Manual events
You can manually trigger events in specific places in your web application
by calling the client directly.
#### Page events
Page events record a page visit. Typically, the easiest strategy is to
enable automatic collection of page visits, but you can also call them
manually.
```javascript theme={null}
// Enable automatic "page" event collection
unify.startAutoPage();
// Send a "page" event
unify.page();
```
#### Custom events
Custom events record specific actions taken by a visitor, such as button
clicks or form fills. You can send custom events with arbitrary
properties anywhere in your web application.
```javascript theme={null}
// Send a basic custom event
unify.track("See More Button Clicked");
// Send a custom event with additional properties
unify.track("Modal Opened", {
modalName: "Contact Sales Modal"
});
```
#### Identify events
Identify events link a visitor to a specific company or person. You can send
identify events whenever you have information about a visitor, such as their
name or email address.
```javascript theme={null}
// Send a basic "identify" event
unify.identify("user@email.com");
// Send an "identify" event with additional company or person attributes
unify.identify("first.last@acme.com", {
person: {
email: "first.last@acme.com",
first_name: "First",
last_name: "Last",
},
company: {
domain: "acme.com",
name: "Acme Corp.",
}
});
```
For complete instructions, see the [Usage guide](/developers/intent-client/usage-guide).
# Capture user interactions
Source: https://docs.unifygtm.com/developers/guides/product-usage-data/user-interactions
Track in‑app user activity with the Unify Intent client.
## Overview
User interactions can be used to powerful Plays that drive activation, upgrades,
expansion, and more. By tracking key in‑app events, you can identify and engage
with users based on their actual product usage and behavior.
Some examples of interaction‑based Plays include:
* **Activation Plays** — Target users who clicked on or started using key
features but haven't yet completed the activation criteria.
* **Upsell Plays** — Reach out to users who encountered paywalls or usage limits
to offer assistance or upgrade options.
* **Expansion Plays** — Identify users who are actively using specific features
and target them with relevant cross-sell or upsell offers.
There is no limit to the number or types of interactions you can track. All the
activities you track can be linked to Company, Person, and other object records
in Unify. This gives a complete view of user behavior within the context of your
business.
## Prerequisites
* Ensure you've already installed the Unify Intent client in your application.
See [Set up the Unify Intent client](#setup) for instructions.
* Ensure your users are being identified when they log in. See
[Capture user logins](/developers/guides/product-usage-data/user-logins)
for a walkthrough.
## Examples
Custom events can be used to capture any type of user interaction within your
application. Below are some examples of common use cases and patterns for
recording user activity.
### Feature button clicked
When a user clicks a button to open or use a feature:
```ts theme={null}
unify.track('Created a Widget', {
feature: 'widget-builder',
type: 'gizmo',
number_created: 4,
});
```
### Paywall encountered
When a user hits a paywall or usage limit:
```ts theme={null}
unify.track('Widget Limit Reached', {
feature: 'widget-builder',
reason: 'plan_limit',
current_plan: 'Free Tier',
limit_reached: 10,
number_requested: 20,
});
```
If you present upgrade options, also track the click:
```ts theme={null}
unify.track('Upgrade CTA Clicked', {
destination: 'billing',
source: 'widget_paywall',
plan_selected: 'Pro',
});
```
### User added
When a user invites another user to their account:
```ts theme={null}
unify.track('User Invited', {
added_user_email: 'bob@acme.com',
role: 'Editor',
});
```
# Capture user logins
Source: https://docs.unifygtm.com/developers/guides/product-usage-data/user-logins
Identify users when they sign in to your application.
## Overview
All the activity you track in your application is associated with a *visitor*
who may or may not have a known identity. When a user signs in, you can send an
Identify event. This associate all their past and future activity with a known
Person (and optionally a Company) in Unify.
## Prerequisites
* Ensure you've already installed the Unify Intent client in your application.
See [Set up the Unify Intent client](#setup) for instructions.
## Walkthrough
### Identify on login
Call `identify` as soon as the user is authenticated. Include useful traits for
the person and (optionally) the company they belong to.
```ts theme={null}
// Determine the current user however you normally do so
const currentUser = getCurrentUser();
// Send an "Identify" event
unify.identify(currentUser.emailAddress, {
// Link this visitor to a Person
person: {
email: currentUser.emailAddress,
first_name: currentUser.firstName,
last_name: currentUser.lastName,
},
// If possible, link this visitor to a Company as well
company: currentUser.organization
? {
domain: currentUser.organization.domain,
name: currentUser.organization.name,
}
: undefined,
});
```
### Send custom event (optional)
You may also want to track that the user logged in. Identify events link
visitors to people or companies, but they don't record an activity that can be
seen or used in Unify. If you want to see when users login or filter Plays based
on login activity, you should also send a custom event.
```ts theme={null}
unify.track('User Logged In');
```
As with all custom events, you can also include additional properties to provide
more context about the login.
## Capture interactions
Now that visitors to your application are identified, you can start tracking key
in-app interactions to power Plays. For more information, see [Capture user interactions](/developers/guides/product-usage-data/user-interactions).
# Request data with the Bulk API
Source: https://docs.unifygtm.com/developers/guides/request-data/bulk-api
Use the Bulk API to request large Sequence datasets from Unify.
## Overview
Use the Bulk API to request large datasets from Unify without waiting
for a long-running HTTP request to finish. The Bulk API is asynchronous: you
create a query job, poll the job until it is finished, and then page through the
materialized results.
Use the Bulk API when you need to export or sync many records. For small operational reads, use the standard API for
the resource instead.
The Bulk API is currently in preview. Endpoint behavior, filters, and response
shapes may change before general availability.
The Bulk API is for requesting data from Unify. To create or update records in
Unify, see [Send records via API](/developers/guides/send-data/overview).
## Supported resources
The Bulk API currently supports these resources:
| Resource | Create a query job | Fetch results |
| :------------------------ | :----------------------------------------------- | :--------------------------------------------------------- |
| Sequence enrollments | `POST /sequences/v1/enrollments/jobs/query` | `GET /sequences/v1/enrollments/jobs/{job_id}/results` |
| Sequence enrollment steps | `POST /sequences/v1/enrollment-steps/jobs/query` | `GET /sequences/v1/enrollment-steps/jobs/{job_id}/results` |
You can also list and inspect jobs for each resource:
```http theme={null}
GET /sequences/v1/{resource}/jobs
GET /sequences/v1/{resource}/jobs/{job_id}
```
In these paths, `{resource}` is either `enrollments` or `enrollment-steps`.
## Authentication
The Bulk API requires a user-backed API key. Generate an API key in
[Settings → Developers](https://app.unifygtm.com/dashboard/settings/integrations/api-keys)
and include it with each request:
```http theme={null}
X-Api-Key:
```
All examples use main unify API base URL:
```text theme={null}
https://api.unifygtm.com
```
## How the Bulk API works
Submit a query job for the resource you want to export. The request body is
optional and defaults to an empty filter.
```bash theme={null}
curl -X POST 'https://api.unifygtm.com/sequences/v1/enrollments/jobs/query' \
-H 'X-Api-Key: ${UNIFY_API_KEY}' \
-H 'Content-Type: application/json' \
```
The response includes the `job_id`, current `status`, and `expires_at` time:
```json theme={null}
{
"job_id": "",
"status": "IN_PROGRESS",
"expires_at": "2026-05-13T12:00:00Z"
}
```
Poll the job metadata endpoint until the job reaches a terminal status.
Avoid tight polling loops; use a steady interval or exponential backoff. Most
Jobs should finish within a few seconds.
```bash theme={null}
curl 'https://api.unifygtm.com/sequences/v1/enrollments/jobs/' \
-H 'X-Api-Key: ${UNIFY_API_KEY}'
```
A finished job includes `total_rows`:
```json theme={null}
{
"job_id": "",
"status": "FINISHED",
"total_rows": 123,
"error_code": null,
"created_at": "2026-05-12T12:00:00Z",
"expires_at": "2026-05-13T12:00:00Z",
"canceled_at": null
}
```
After the job status is `FINISHED`, fetch results by page. Results are
immutable for a finished job, so each page is stable for that job.
```bash theme={null}
curl 'https://api.unifygtm.com/sequences/v1/enrollments/jobs//results?page=1&page_size=1000' \
-H 'X-Api-Key: ${UNIFY_API_KEY}' \
-H 'Accept: application/json'
```
```json theme={null}
{
"total": 123,
"page": 1,
"page_size": 1000,
"data": [
{
"id": "",
"updated_at": "2026-05-12T11:58:00Z"
}
]
}
```
For recurring syncs, store a checkpoint such as the newest `updated_at` value
you processed. Use that checkpoint in the next query job so you only request
new or changed records.
## Create a query job
Create-job endpoints are resource-specific:
```http theme={null}
POST /sequences/v1/enrollments/jobs/query
POST /sequences/v1/enrollment-steps/jobs/query
```
Use filters to narrow the dataset. For example, you can scope a request to exclude
specific set of IDs:
```json theme={null}
{
"filter": {
"id": { "not_in": ["", ""] }
}
}
```
## Filter fields
Available filter fields depend on the resource.
Common filters include:
| Filter | Description |
| :----------- | :-------------------------------------------- |
| `updated_at` | Filter by the time records were last updated. |
| `person` | Filter by person. |
| `company` | Filter by company. |
| `id.in` | Include only specific record IDs. |
| `id.not_in` | Exclude specific record IDs. |
Bulk API endpoints also support endpoint-specific filters.
## Job statuses
A job can have one of the following statuses:
| Status | Meaning |
| :------------ | :------------------------------------------------------------- |
| `IN_PROGRESS` | The job has been created and results are still being prepared. |
| `FINISHED` | The job completed successfully and results are available. |
| `FAILED` | The job failed. Check `error_code` on the job metadata. |
| `CANCELED` | The job was canceled before completion. |
| `EXPIRED` | The job or its results are no longer available. |
Jobs and results are available until `expires_at`, which is currently 24 hours
after job creation. Download all required result pages before that time.
## List jobs
List jobs for a resource:
```http theme={null}
GET /sequences/v1/{resource}/jobs
```
Query parameters:
| Parameter | Description |
| :-------- | :------------------------------------------------------------- |
| `limit` | Number of jobs to return. Defaults to `100`; maximum is `100`. |
| `cursor` | Opaque pagination cursor from the previous response. |
| `status` | Optional job status filter. |
Example request:
```bash theme={null}
curl 'https://api.unifygtm.com/sequences/v1/enrollments/jobs?limit=100&status=FINISHED' \
-H 'X-Api-Key: ${UNIFY_API_KEY}'
```
Example response:
```json theme={null}
{
"jobs": [
{
"job_id": "",
"status": "FINISHED",
"total_rows": 123,
"error_code": null,
"created_at": "2026-05-12T12:00:00Z",
"expires_at": "2026-05-13T12:00:00Z",
"canceled_at": null
}
],
"next_cursor": null
}
```
## Fetch results
Fetch results for a finished job:
```http theme={null}
GET /sequences/v1/{resource}/jobs/{job_id}/results?page=1&page_size=1000
```
Result pagination is page-based. Rows are ordered by most recently updated first
with a stable tie-breaker, so pages remain stable for a finished job.
### JSON results
JSON is the default response format:
```http theme={null}
Accept: application/json
```
JSON limits:
| Limit | Value |
| :----------------------- | :------ |
| Default `page_size` | `1000` |
| Maximum `page_size` | `2000` |
| Maximum raw payload size | `8 MiB` |
If a JSON page is too large, request a smaller `page_size` or use NDJSON.
### NDJSON results
Use NDJSON for larger streamed pages by providing the following
header in your request.
```http theme={null}
Accept: application/x-ndjson
```
Example request:
```bash theme={null}
curl -N 'https://api.unifygtm.com/sequences/v1/enrollments/jobs//results?page=1&page_size=10000' \
-H 'X-Api-Key: ${UNIFY_API_KEY}' \
-H 'Accept: application/x-ndjson'
```
NDJSON responses return one JSON object per line, streaming one line at a time. Pagination metadata is
returned in response headers instead of a JSON envelope:
```http theme={null}
x-total: 123
x-page: 1
x-page-size: 10000
```
The maximum NDJSON `page_size` is `10000`.
## Job states
The results endpoint validates the job state before returning data:
| Job state | HTTP status | Error code |
| :------------ | :---------- | :------------------ |
| `IN_PROGRESS` | `409` | `results_not_ready` |
| `FAILED` | `409` | `results_failed` |
| `CANCELED` | `409` | `results_canceled` |
| `Expired` | `410` | `results_expired` |
Only fetch results after the job status is `FINISHED`.
## Rate limiting
Bulk API rate limits are applied by operation type. Query job creation is
limited to roughly **100 jobs per day**, so create jobs only when you need a
new snapshot and use filters or checkpoints to keep each job focused.
| Operation | Rate limit |
| :---------------------- | :----------------------- |
| Create query jobs | \~100 jobs per day |
| query/list job status | \~10 requests per second |
| list/stream job results | \~5 requests per second |
| cancel in flight jobs | \~1 request per second |
Treat other job management requests, such as checking job status, listing jobs,
and canceling jobs, as low-frequency control-plane calls. When polling job
status, use a steady interval or exponential backoff instead of a tight loop.
Status checks are intended for roughly 5 requests per second, and most jobs
finish quickly enough that slower polling is usually sufficient.
When fetching results, page through data with bounded concurrency. If you receive
`429 Too Many Requests`, wait before retrying, honor the `Retry-After` header to avoid additional rate limiting.
## Best practices
* **Request only the data you need.** Use filters to keep result sets small and
exports fast.
* **Prefer incremental syncs.** Use checkpoints such as `updated_at` so
recurring jobs only request new or changed records.
* **Design for retries.** Store the `job_id`, job status, and processing
checkpoint in your system.
* **Process results idempotently.** Use stable record IDs so retrying a page
does not create duplicate downstream records.
* **Use NDJSON for large pages.** NDJSON streams results and supports larger
pages than JSON.
* **Download before expiration.** Jobs expire at `expires_at`; create a new job
if you need the data again after expiration.
## What's next
Review the Data API for object, attribute, and record operations.
Learn how to create and update records in Unify.
# Send records from Clay sheet
Source: https://docs.unifygtm.com/developers/guides/send-data/clay-webhook
Learn how to send records from a Clay sheet into Unify via webhook.
## Overview
This tutorial walks through the process of sending records from a Clay sheet
into Unify. To do this, we'll set up a webhook in Clay that sends records to the
[Data API](/developers/api/data/overview).
This guide uses a sheet of companies as an example, but the same approach can
be used for any object type, such as `person` or a custom object.
## Prerequisites
* An existing Clay sheet that you want to send into Unify
* An existing [API key](https://app.unifygtm.com/dashboard/settings/integrations/api-keys) for Unify
## Steps
Navigate to your Clay sheet and open the **Actions** pane.
Under **Enrichments**, select **View all enrichments** and then choose **HTTP API**.
Select the **Configure** tab. Under **Select HTTP API (Headers) account**, choose **Add account**.
If you have already completed this step in the past, you can simply select
your existing headers account for Unify.
Give the new account a name (e.g., "unify-api") and add a header named
`X-Api-Key` whose value is your Unify API key.
Select **Save** to confirm the credentials.
You can send records into Unify for any standard or custom object using the
[Data API](/developers/api/data/overview). Before constructing the request,
decide which object you want to send records into (e.g., company, person, or
a custom object).
Once you've picked an object, you will need its API name. The API name can
be found on the [object settings page](https://app.unifygtm.com/dashboard/settings/integrations/objects).
For example, the company object's API name is `company`.
For an overview of how objects work in Unify, see [Objects in Unify](/reference/objects/overview).
The best way to send data into Unify is with the [Upsert method](/developers/api/data/records/upsert).
The URL for this API method is:
```http theme={null}
https://api.unifygtm.com/data/v1/objects/{object_name}/records/upsert
```
You will need to replace `{object_name}` with the API name of the object you
want to send records into (e.g., `company`, `person`, or a custom object).
For example, the company object's API name is `company`, so the URL is:
```http theme={null}
https://api.unifygtm.com/data/v1/objects/company/records/upsert
```
Under **Setup Inputs**, fill in a few fields:
* **Method**: Choose `POST` when performing an upsert. For other operations,
use the correct method from the API reference.
* **Endpoint**: Insert the URL of the API constructed above.
* **Body**: A valid payload for the [Upsert method](/developers/api/data/records/upsert). For example:
```json theme={null}
{
"match": {
"domain": "{{Website}}"
},
"create_or_update_if_empty": {
"name": "{{Company Name}}",
"domain": "{{Website}}",
"status": "{{Status}}"
}
}
```
A person upsert can also include a nested company match or upsert:
```json theme={null}
{
"match": {
"email": "{{Email}}"
},
"create_or_update_if_empty": {
"first_name": "{{First Name}}",
"last_name": "{{Last Name}}",
"email": "{{Email}}",
"title": "{{Title}}",
"company": {
"match": {
"domain": "{{Company Domain}}"
},
"create_or_update_if_empty": {
"name": "{{Company Name}}",
"domain": "{{Company Domain}}"
}
}
}
}
```
Be sure to replace these placeholders with the actual column names from
your Clay sheet. Press the slash (`/`) key to select a column when editing
the body.
Don't forget to include quotation marks around the column placeholders!
That's it! All of the other settings can be left in their default state or
modified as needed.
Once the action is configured, you can test it by clicking the **Try on 5 rows**
button. You should see a status code in the range 200-299 if the request was
successful.
Finally, select **Save** and choose whether you want to immediately run the
action on all rows in the sheet. Once run, you should see successful status
codes appear in the column.
You can click into any cell in the column to see the full response from the
API. Typically, the Data API will return the full record that was created or
updated.
If you search for that record in Unify, you should see it appear with the
correct data!
## FAQ
Here are a few things to try:
* **Inspect the response**: All error responses should contain details about
why the request failed. Click into the cell in the sheet to view the full
response and look for any error messages or details.
* **Check the object API name**: The URL must use the object's API name,
such as `company`, `person`, or the custom object API name from settings.
* **Check the upsert body**: Upsert requests must include `match` and one of
`create`, `create_or_update`, or `create_or_update_if_empty`.
* **Check the body formatting**: Ensure that the JSON body correctly matches
the expected format of the [Data API](/developers/api/data/overview)
method you are trying to use. Double check that all Clay column variable
placeholders are inside quotation marks.
* **Check nested references**: If you are linking a person to a company,
make sure the nested company value is a valid match, create, or upsert
payload.
* **Check for sheet errors**: Look for a red error symbol in the column name
of the HTTP API column in the sheet. Sometimes, this will indicate an
error in the Clay UI. For example, you may be referencing a deleted column
name, or there may have been a subtle error when pasting the body into the
Clay UI.
# Import data from a CSV
Source: https://docs.unifygtm.com/developers/guides/send-data/csv-import
Upload data from a CSV file or spreadsheet into Unify.
## Overview
This tutorial walks through the process of creating Company records via API
using a simple script. We'll assume the starting point is a CSV of company data
to upload, but in general the approach here can be used for any data source.
The **Company** object is one of the standard objects provided by default in
Unify. Along with **Person**, it powers many of the core capabilities in Unify.
The unique identifier of a company is its website domain.
## Steps
Before you begin, be sure to [generate an API key](https://app.unifygtm.com/dashboard/settings/integrations/api-keys)
if you don't already have one. You’ll pass it as an HTTP header on each request:
```http theme={null}
X-Api-Key:
```
In addition, you will need to install one of the official SDKs to follow along:
```bash Python theme={null}
pip install unifygtm-sdk
```
```bash TypeScript theme={null}
npm install @unifygtm/sdk csv-parse
```
Construct a CSV file that includes the companies you want to send into
Unify. The columns don't need to match the attribute names in Unify since
we'll map them over in the script.
Here's an example CSV for the sake of this tutorial:
```csv example_companies.csv theme={null}
Website,Name,LinkedIn,Score
"tailscale.com","Tailscale","linkedin.com/company/tailscale",100
"dagster.io","Dagster","linkedin.com/company/dagsterlabs",90
"hashicorp.com","HashiCorp","linkedin.com/company/hashicorp",80
"langchain.com","LangChain","linkedin.com/company/langchain",90
"vercel.com","Vercel","linkedin.com/company/vercel",80
"datadoghq.com","Datadog","linkedin.com/company/datadog",70
"temporal.io","Temporal Technologies","linkedin.com/company/temporal-technologies",100
"elastic.co","Elastic","linkedin.com/company/elastic-co",60
"clickhouse.com","ClickHouse","linkedin.com/company/clickhouseinc",90
"streamnative.io","StreamNative","linkedin.com/company/streamnative",70
```
The only required column is a website or domain for the company. That's
because Unify uses a company's domain as its unique identifier. Everything
else is optional.
We’ll use the [upsert method](/developers/api/data/records/upsert) to create
companies if they don't exist and update them if they do. The upsert method
is the recommended way of sending data into Unify since it gives you the
most control and never fails if a record already exists.
The upsert requests we send will have two properties:
* `match` -- This contains the value(s) used to match existing records. In
this case, the only value we will use for matching is the `domain`.
* `create_or_update_if_empty` -- This contains the values used when creating
or updating the record. This will avoid overwriting existing values.
The `create_or_update_if_empty` property never overwrites values if the
record already exists, which is a nice property. For full control over how
existing records are updated, you can specify [more specific properties](/developers/api/data/records/upsert#overview)
instead.
Here’s what a single request looks like for one company row:
```http Request theme={null}
POST /data/v1/objects/company/records/upsert
Host: api.unifygtm.com
Content-Type: application/json
X-Api-Key:
{
"match": {
"domain": "unifygtm.com"
},
"create_or_update_if_empty": {
"name": "Unify",
"domain": "unifygtm.com",
"description": "A nice company with a top-notch API.",
"industry": "Software",
"linkedin_url": "https://www.linkedin.com/company/unifygtm"
}
}
```
The script needs to perform a few steps:
1. Read the CSV
2. Construct an upsert request payload for each row
3. Send the requests
Here's a complete script that does just that:
```python Python theme={null}
import csv
import sys
from typing import TypedDict
from unify import NotFoundError, Unify
class Company(TypedDict):
name: str | None
domain: str
linkedin_url: str | None
account_score: float | None
def read_csv(path: str) -> list[Company]:
rows = []
with open(path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for i, raw in enumerate(reader, start=1):
domain = raw.get("Website")
if not domain:
print(f"[WARN] Row {i} skipped: missing required 'domain'")
continue
rows.append(
{
"name": raw.get("Name"),
"domain": domain,
"linkedin_url": raw.get("LinkedIn"),
"account_score": raw.get("Score"),
}
)
return rows
def main():
if len(sys.argv) != 2:
print("Usage: python upload_companies.py ")
return 2
# Read the CSV file
companies = read_csv(path=sys.argv[1])
if not companies:
print("No valid rows to process.")
return 0
# Instantiate the client
client = Unify()
# Create a custom attribute for the account scores
try:
client.data.attributes.retrieve(
object_name="company",
attribute_name="account_score",
)
except NotFoundError:
client.data.attributes.create(
object_name="company",
api_name="account_score",
display_name="Account Score",
description="Account score for the CSV upload guide.",
type="DECIMAL",
is_required=False,
is_unique=False,
)
print("Created custom attribute 'account_score' for companies.")
# Create or update all records from the CSV file
success = 0
for row in companies:
result = client.data.records.upsert(
object_name="company",
match={
"domain": row["domain"],
},
create_or_update_if_empty={
"domain": row["domain"],
"name": row["name"],
"linkedin_url": row["linkedin_url"],
"account_score": row["account_score"],
},
)
success += 1
print(f"[OK] Upserted company {row['domain']} -> id={result.data.id}")
print(f"Done. Upserted {success} of {len(companies)} companies.")
if __name__ == "__main__":
main()
```
```typescript TypeScript theme={null}
import { readFileSync } from "fs";
import { parse } from "csv-parse/sync";
import Unify from "@unifygtm/sdk";
import { UCompanyAttributes } from "@unifygtm/sdk/resources/data";
interface CompanyRow {
Name?: string;
Website?: string;
LinkedIn?: string;
Score?: string;
}
function readCsv(path: string): UCompanyAttributes[] {
const raw = readFileSync(path, "utf-8");
const rows = parse(raw, { columns: true }) as CompanyRow[];
const companies = new Array();
for (const row of rows) {
if (!row.Website) {
console.log(
`[WARN] Skipping row with missing 'Website': ${JSON.stringify(row)}`,
);
continue;
}
companies.push({
name: row.Name,
domain: row.Website,
linkedin_url: row.LinkedIn,
score: row.Score ? parseFloat(row.Score) : undefined,
});
}
return companies;
}
async function main(): Promise {
const path = process.argv[2];
if (!path) {
console.log("Usage: npx tsx upload_companies.ts ");
process.exit(2);
}
const companies = readCsv(path);
if (companies.length === 0) {
console.log("No valid rows to process.");
return;
}
const client = new Unify();
// Create a custom attribute for the account scores
try {
await client.data.attributes.retrieve("account_score", {
object_name: "company",
});
} catch (err: any) {
if (err.status === 404) {
await client.data.attributes.create("company", {
api_name: "account_score",
display_name: "Account Score",
description: "Account score for the CSV upload guide.",
type: "DECIMAL",
is_required: false,
is_unique: false,
});
console.log("Created custom attribute 'account_score' for companies.");
} else {
throw err;
}
}
// Create or update all records from the CSV file
let success = 0;
for (const row of companies) {
const result = await client.data.records.upsert("company", {
match: {
domain: row.domain,
},
create_or_update_if_empty: {
name: row.name,
domain: row.domain,
linkedin_url: row.linkedin_url,
account_score: row.score,
},
});
success++;
console.log(`[OK] Upserted company ${row.domain} -> id=${result.data.id}`);
}
console.log(`Done. Upserted ${success} of ${companies.length} companies.`);
}
main();
```
Set your API key as an environment variable and then run the script:
```shell Python theme={null}
export UNIFY_API_KEY=""
python send_companies.py example_companies.csv
```
```shell TypeScript theme={null}
export UNIFY_API_KEY=""
npx tsx send_companies.ts example_companies.csv
```
You should see output like this:
```text Output theme={null}
[OK] Upserted company tailscale.com -> id=04caa07b-cbdd-4bb4-9de4-49abdc106aaa
[OK] Upserted company dagster.io -> id=18cd4c36-3e6c-4778-88c3-4bd5c103f381
...
Done. Upserted 10 of 10 companies.
```
You can verify a specific company with the [Find unique record](/developers/api/data/records/find-unique)
API. Simply send a request with a `match` property containing the domain of
one of the companies the script ran on:
```http Request theme={null}
POST /objects/company/find-unique
{
"domain": "tailscale.com"
}
```
```json Response theme={null}
{
"status": "success",
"data": {
"object": "company",
"id": "04caa07b-cbdd-4bb4-9de4-49abdc106aaa",
"created_at": "2025-09-04T01:23:45Z",
"updated_at": "2025-09-04T01:23:45Z",
"attributes": {
"name": "Tailscale",
"domain": "tailscale.com",
"linkedin_url": "linkedin.com/company/tailscale",
"lead_source": "CSV Tutorial"
// ...
}
}
}
```
## Conclusion
This approach works for any type of object in Unify, including both standard
objects and custom objects that you create. This script can be used as a
starting point and customized to send additional values, read from other data
sources, or send different types of records.
# Send records via API
Source: https://docs.unifygtm.com/developers/guides/send-data/overview
Send data from external tools and systems via API.
## Overview
For tools and data sources that can't be connected using an existing
integration, Unify's [Data API](/developers/api/data/overview) can be used
instead. This general-purpose API allows you to programmatically find, create,
update, and delete records in Unify.
## Quickstart
Navigate to
[Settings → Developers](https://app.unifygtm.com/dashboard/settings/integrations/api-keys)
in Unify and select **Generate API Key**.
If you are writing Python or TypeScript code, you can install the official
Unify SDK to simplify interacting with the API.
Install and use the official Unify Python library.
Install and use the official Unify TypeScript library.
You can create records for any object in Unify, including standard objects
like `company` or `person`. But you can also create custom objects with
whatever attributes and relationships you need.
Create and manage objects, attributes, and records in Unify via API.
Create and manage objects, attributes, and records within the Unify app.
Now you can push records into the selected object.
The recommended API for creating and updating records is [Upsert record](/developers/api/data/records/upsert).
This allows you to specify a unique attribute to match existing records and
create or update based on whether a match is found, preventing duplicates.
```python Python theme={null}
record = client.data.records.upsert(
object_name="my_object",
match={
"email": "jane@example.com",
},
create_or_update_if_empty={
"email": "jane@example.com",
"login_count": 12,
"plan_tier": "pro",
},
)
print(f"Record ID: {record.data.id}")
print(f"Email: {record.data.attributes['email']}")
```
```typescript TypeScript theme={null}
const record = await client.data.records.upsert('my_object', {
match: {
email: 'jane@example.com',
},
create_or_update_if_empty: {
email: 'jane@example.com',
login_count: 12,
plan_tier: 'pro',
},
});
console.log(`Record ID: ${record.data.id}`);
console.log(`Email: ${record.data.attributes.email}`);
```
```shell curl theme={null}
curl -X POST 'https://api.unifygtm.com/data/v1/objects/product_user/records/upsert' \
-H 'X-Api-Key: ${UNIFY_API_KEY}' \
-H 'Content-Type: application/json' \
-d '{
"match": {
"email": "jane@example.com"
},
"create_or_update_if_empty": {
"email": "jane@example.com",
"login_count": 12,
"plan_tier": "pro"
}
}'
```
The record should now exist in Unify. You can verify this by searching for
the record in the UI or by retrieving it via API.
```python Python theme={null}
record = client.data.records.find_unique(
object_name="my_object",
match={
"email": "jane@example.com",
},
)
print(f"Record ID: {record.data.id}")
print(f"Email: {record.data.attributes['email']}")
```
```typescript TypeScript theme={null}
const record = await client.data.records.findUnique('my_object', {
match: {
email: 'jane@example.com',
},
});
console.log(`Record ID: ${record.data.id}`);
console.log(`Email: ${record.data.attributes.email}`);
```
```shell curl theme={null}
curl -X POST 'https://api.unifygtm.com/data/v1/objects/product_user/records/find-unique' \
-H 'X-Api-Key: ${UNIFY_API_KEY}' \
-H 'Content-Type: application/json' \
-d '{
"match": {
"email": "jane@example.com"
}
}'
```
## What's next
Sync data from a CSV file or spreadsheet into Unify.
}>
Send records from a Clay sheet into Unify via webhook.
Explore the full Data API reference.
# Capture button clicks
Source: https://docs.unifygtm.com/developers/guides/website-traffic/button-clicks
Record button clicks and other interactions with the Unify Intent client.
## Overview
Website and product interactions are a strong indicator of a visitor's goal. For
example, a visitor to your marketing website who interacts with a pricing
calculator is much more likely to be an interested buyer than the average
visitor.
In Unify, you capture these types of interactions by sending a custom event when
buttons or website components are clicked. If a visitor is then identified
(e.g., from another form fill or based on company IP matches), their
interactions can be viewed in Unify and used to run Plays.
## Prerequisites
* Ensure you've already installed the Unify Intent client on your website.
See [Set up the Unify Intent client](/developers/guides/website-traffic/unify-intent-client)
for instructions.
## Example
Suppose you have a button that opens your pricing calculator. You can log this
interaction with a custom event that includes useful context as properties.
### Send a custom event
Call `track` when the button is clicked. Include properties that help with
attribution and routing later (e.g., `button_id`, page, placement, or variant).
```ts theme={null}
unify.track('Pricing Calculator Opened', {
button_id: 'pricing-calculator',
placement: 'pricing-page-hero',
});
```
### Send multiple custom events
There's no limit on the number of events you can send into Unify, and more data
is always better. You can fire off multiple custom events to capture
interactions at a more granular level.
For example, suppose your pricing calculator contains buttons to select which
pricing plan to view (e.g., "Basic," "Pro," "Enterprise"). You can send an event
each time this button is clicked:
```ts theme={null}
unify.track('Pricing Plan Selected', {
button_id: 'select-pro-plan',
plan: 'Pro',
});
```
Suppose that the pricing calculator also contains a slider to select the usage
quantity they expect to use. This information can be sent as well:
```ts theme={null}
unify.track('Pricing Quantity Selected', {
slider_id: 'usage-quantity',
credits_needed: '5000',
plan: 'Pro',
});
```
Now, you can run a Play that segments visitors based on what pricing tier they
are most likely to be interested in. Your reps will also know what usage tier
they viewed or selected.
### Identifying the visitor
This example assumes the button click itself does not collect any information
about the visitor, such as name or email address. That's fine—if the visitor is
identified later (e.g., via form fill or IP match), Unify will link this event
to that person or company.
If you *do* have information about the visitor at click time, you should also
send an Identify event. This will immediately link the visitor to the person
or company you specify.
```ts theme={null}
// Send a basic "Identify" event
unify.identify('alice@acme.com');
// Send an "Identify" event with company and person information
unify.identify('alice@acme.com', {
person: {
email: 'alice@acme.com',
first_name: 'Alice',
last_name: 'Smith',
title: 'Product Manager',
},
company: {
name: 'Acme Inc.',
domain: 'acme.com',
},
});
```
You do not need to send an Identify event on every click. Send it wherever you
first learn the visitor's identity and then subsequent events will be linked
automatically.
## Launch a Play
With this custom event in place, you can create Plays in Unify that react to
high‑intent clicks like “Pricing Calculator Opened.” For example, a Play could:
1. Apply exclusions (competitors, open opportunities, current customers)
2. Launch an AI agent to research the company or person for ICP fit
3. Enroll the person in a follow‑up sequence
4. Prospect more contacts at the same company and enroll them in outreach
## FAQ
Include any information that may help you qualify and route interactions,
such as:
* Button or form identifier
* Values entered by the visitor
* Page or component context
* Variant (for A/B tests)
When in doubt, err on the side of including more properties.
# Capture form fills
Source: https://docs.unifygtm.com/developers/guides/website-traffic/form-fills
Record form submissions with the Unify Intent client.
## Overview
One of the most effective ways to capture leads on your website is through form
submissions. With Unify, you can capture these submissions and then run Plays on
them.
The Unify Intent client can be used to capture these form fills. This can be
achieved by firing two separate events:
* **Custom event** — This captures the submission and any data entered into the
form. Any Plays will be able to filter companies or people based on whether
they have a matching custom event.
* **Identify event** — This captures the identity of the visitor and links them
to a company or person in Unify. The Identify event is necessary to associate
the form submission with a specific person or company.
Typically, the simplest approach is to send both events at the same time when a
visitor completes and submits a form.
## Prerequisites
* Ensure you've already installed the Unify Intent client on your website.
See [Set up the Unify Intent client](/developers/guides/website-traffic/unify-intent-client)
for instructions.
## Example
Suppose you have a "demo sign-up" form that collects the following information:
* **First name** (e.g., `Alice`)
* **Last name** (e.g., `Smith`)
* **Email address** (e.g., `alice@acme.com`)
* **Company name** (e.g., `Acme Inc.`)
* **What are you most interested in seeing?** (e.g., `AI capabilities`)
To send this data into Unify, you should send two events.
### Send a custom event
First, send a custom event that records the form submission. The form fields can
be included as *properties* on the event. Properties are arbitrary key-value
pairs that can be viewed and used to filter events in Unify.
```ts theme={null}
unify.track('Demo Requested', {
form_id: 'demo-signup',
first_name: 'Alice',
last_name: 'Smith',
company: 'Acme Inc.',
interest: 'AI capabilities',
});
```
### Send an Identify event
Next, send an Identify event that associates the submission with a known person
(using their email address). You can also include additional traits about the
person and their company. These traits will appear on the person and company
records in Unify.
```ts theme={null}
unify.identify('alice@acme.com', {
person: {
email: 'alice@acme.com',
first_name: 'Alice',
last_name: 'Smith',
},
company: {
name: 'Acme Inc.',
},
});
```
## Launch a Play
With both events sent, you can now create Plays in Unify that run on the inbound
lead. Plays can be triggered based on the custom event (e.g., "Demo Requested")
and can use filters based on the properties you sent.
For example, a Play running on inbound form fills could:
1. Apply exclusions to ensure the inbound lead is not a competitor, open
opportunity, or existing customer
2. Launch an AI agent to research the company or person and qualify or
disqualify them based on ICP fit or other criteria
3. Enroll the person in an outbound sequence
4. Find a few more contacts at the same company that match your ideal buyer
persona and enroll them in another outbound sequence
## FAQ
If you don't have an email address to include in the Identify event, Unify
will not have a unique identifier to link this visitor to a person. However,
you have other options.
**Link to a company instead:**
If you have a company website, you can send an Identify event with the
visitor's company instead. This will not link the custom event to a specific
person, but it will link it to a specific company.
You can then run a Play on companies with matching custom events. That Play
can prospect for contacts at the company with ICP personas.
**Capture identity elsewhere:**
If the visitor does not provide an email address on the form, consider
capturing their identity elsewhere on your website. For example:
* If you have another sign-up form that collects email addresses, you can
send an Identify event when a visitor completes that form.
* If you link the custom event to a company, prospect for people at that
company, and then send sequence emails to them, you can track email link
clicks. When a person clicks a link in an email, their website activity
will be identified.
If the visitor is identified through other means, all of their past activity
will also be retroactively identified.
**Automatically identify the company:**
If you do not send an Identify event, Unify will still automatically attempt
to identify the visitor's company using reveal providers based on their IP
address.
If you send an Identify event with a corporate email address but no company,
Unify can infer the company from the email domain.
For example, if you identify a visitor as `alice@acme.com`, Unify can link
the person to the company with the `acme.com` domain. This inference is not
performed for personal or non-corporate email domains. If the visitor uses a
personal email address, include a company domain in the Identify event when
you want to link the person to a company.
Yes. If you want to track partial progress before final submission, send a
custom event when a meaningful step is completed or enable automatic email
input monitoring with the Unify Intent Client.
For most lead-routing workflows, still send a final custom event when the
form is actually submitted. This gives you a clean trigger for Plays and
avoids treating abandoned form activity as a completed hand-raise.
Usually, yes. The custom event captures what happened, such as `Demo
Requested`. The Identify event captures who did it. Sending both gives you a
reliable Play trigger and a person or company record to act on.
# Connect website traffic to Unify
Source: https://docs.unifygtm.com/developers/guides/website-traffic/introduction
Send website visitors, sessions, and events into Unify.
This guide is applicable to any website, but it is especially focused on
marketing websites. If you are looking to connect product usage or analytics
data from your web application, see [Connect product usage data to Unify](/developers/guides/product-usage-data/introduction)
for a more targeted guide.
## Overview
One of the most popular strategies in Unify is to identify the companies and
people who are visiting your website and accessing your content. By sending
website traffic data into Unify, you can instantly launch Plays to start taking
action on these visitors.
Unify can automatically identify companies visitors are associated with by using
a combination of built-in matching providers for high coverage and accuracy.
Unify also supports person-level identification through a combination of email
link click tracking, form fills, and [Identify events](/developers/api/analytics/identify).
Website traffic data in Unify consists of three types of events:
| Event type | What it captures | Common examples |
| -------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- |
| Page view | A visitor viewed a URL. | Pricing page viewed, docs page viewed, product page viewed. |
| Identify event | A visitor's person or company identity became known. | Form submit, sign in, OAuth login, demo request, company domain submitted. |
| Custom event | A named action you define. | Button clicked, form submitted, plan selected, product limit reached. |
All event tracking capabilities integrate natively with the rest of the Unify
data layer. This makes it easy to link website intent to companies or people and
access that data throughout Unify.
## Setup
Unify provides an SDK called the [Unify Intent Client](/developers/intent-client/overview)
that can be installed on your website to start sending visitor events. This is
the easiest and recommended way to get started.
>
}
href="/developers/guides/website-traffic/unify-intent-client"
>
Send events using the Unify's native JavaScript or React client.
If you're already collecting website traffic data using a third-party tool, you
can also send that data into Unify. This can be done using either a supported
integration or the general-purpose [Analytics API](/developers/api/analytics/overview).
You can find guides for popular tools below.
} href="/reference/integrations/segment">
Send Segment events into Unify.
>
}
href="/reference/integrations/posthog"
>
Send PostHog events into Unify.
If you forward events from Segment or PostHog, do not also install the Unify
Intent Client on the same site. Running multiple event sources on the same
site can produce duplicate data.
## Examples
Once you've configured events to be sent into Unify, you can start using that
data to launch Plays based on visitor intent. See the following guides for
examples.
Record form submissions with the Unify Intent client.
Record button clicks and other interactions with the Unify Intent client.
# Set up the Unify Intent client
Source: https://docs.unifygtm.com/developers/guides/website-traffic/unify-intent-client
Send website events directly into Unify with the native SDK.
## Installation
There are multiple ways to install the Unify Intent client depending on your
setup. In most cases, the website tag is the best option to get started.
Paste a pre-configured JavaScript snippet in your website's HTML. This
method is the easiest way to get started and is suitable for most websites.
Install the client in your React web application for a more native
integration.
Install the client in your non-React frontend application, such as a Single
Page App (SPA).
You should only use one installation method per website or application. If the
client is installed twice on the same site, it may not work correctly.
## Usage
### Automatic events
Once the website tag is added, it will automatically start collecting
certain types of events:
* **Page events**: Every time a visitor loads a page on your website, a page
view event is sent to Unify. When that visitor is identified, all their
sessions will be linked to that identity.
* **Custom events**: Certain button clicks and interactions will be tracked
automatically. This does not cover all possible interactions, and you will
likely want to manually trigger events in specific places on your website
for more complete coverage.
* **Identify events**: When a visitor fills out a form with their email
address, they will be automatically identified and linked to that address.
Other information they fill out (such as first and last name) will not be
automatically recorded, so you may also want to manually send "identify"
events on form fills, logins, etc.
If desired, you can disable automatic event collection by setting specific
options when initializing the client. See the full [Usage guide](/developers/intent-client/usage-guide)
for more details.
### Manual events
You can manually trigger events in specific places on your website by
calling the client directly. When you include the tag in your HTML, you will
immediately be able to access the client at `window.unify` (or simply
`unify` since `window` is global).
#### Page events
Page events record a page visit. They are fired automatically by default
when using the website tag, but you can also call them manually.
```javascript theme={null}
// Send a "page" event
unify.page();
```
#### Custom events
Custom events record specific actions taken by a visitor, such as button
clicks or form fills. Some custom events are captured automatically when
using the website tag, but you can also send custom events manually.
```javascript theme={null}
// Send a basic custom event
unify.track("See More Button Clicked");
// Send a custom event with additional properties
unify.track("Modal Opened", {
modalName: "Contact Sales Modal"
});
```
#### Identify events
Identify events link a visitor to a specific company or person. Basic
identify events are sent automatically when a visitor fills out a form with
their email address, but you can also send more detailed identify events
when you have additional information.
```javascript theme={null}
// Send a basic "identify" event
unify.identify("user@email.com");
// Send an "identify" event with additional company or person attributes
unify.identify("first.last@acme.com", {
person: {
email: "first.last@acme.com",
first_name: "First",
last_name: "Last",
},
company: {
domain: "acme.com",
name: "Acme Corp.",
}
});
```
### Automatic events
By default, the React library will not automatically collect any events. You
can enable automatic event collection when initializing the client. See the
full [Usage guide](/developers/intent-client/usage-guide) for more details.
### Manual events
You can manually trigger events in specific places in your web application
by calling the client directly.
#### Page events
Page events record a page visit. Typically, the easiest strategy is to
enable automatic collection of page visits, but you can also call them
manually.
```javascript theme={null}
// Enable automatic "page" event collection
unify.startAutoPage();
// Send a "page" event
unify.page();
```
#### Custom events
Custom events record specific actions taken by a visitor, such as button
clicks or form fills. You can send custom events with arbitrary
properties anywhere in your React application.
```javascript theme={null}
// Send a basic custom event
unify.track("See More Button Clicked");
// Send a custom event with additional properties
unify.track("Modal Opened", {
modalName: "Contact Sales Modal"
});
```
#### Identify events
Identify events link a visitor to a specific company or person. You can send
identify events whenever you have information about a visitor, such as their
name or email address.
```javascript theme={null}
// Send a basic "identify" event
unify.identify("user@email.com");
// Send an "identify" event with additional company or person attributes
unify.identify("first.last@acme.com", {
person: {
email: "first.last@acme.com",
first_name: "First",
last_name: "Last",
},
company: {
domain: "acme.com",
name: "Acme Corp.",
}
});
```
### Automatic events
By default, the JavaScript library will not automatically collect any
events. You can enable automatic event collection when initializing the
client. See the full [Usage guide](/developers/intent-client/usage-guide)
for more details.
### Manual events
You can manually trigger events in specific places in your web application
by calling the client directly.
#### Page events
Page events record a page visit. Typically, the easiest strategy is to
enable automatic collection of page visits, but you can also call them
manually.
```javascript theme={null}
// Enable automatic "page" event collection
unify.startAutoPage();
// Send a "page" event
unify.page();
```
#### Custom events
Custom events record specific actions taken by a visitor, such as button
clicks or form fills. You can send custom events with arbitrary
properties anywhere in your web application.
```javascript theme={null}
// Send a basic custom event
unify.track("See More Button Clicked");
// Send a custom event with additional properties
unify.track("Modal Opened", {
modalName: "Contact Sales Modal"
});
```
#### Identify events
Identify events link a visitor to a specific company or person. You can send
identify events whenever you have information about a visitor, such as their
name or email address.
```javascript theme={null}
// Send a basic "identify" event
unify.identify("user@email.com");
// Send an "identify" event with additional company or person attributes
unify.identify("first.last@acme.com", {
person: {
email: "first.last@acme.com",
first_name: "First",
last_name: "Last",
},
company: {
domain: "acme.com",
name: "Acme Corp.",
}
});
```
For complete instructions, see the [Usage guide](/developers/intent-client/usage-guide).
# Install JavaScript package
Source: https://docs.unifygtm.com/developers/intent-client/js-client
Install the Unify Intent client in a frontend web application framework.
## Installation
You can install the Unify Intent JS Client with your preferred package manager:
```shell npm theme={null}
npm install @unifygtm/intent-client
```
```shell yarn theme={null}
yarn add @unifygtm/intent-client
```
```shell pnpm theme={null}
pnpm add @unifygtm/intent-client
```
```shell bun theme={null}
bun add @unifygtm/intent-client
```
## Usage
After installing the client, you must initialize it in your application code:
```ts index.ts theme={null}
import { UnifyIntentClient, UnifyIntentClientConfig } from '@unifygtm/intent-client';
const writeKey = 'YOUR_PUBLIC_WRITE_KEY';
const config: UnifyIntentClientConfig = {
autoPage: true,
autoIdentify: false,
};
const unify = new UnifyIntentClient(writeKey, config);
// Do not call mount during server side rendering. Only call it in a browser context.
unify.mount();
```
Be sure to only initialize the client one time in your application.
For details on how to use the client to send events, see the full [Usage guide](/developers/intent-client/usage-guide).
## Cleanup
When you are done using the client, if you wish to clean up the side effects
generated by it, you can do so with the `unmount` method:
```ts cleanup.ts theme={null}
import { UnifyIntentClient, UnifyIntentClientConfig } from '@unifygtm/intent-client';
const writeKey = 'YOUR_PUBLIC_API_KEY';
const unify = new UnifyIntentClient(writeKey);
unify.mount();
// Use the client for some time, then later...
unify.unmount();
```
# Unify Intent Client
Source: https://docs.unifygtm.com/developers/intent-client/overview
Send website and product events directly into Unify with the native SDK.
## Overview
The Unify Intent client is a JavaScript library that allows you to collect
events from your website and send them to Unify. It supports page, identify, and
custom events.
e.g. a user visits the pricing page of your marketing website
e.g. a user clicks a button to open a pricing calculator
e.g. a user logs into your web app with their email address
When you install the Unify Intent client on your website, it can automatically
collect these events and send them to Unify. You can also customize its behavior
by sending events manually.
The Intent Client source code is [available on GitHub](https://github.com/unifygtm/intent-js-client).
## Installation
There are multiple ways to install the Unify Intent client depending on your
setup. In most cases, the website tag is the best option to get started.
Paste a pre-configured JavaScript snippet in your website's HTML. This
method is the easiest way to get started and is suitable for most websites.
Install the client in your React web application for a more native
integration.
Install the client in your non-React frontend application, such as a Single
Page App (SPA).
You should only use one installation method per website or application. If the
client is installed twice on the same site, it may not work correctly.
## Usage
If you install the client using the website tag method, it will start collecting
page visits automatically. Often, no further setup is required unless you wish
to track visitor interactions manually.
For other installation methods, or to learn how to manually send other types of
events, see the complete [Usage guide](/developers/intent-client/usage-guide).
# Install React package
Source: https://docs.unifygtm.com/developers/intent-client/react
Install the Unify Intent client in a React app.
## Installation
You can install the Unify Intent React library with your preferred package manager:
```shell npm theme={null}
npm install @unifygtm/intent-react
```
```shell yarn theme={null}
yarn add @unifygtm/intent-react
```
```shell pnpm theme={null}
pnpm add @unifygtm/intent-react
```
```shell bun theme={null}
bun add @unifygtm/intent-react
```
## Usage
First, wrap your React app in a `UnifyIntentProvider`:
```tsx index.tsx theme={null}
import {
UnifyIntentClient,
UnifyIntentClientConfig,
UnifyIntentProvider,
} from '@unifygtm/intent-react';
const writeKey = 'YOUR_PUBLIC_WRITE_KEY';
const config: UnifyIntentClientConfig = {
autoPage: true,
autoIdentify: false,
};
const intentClient = new UnifyIntentClient(writeKey, config);
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
root.render(
);
```
The `UnifyIntentProvider` automatically takes care of mounting and unmounting the client
for you whenever the component mounts and unmounts. Any components rendered in your app
can access and use the intent client using the `useUnifyIntent` hook:
```tsx Example.tsx theme={null}
import { useUnifyIntent } from '@unifygtm/intent-react';
const Example = () => {
// Get the Unify Intent Client
const unify = useUnifyIntent();
// However you access the current user...
const currentUser = useCurrentUser();
useEffect(() => {
if (currentUser?.emailAddress) {
// Log an identify event for the current user
unify.identify(currentUser.emailAddress, {
firstName: currentUser.firstName,
lastName: currentUser.lastName,
company: {
name: currentUser.companyName,
domain: currentUser.companyDomain
},
});
}
}, [currentUser, unify]);
...
};
export default Example;
```
For details on how to use the client to send events, see the full [Usage guide](/developers/intent-client/usage-guide).
# Usage guide
Source: https://docs.unifygtm.com/developers/intent-client/usage-guide
Learn how to send events using the Unify Intent Client.
The Unify Intent Client can be used to log user activity across multiple subdomains of the
same top-level domain. For example, if a user visits your marketing website at `www.yoursite.com`
and then logs into your production web application at `app.yoursite.com`, the activity in both
places will be attributed to the same person.
## Event types
### Page view events
Website page views are an indicator of buyer intent. You can log this information to the Unify platform for usage with the `page` method.
There are two ways to collect page data with the Unify intent client:
1. Automatic monitoring of the current page (see [here](#automatic-page-monitoring))
2. Manually via the client `page` method (see [here](#manual-page-logging))
Utilizing both of these methods when appropriate is recommended to take full advantage of intent data within Unify.
#### Automatic page monitoring
The Unify intent client is capable of automatically monitoring the user's current page to trigger page events. This will happen by default when the client is installed via the Unify JavaScript tag. If the client is installed via a package manager, you must pass the `autoPage` configuration option when instantiating the client. See [Configuration](#configuration) below for more details.
In either case, this behavior can be enabled or disabled programmatically via the `startAutoPage` and `stopAutoPage` methods on the client:
```TypeScript theme={null}
// Initialize the client and tell it to automatically monitor pages
const unify = new UnifyIntentClient(
'YOUR_PUBLIC_WRITE_KEY',
{ autoPage: true },
);
unify.mount();
// Tell the client to stop monitoring pages
unify.stopAutoPage();
// Tell the client to start monitoring pages again
unify.startAutoPage();
```
#### Manual page logging
You can also manually trigger a page event with the `page` method on the client. This is useful when you do not want to trigger page events for *every* page.
```TypeScript theme={null}
const unify = new UnifyIntentClient('YOUR_PUBLIC_WRITE_KEY');
unify.mount();
// Trigger a page event for whatever page the user is currently on
unify.page();
// Trigger a page event for a custom page that the user is not necessarily on
unify.page({ pathname: '/some-custom-page' });
```
### Identify events
All intent data collected for users by Unify is anonymous by default. When intent events are logged, Unify will attempt to automatically de-anonymize the IP address of a user to associate them with a specific company, but their personal identity will remain anonymous until an identify event is triggered for them.
There are two ways to collect identity data with the Unify intent client:
1. Automatic monitoring of email input elements (see [here](#automatic-input-monitoring))
2. Manually via the client `identify` method (see [here](#manual-identification))
Utilizing both of these methods when appropriate is recommended to take full advantage of intent data within Unify.
#### Automatic input monitoring
The Unify intent client is capable of automatically monitoring text and email input elements on the page to collect user identity. This will happen by default when the client is installed via the Unify JavaScript tag. If the client is installed via a package manager, you must pass the `autoIdentify` configuration option when instantiating the client. See [Configuration](#configuration) below for more details.
In either case, this behavior can be enabled or disabled programmatically via the `startAutoIdentify` and `stopAutoIdentify` methods on the client:
```TypeScript theme={null}
// Initialize the client and tell it to automatically monitor inputs
const unify = new UnifyIntentClient(
'YOUR_PUBLIC_WRITE_KEY',
{ autoIdentify: true },
);
unify.mount();
// Tell the client to stop monitoring inputs for now
unify.stopAutoIdentify();
// Tell the client to start monitoring inputs again
unify.startAutoIdentify();
```
#### Manual identification
You can also manually trigger an identify event with the `identify` method on the client. This is useful when users log-in with OAuth or SSO, for example, because they do not enter their email into an input on the page.
When using this method, you can also optionally specify a set of Person or Company attributes to upsert and associate with the identified user. This is done in the form of an optional second argument to `identify`.
```TypeScript theme={null}
const unify = new UnifyIntentClient('YOUR_PUBLIC_WRITE_KEY');
unify.mount();
// However you determine the currently logged-in user
const currentUser = getCurrentUser();
// Identify the current user
unify.identify(currentUser.emailAddress);
// OR identify the current user and upsert the associated Company/Person in Unify
unify.identify(
currentUser.emailAddress,
{
person: {
email: currentUser.emailAddress,
first_name: currentUser.firstName,
last_name: currentUser.lastName,
},
company: {
domain: currentUser.organization.domain, // must match domain of email above
name: currentUser.organization.name,
}
}
)
```
### Custom events
Certain user actions are valuable indicators of buying intent. You can use custom events to log these events along with custom properties for each event that you can then use within Unify to filter your visitors and act accordingly.
There are three ways to fire custom events with the Unify intent client:
1. Manually via the client `track` method (see [here](#manual-tracking))
2. Custom HTML data attributes (see [here](#html-data-attributes))
3. Automatically with CSS selectors (see [here](#automatic-tracking))
#### Manual tracking
You can also manually trigger a custom event with the `track` method on the client:
```TypeScript theme={null}
const unify = new UnifyIntentClient('YOUR_PUBLIC_WRITE_KEY');
unify.mount();
// Only the event name is required - we recommend spaces between capitalized words
unify.track('See More Button Clicked');
// You can also specify custom properties to include
unify.track('Modal Opened', { modalName: 'Contact Sales Modal' });
```
The `track` method accepts two arguments:
1. A string `name` (required) - this is used to identify the event downstream within Unify.
2. An object of keys and string values `properties` (optional) - this is used to specify custom properties associated with the event which can be used for filtering and identification downstream within Unify.
#### HTML data attributes
You can leverage various data attributes in the HTML of your site or application to automatically track click events for elements you care about:
##### `data-unify-click-event-name`
The presence of this attribute on an element indicates that the intent client should automatically fire an event when the element is clicked. The *value* of this attribute defines the **name** of the event which will be fired. For example, clicking the `button` in the following HTML will result in a custom event with the name `See More Button Clicked` to be fired:
```html theme={null}
```
By default, the intent client will make a best effort at identifying a **label** for the clicked element to include in the **properties** of the event which is fired. It will do so using the text content and ARIA attributes of the element. This can be used to differentiate between custom events with the *same name* fired for *different elements*. If the client is not able to determine a human-readable label, the `label` property will simply be omitted from the event properties. For example, clicking the `div` in the following HTML will result in a custom event with the name `Download Button Clicked` to be fired with `properties` containing a `label` with the value `Free sample`:
```html theme={null}
Free sample
```
##### `data-unify-label`
This attribute can be used to override the label (or provide a label which is otherwise missing) for an element that is tracked by the client. For example, even though clicking the `button` in the following HTML would normally result in a custom event with the label `See more` to be fired, the presence of the `data-unify-label` attribute results in the event being fired with a `label` property of `Custom label`:
```html theme={null}
```
##### `data-unify-event-prop-*`
By default, only the label of an element is included in the `properties` of auto tracked events. You can specify additional properties to include using the prefix `data-unify-event-prop-`. For example, clicking the `button` in the following HTML will result in an event being fired with `properties` including a property of `customValue` set to `100`:
```html theme={null}
```
#### Automatic tracking
The Unify intent client is capable of automatically monitoring elements that match a list of CSS selectors specified by you in the `UnifyIntentClientConfig`'s `autoTrackOptions.clickTrackingSelectors`. When an element matching one of these selectors is clicked by the user, the client will automatically fire an event for it.
`clickTrackingSelectors` can be a list of simple CSS string selectors, in which case the default event name `Element Clicked` will be used:
```typescript theme={null}
const options: AutoTrackOptions = {
clickTrackingSelectors: ['.button', '.another-custom-selector'],
};
// Will fire `Element Clicked` events for clicked elements matching CSS selectors
const unify = new UnifyIntentClient('YOUR_PUBLIC_WRITE_KEY', {
autoTrackOptions: options,
});
```
Alternatively, you can customize the event name fired for each selector by passing a list of objects for `clickTrackingSelectors`, where each object contains a `selector` string and optional `eventName` which will be used as the name of the auto tracked event:
```typescript theme={null}
const options: AutoTrackOptions = {
clickTrackingSelectors: [
{ selector: '.button', eventName: 'Button Clicked' },
{
selector: '.another-custom-selector',
eventName: 'Custom Element Clicked',
},
],
};
// Will fire events with specified custom names for clicked elements matching CSS selectors
const unify = new UnifyIntentClient('YOUR_PUBLIC_WRITE_KEY', {
autoTrackOptions: options,
});
```
You can use the `data-unify-label` to customize the element `label` in the event `properties` and the `data-unify-event-prop-` prefix to add custom `properties`. See [HTML data attributes](#html-data-attributes) for more info.
If you would like to exclude a specific element from tracking which matches your specified CSS selectors list, you can do so with the `data-unify-exclude` attribute.
This behavior can be enabled or disabled programmatically via the `startAutoTrack` and `stopAutoTrack` methods on the client:
```TypeScript theme={null}
// Initialize the client and tell it to automatically track clicks for all elements with class="button"
const unify = new UnifyIntentClient(
'YOUR_PUBLIC_WRITE_KEY',
{ autoTrackOptions: { clickTrackingSelectors: ['.button'] } },
);
unify.mount();
// Tell the client to stop monitoring buttons for now
unify.stopAutoTrack();
// Tell the client to start monitoring buttons again
unify.startAutoTrack();
// OR tell the client to start monitoring something else
unify.startAutoTrack({ clickTrackingSelectors: ['.custom-button'] });
```
## Server side
It is possible to send requests to the Unify Intent API directly. You might do this on a web server, for example, to circumvent ad blockers. The Unify client exposes a method for each of its event types (`page`, `identify`, and `track` events) which can be used to generate the request payload for each event. This payload can then be sent to your web server which functions as a proxy to forward the request directly to the Unify Intent API.
The three methods on the client for generating event payloads are the following:
* `getPagePayload`
* `getIdentifyPayload`
* `getTrackPayload`
Below is an example of using the `getIdentifyPayload` method in a React app to generate the payload for an `identify` event, send it to a proxy web server, and forward it to the Unify Intent API.
```tsx client.tsx theme={null}
// However you make requests to your web server
const apiClient = useApiClient();
// However you get your current user object
const currentUser = useCurrentUser();
// Get the Unify client
const unify = useUnifyIntent();
useEffect(() => {
if (currentUser.email) {
const payload = unify.getIdentifyPayload(currentUser.email);
// If the email is valid
if (payload) {
// Send event to server
apiClient.post('/identify', payload);
}
}
}, [currentUser, unify, apiClient]);
```
```TypeScript server.ts theme={null}
const unifyClient = axios.create({
// The Unify Intent API URL
baseURL: 'https://api.unifyintent.com/analytics/v1',
headers: {
'Content-type': 'application/json; charset=UTF-8',
// Your public write key which can be found at https://app.unifygtm.com/dashboard/settings/integrations/unify-intent-client
'X-Write-Key': 'wk_5fTtsDLJ_7vx9DsjPcr79yk4FweES727w59pxS8EJ',
},
});
router.post(
'/identify',
async (req: Request, res: Response) => {
// Forward the event to the Unify Intent API
const response = await unifyClient.post('/identify', req.body);
return res.status(200).json({
success: response.status === 200,
});
}
);
```
## Third-party tools
The intent client ships with out-of-the-box event tracking for some popular third-party tools such as [Default](https://www.default.com/) and [Navattic](https://www.navattic.com/). If these tools are embedded into a page where the intent client is running, the client will track relevant events automatically. This behavior can be disabled with the `UnifyIntentClientConfig`.
See [Configuration](#configuration) for more details on configuring the intent client.
### Default
[Default](https://www.default.com/) is a popular tool for automating inbound form submission workflows. If you use a Default form wherever the intent client is running, the client will automatically fire events for corresponding Default form events. The following Default form events are supported:
| Default event | Unify event name | Unify event properties | Description |
| ----------------------------- | ----------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------- |
| `default.form_completed` | `Default Form Completed` | `form`, `formId` | When a user completes all steps in a form. |
| `default.form_page_submitted` | `Default Form Page Submitted` | `form`, `formId`, `pageNumber` | When a user completes a single step of a mult-step form. |
| `default.meeting_booked` | `Default Meeting Booked` | `memberName`, `memberEmail`, `durationInMinutes`, `startDateTime` | When a user successfully books a meeting via the Default scheduler. |
| `default.scheduler_closed` | `Default Scheduler Closed` | | When a user closes the Default scheduler UI. |
| `default.scheduler_displayed` | `Default Scheduler Displayed` | `formId` | When the Default scheduler UI is displayed to the user. |
### Navattic
[Navattic](https://www.navattic.com/) is a popular tool for automating interactive product demos. If you use a Navattic demo wherever the intent client is running, the client will automatically fire events for corresponding Navattic demo events. The following Navattic demo events are supported:
| Navattic event | Unify event name | Unify event properties | Description |
| --------------- | --------------------------- | ---------------------- | --------------------------------------------------- |
| `START_FLOW` | `Navattic Demo Started` | `demo` | When a user starts a Navattic demo. |
| `VIEW_STEP` | `Navattic Demo Step Viewed` | `demo`, `step` | When a user views a new step of the demo. |
| `COMPLETE_FLOW` | `Navattic Demo Completed` | `demo` | When a user successfully completes the entire demo. |
## Configuration
If installed via the [Unify Website tag](./website-tag) then the `autoPage` config will default to `true`.
Tells the client to automatically log page events whenever the current page changes.
Works for static websites and Single Page Apps. Also logs a page event for the initial page.
If installed via the [Unify Website tag](./website-tag) then the `autoIdentify` config will default to `true`.
Tells the client to automatically monitor text and email input elements on the
page for changes. When the current user enters a valid email address into an input, the client
will log an identify event for that email address.
Options to customize the auto-tracking of user actions such as click events.
Optional list of CSS selectors to customize which elements the client will automatically fire a custom event for when clicked.
Can be a list of string selectors, in which case the default event name `Element Clicked` will be used as the event name.
Can also be a list of objects containing a `selector` key and `eventName` key, in which case the value of `eventName` will be used as the event name.
By default, the intent client will fire custom events for [Default](https://www.default.com/) form events.
This option can be used to customize which Default form events will result in Unify events being fired or disable this behavior entirely.
By default, the intent client will fire custom events for [Navattic](https://www.navattic.com/) product demos.
This option can be used to customize which Navattic demo events will result in Unify events being fired or disable this behavior entirely.
Length in minutes that user sessions will persist when no activities are tracked. Activities include `page`, `identify,` and `track` activities.
## Cookies
This section only applies to intent client versions `1.4.0` and up. If you install the intent client with the website tag, you automatically get access to the latest client version. Versions older than this use obfuscated cookie names.
When the intent client mounts, it places two values in the user's cookies:
* `unify_visitor_id` - A randomly generated UUID which uniquely identifies the user. This persists across sessions.
* `unify_session_id` - A randomly generated UUID which uniquely identifies the user's current session. Sessions will persist as long as a new `page`, `identify`, or `track` event is fired at least once every 30 minutes. This duration be customized with the `sessionDurationMinutes` option on the `UnifyIntentClientConfig`.
These cookies are *first-party cookies* and associated with the top-level domain where the intent client is running.
**Example**
If the intent client is running on [https://www.unifygtm.com](https://www.unifygtm.com) then the cookies will be associated with `.unifygtm.com`. This means that they are accessible and reused across all subdomains of the top-level domain.
In this example, if the intent client were also running on [https://app.unifygtm.com](https://app.unifygtm.com) then a visitor ID stored while on the `www` subdomain would be reused on `app` subdomain.
These cookies are stored for the maximum permitted time by Google Chrome of 400 days and updated every time the visitor visits your site. In other words, as long as the same visitor visits your site at least once every 400 days and does not clear their browser cookies, their visitor ID will be reused across sessions. Note that some browsers have default limits lower than 400 days. In these cases, the maximum allowed limit by the browser will be used.
# Website tag
Source: https://docs.unifygtm.com/developers/intent-client/website-tag
Set up the Unify Intent client on a marketing website.
## Installation
You can automatically load and install the client by placing a `