Zoho CRM
Datafly Signal delivers identify and lead events server-to-server into Zoho CRM using the V8 REST Upsert Records API. Each event creates a new record or updates the matching one (deduplicated on email), keeping your CRM in sync with first-party website signals without a browser script.
Prerequisites
Before configuring Zoho CRM in Signal, you need a Zoho CRM account and an OAuth application that can issue a long-lived refresh token.
Identify your data center
Zoho is region-partitioned. Every account lives in one data center (US, EU, India, Australia, Japan, Saudi Arabia, China, or Canada), and your API host, OAuth host, and tokens must all belong to the same one. Check the domain you log in with:
| Data center | API Domain | Accounts (OAuth) Host |
|---|---|---|
| US | www.zohoapis.com | accounts.zoho.com |
| EU | www.zohoapis.eu | accounts.zoho.eu |
| India | www.zohoapis.in | accounts.zoho.in |
| Australia | www.zohoapis.com.au | accounts.zoho.com.au |
| Japan | www.zohoapis.jp | accounts.zoho.jp |
| Canada | www.zohoapis.ca | accounts.zohocloud.ca |
Create an OAuth application
- Go to the Zoho API Console (sign in with your Zoho CRM admin account).
- Click Add Client and choose Self Client (for a single-tenant server integration).
- Note the Client ID and Client Secret.
Generate a refresh token
- In the Self Client, open the Generate Code tab.
- Enter the scope
ZohoCRM.modules.ALL(or the narrowerZohoCRM.modules.leads.CREATE). - Choose a duration and generate the grant code.
- Exchange the grant code for a refresh token at your data center’s token endpoint:
curl https://accounts.zoho.com/oauth/v2/token \
-d grant_type=authorization_code \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d code=THE_GRANT_CODE- Copy the
refresh_tokenfrom the response.
The grant code expires within minutes. Generate it and exchange it immediately. The refresh token is long-lived — store it securely.
Configuration
| Field | Required | Description |
|---|---|---|
api_domain | Yes | Your data center API host (no scheme), e.g. www.zohoapis.com. |
accounts_url | Yes | Your data center OAuth host (no scheme), e.g. accounts.zoho.com. |
module | Yes | The module records are upserted into. Use Leads for lead capture. |
client_id | Yes | OAuth Client ID from the Zoho API Console. |
client_secret | Yes | OAuth Client Secret. |
refresh_token | Yes | Long-lived refresh token with ZohoCRM.modules.ALL scope. |
Configure in Signal
- Go to Integrations > Add Integration > Zoho CRM.
- Select the Lead Capture preset.
- Enter your
api_domain,accounts_url,module,client_id,client_secret, andrefresh_token. - Select consent categories (typically
marketing). - Click Save.
Signal exchanges your refresh token for a one-hour access token automatically and refreshes it as needed.
API Endpoint
POST https://{api_domain}/crm/v8/{module}/upsertFor example, US Leads:
POST https://www.zohoapis.com/crm/v8/Leads/upsertAuthentication is sent as Authorization: Zoho-oauthtoken {access_token}. The body is a data array of record objects keyed by Zoho field API names.
Identity Signals
Zoho CRM is a human-facing record store, so identity is the record’s natural keys, sent in plaintext (unlike ad-platform APIs that require hashed match keys):
| Trait | Zoho field | Notes |
|---|---|---|
email | Email | The system-defined duplicate-check field for Leads and Contacts. Upserts dedupe on it. |
firstName | First_Name | |
lastName | Last_Name | Mandatory for the Leads module. |
phone | Phone | |
company | Company | Mandatory for the Leads module. |
Provide lastName and company on every identified event destined for the Leads module — both are mandatory in the default Lead layout, and Zoho rejects records that omit them with a MANDATORY_NOT_FOUND per-record error.
How to send user data
Call datafly.identify() when a user submits a form, registers, or logs in:
datafly.identify("user-123", {
email: "jane.doe@example.com",
firstName: "Jane",
lastName: "Doe",
phone: "+44 7700 900123",
company: "Acme Ltd"
});Event Mapping
Lead Capture preset
| Signal event | Zoho action |
|---|---|
Identified | Upsert into Leads (dedupe on Email) |
Lead Generated | Upsert into Leads, sets Lead_Source |
Signed Up | Upsert into Leads |
Page views and other analytics events are dropped — a CRM is not an analytics sink. To capture custom fields, map additional traits to your module’s field API names (retrieve them via Zoho’s Fields metadata API) in the integration’s Field Mappings.
Example: Lead Generated event
Datafly.js call:
datafly.identify("user-123", {
email: "jane.doe@example.com",
firstName: "Jane",
lastName: "Doe",
company: "Acme Ltd"
});
datafly.track("Lead Generated", {
form_name: "Request a Demo",
source: "Website"
});Zoho CRM payload sent by Signal:
{
"data": [
{
"Email": "jane.doe@example.com",
"First_Name": "Jane",
"Last_Name": "Doe",
"Company": "Acme Ltd",
"Lead_Source": "Website",
"Description": "Request a Demo"
}
]
}Because duplicate_check_fields is omitted, Zoho falls back to the module’s system-defined unique field (Email for Leads), so a repeat submission updates the existing lead instead of creating a duplicate.
Testing
Send a test event
Trigger an Identified or Lead Generated event on your site, or replay one from Signal’s event debugger.
Verify in Zoho CRM
- In Zoho CRM, open the Leads module.
- Find the lead by the email you sent — a new record appears, or an existing one is updated.
- Confirm
First_Name,Last_Name,Company, andLead_Sourceare populated.
Inspect the response
Zoho returns a per-record status for each entry in data. A successful upsert returns "status": "success" with "code": "SUCCESS". Review the raw response in Signal’s event debugger.
Troubleshooting
| Problem | Solution |
|---|---|
401 INVALID_TOKEN | The access token expired or the auth prefix is wrong. Confirm the refresh token, client ID, and client secret are correct and from the same data center as api_domain. |
404 on the request | api_domain or module is wrong — most often a data-center mismatch. Ensure api_domain and accounts_url match the DC your refresh token was issued in. |
MANDATORY_NOT_FOUND per record | A required Lead field is missing. Send lastName and company on every identified event. |
INVALID_DATA per record | A mapped field name is not a real field API name in your module. Retrieve valid API names from Zoho’s Fields metadata API and update your Field Mappings. |
INVALID_URL_PATTERN | The module name is wrong or not capitalised correctly (Leads, not leads). |
DUPLICATE_DATA | Expected behaviour suppressed — confirm Email is the duplicate-check field and that you are using the /upsert endpoint, not /insert. |
Rate Limits
Zoho CRM enforces a per-day API credit limit that varies by edition. The Upsert API accepts up to 100 records per call. For most lead-capture volumes the default one-record-per-event delivery is well within limits; enable batching if you expect high throughput.