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
| Field | Required | Description |
|---|---|---|
api_domain | Yes | Your 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_url | Yes | Your 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. |
module | Yes | The 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_id | Yes | OAuth Client ID from your Zoho self-client or connected app (Zoho API Console). |
client_secret | Yes | OAuth Client Secret from your Zoho API Console application. |
refresh_token | Yes | Long-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
- Go to Integrations in the sidebar and open the Integration Library tab.
- Find Zoho CRM under CRM and click Install.
- 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"
}
]
}| Field | Source |
|---|---|
data[0].Email | The email, trimmed and lower-cased; the duplicate-check field for Leads and Contacts. An event without one is not sent. |
First_Name, Last_Name | From traits. Zoho requires Last_Name: an event without one is not sent. |
Phone | The phone trait in E.164. |
Company, Designation, Website | From traits (title for the designation). |
City, State, Zip_Code, Country | From the address trait; the country is upper-cased. |
Description | The 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
| Preset | Signal 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
DesignationtoTitleand removeCompanyandWebsiteon the installed integration. Lead_Sourceis 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 useZoho-oauthtoken.
Consent
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
- Open the integration in the Signal Management UI and watch Live Events while you trigger a mapped event.
- Confirm a 2xx response from Zoho CRM.
- Find the record in Zoho CRM and check it arrived.
Troubleshooting
| Symptom | Resolution |
|---|---|
INVALID_TOKEN / 401 | The refresh token is from a different data centre than the API domain, or was revoked. |
MANDATORY_NOT_FOUND | A mandatory field in your layout is not mapped; add it on the installed integration. |
DUPLICATE_DATA | More than one record matches the email. |