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 centerAPI DomainAccounts (OAuth) Host
USwww.zohoapis.comaccounts.zoho.com
EUwww.zohoapis.euaccounts.zoho.eu
Indiawww.zohoapis.inaccounts.zoho.in
Australiawww.zohoapis.com.auaccounts.zoho.com.au
Japanwww.zohoapis.jpaccounts.zoho.jp
Canadawww.zohoapis.caaccounts.zohocloud.ca

Create an OAuth application

  1. Go to the Zoho API Console (sign in with your Zoho CRM admin account).
  2. Click Add Client and choose Self Client (for a single-tenant server integration).
  3. Note the Client ID and Client Secret.

Generate a refresh token

  1. In the Self Client, open the Generate Code tab.
  2. Enter the scope ZohoCRM.modules.ALL (or the narrower ZohoCRM.modules.leads.CREATE).
  3. Choose a duration and generate the grant code.
  4. 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
  1. Copy the refresh_token from the response.
⚠️

The grant code expires within minutes. Generate it and exchange it immediately. The refresh token is long-lived — store it securely.

Configuration

FieldRequiredDescription
api_domainYesYour data center API host (no scheme), e.g. www.zohoapis.com.
accounts_urlYesYour data center OAuth host (no scheme), e.g. accounts.zoho.com.
moduleYesThe module records are upserted into. Use Leads for lead capture.
client_idYesOAuth Client ID from the Zoho API Console.
client_secretYesOAuth Client Secret.
refresh_tokenYesLong-lived refresh token with ZohoCRM.modules.ALL scope.

Configure in Signal

  1. Go to Integrations > Add Integration > Zoho CRM.
  2. Select the Lead Capture preset.
  3. Enter your api_domain, accounts_url, module, client_id, client_secret, and refresh_token.
  4. Select consent categories (typically marketing).
  5. 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}/upsert

For example, US Leads:

POST https://www.zohoapis.com/crm/v8/Leads/upsert

Authentication 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):

TraitZoho fieldNotes
emailEmailThe system-defined duplicate-check field for Leads and Contacts. Upserts dedupe on it.
firstNameFirst_Name
lastNameLast_NameMandatory for the Leads module.
phonePhone
companyCompanyMandatory 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 eventZoho action
IdentifiedUpsert into Leads (dedupe on Email)
Lead GeneratedUpsert into Leads, sets Lead_Source
Signed UpUpsert 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

  1. In Zoho CRM, open the Leads module.
  2. Find the lead by the email you sent — a new record appears, or an existing one is updated.
  3. Confirm First_Name, Last_Name, Company, and Lead_Source are 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

ProblemSolution
401 INVALID_TOKENThe 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 requestapi_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 recordA required Lead field is missing. Send lastName and company on every identified event.
INVALID_DATA per recordA 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_PATTERNThe module name is wrong or not capitalised correctly (Leads, not leads).
DUPLICATE_DATAExpected 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.