Zoho CRM

Zoho CRM is a CRM. Datafly Signal upserts records server-side through the V8 Upsert Records API (POST /crm/v8/<module>/upsert), matched on Email, so lead forms, sign-ups and identify calls create or update one lead.

Why route Zoho CRM through Datafly Signal

  • Records come from your server, not the browser. They reach Zoho CRM even when a tracker blocker or a failed page unload would have dropped a client-side call, and no form integration or browser tag is needed.
  • Delivery is queued and retried. Signal buffers to Kafka and retries on 429 or a transient failure rather than dropping the event; a permanent rejection goes to the dead-letter queue with the vendor’s reason.
  • One consent decision governs every destination. Zoho CRM is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.

Get your Zoho CRM credentials

Create a self client

In the Zoho API Console create a Self Client and generate a grant token with scope ZohoCRM.modules.ALL (or ZohoCRM.modules.leads.ALL).

Exchange it for a refresh token

Exchange the grant token at your data centre’s accounts host (accounts.zoho.eu …) for a refresh token. Signal exchanges the refresh token for one-hour access tokens.

Note your data centre hosts

The API domain (www.zohoapis.eu …) and accounts host of the same data centre.

Configuration

FieldRequiredDescription
api_domainYesYour Zoho data-center API host (no scheme). US: www.zohoapis.com, EU: www.zohoapis.eu, IN: www.zohoapis.in, AU: www.zohoapis.com.au, JP: www.zohoapis.jp, CA: www.zohoapis.ca. Must match the data center your refresh token was created in. Returned as api_domain in the OAuth token response.
accounts_urlYesYour Zoho Accounts host for the OAuth token endpoint (no scheme). US: accounts.zoho.com, EU: accounts.zoho.eu, IN: accounts.zoho.in, AU: accounts.zoho.com.au, JP: accounts.zoho.jp, SA: accounts.zoho.sa, CN: accounts.zoho.com.cn, CA: accounts.zohocloud.ca. Must match the same data center as the API Domain.
moduleYesThe CRM module API name records are upserted into. Common values: Leads, Contacts, Deals. Records are deduplicated on the module’s system-defined unique field (Email for Leads/Contacts).
client_idYesOAuth Client ID from your Zoho self-client or connected app (Zoho API Console).
client_secretYesOAuth Client Secret from your Zoho API Console application.
refresh_tokenYesLong-lived OAuth refresh token generated for scope ZohoCRM.modules.ALL (or ZohoCRM.modules.<module>.CREATE). Signal exchanges it for a 1-hour access token automatically.

Management UI setup

  1. Go to Integrations in the sidebar and open the Integration Library tab.
  2. Find Zoho CRM under CRM and click Install.
  3. Choose a preset, enter the fields above and click Install Integration.

Management API setup

curl -X POST http://localhost:8084/v1/admin/integration-catalog/zoho_crm/install \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Zoho CRM",
  "variant": "default",
  "config": {
    "api_domain": "your_api_domain",
    "accounts_url": "your_accounts_url",
    "module": "your_module",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret",
    "refresh_token": "your_refresh_token"
  },
  "delivery_mode": "server_side"
}'

What Signal sends

An example request, rendered from the default preset for generate_lead:

POST https://www.zohoapis.eu/crm/v8/Leads/upsert
 
{
  "data": [
    {
      "City": "Bath",
      "Company": "Acme Ltd",
      "Country": "GB",
      "Description": "Lead form: contact-us",
      "Designation": "Head of Growth",
      "Email": "neill@example.com",
      "First_Name": "Neill",
      "Last_Name": "Mcfixture",
      "Phone": "+447700900123",
      "State": "Somerset",
      "Website": "https://acme.example",
      "Zip_Code": "BA1 1AA"
    }
  ]
}
FieldSource
data[0].EmailThe email, trimmed and lower-cased; the duplicate-check field for Leads and Contacts. An event without one is not sent.
First_Name, Last_NameFrom traits. Zoho requires Last_Name: an event without one is not sent.
PhoneThe phone trait in E.164.
Company, Designation, WebsiteFrom traits (title for the designation).
City, State, Zip_Code, CountryFrom the address trait; the country is upper-cased.
DescriptionThe form, lead type or event that produced the record.

Zoho matches the record by email, so email, name and phone are sent in plain text to your own Zoho CRM.

Presets

PresetSignal events
Lead Capture (default)identify, generate_lead, sign_up
B2B / SaaS (saas)identify, generate_lead, demo_requested, sign_up, trial_started
Insurance (insurance)quote_requested, generate_lead
Automotive (automotive)test_drive_booked, dealer_contacted, generate_lead

Events not listed are not sent. Each preset is a starting point: add, rename or remove mappings on the installed integration.

Notes and limits

  • The presets use the Leads module’s field names. To upsert Contacts (the Target Module setting), rename Designation to Title and remove Company and Website on the installed integration.
  • Lead_Source is a picklist. Map it on the installed integration to one of your picklist values if you use it.
  • Signal sends the access token as Authorization: Bearer, the scheme Zoho’s OAuth documentation describes; Zoho CRM’s own examples use Zoho-oauthtoken.

Assign the consent category you use for this processing when you install Zoho CRM, so an event is delivered only when the visitor has granted it. Signal never writes a cookie-banner choice into Zoho CRM’s contact permissions (Email_Opt_Out): consent to cookies is not consent to be emailed, called or messaged. Record communication opt-in and opt-out in Zoho CRM.

Verify it’s working

  1. Open the integration in the Signal Management UI and watch Live Events while you trigger a mapped event.
  2. Confirm a 2xx response from Zoho CRM.
  3. Find the record in Zoho CRM and check it arrived.

Troubleshooting

SymptomResolution
INVALID_TOKEN / 401The refresh token is from a different data centre than the API domain, or was revoked.
MANDATORY_NOT_FOUNDA mandatory field in your layout is not mapped; add it on the installed integration.
DUPLICATE_DATAMore than one record matches the email.