7.1 KiB
Zoho CRM Setup Guide for Queue North Admins
Status: Current
Owner: _null
Last reviewed: 2026-08-18
Governs: server/index.js, .env.example — the Zoho CRM integration: app setup,
OAuth credentials, environment variables, and how to test that leads
arrive
Review trigger: Any change to ZOHO_* environment variables, the forwarding mode,
the field mapping, or the datacenter; any Zoho API version change
Read the mode first. This guide documents the OAuth/REST path. Production currently runs
ZOHO_FORWARDING_MODE=webtolead, which needs none of the OAuth setup below — the REST path is kept as a configured standby..env.exampleshows both. The reasoning behind the current shape is indocs/history/BATCH_LEDGER.md.
This guide walks you through the current Zoho CRM integration. Contact leads use the legacy Zoho WebToLead form tokens, while the OAuth/API integration remains available as a standby option for future lead upserts or support cases.
Prerequisites
Before you begin, ensure you have:
- A Zoho CRM account (admin access required)
- The WebToLead hidden field values from the old Zoho form
Step 1: Gather WebToLead Values
The current integration needs the old Zoho form's hidden fields:
xnQsjsdp
xmIwtLD
actionType
returnURL
zc_gad
These values are stored locally in zoho.md, which is ignored by git.
Optional Standby: Create a Zoho Self-Client App
Only use these OAuth steps if switching ZOHO_FORWARDING_MODE=api.
- Go to https://api-console.zoho.com
- Click "Create Self Client"
- Fill in:
- Client Name: Queue-North-Zoho-Integration
- Description: Auto-capture leads and cases from Queue North website
- Redirect URI:
https://www.zoho.com(required for Self-Client, not used)
- Click Create
- Copy and save:
- Client ID
- Client Secret
⚠️ Store these securely — they're like a username and password.
Optional Standby: Generate an Authorization Code
- In the Self Client tab, click "Generate Code"
- Set the Scope to:
ZohoCRM.modules.leads.CREATE,ZohoCRM.modules.leads.READ,ZohoCRM.modules.cases.CREATE,ZohoCRM.modules.cases.READ - Set Expiry to 10 minutes (use it quickly)
- Click Generate
- Copy the authorization code — it expires in 10 minutes
Optional Standby: Exchange Auth Code for Tokens
Run this curl command (replace placeholders):
curl -X POST https://accounts.zoho.com/oauth/v2/token \
-d "code=<YOUR_AUTH_CODE>" \
-d "client_id=<YOUR_CLIENT_ID>" \
-d "client_secret=<YOUR_CLIENT_SECRET>" \
-d "grant_type=authorization_code" \
-d "redirect_uri=https://www.zoho.com"
Response will include:
{
"access_token": "1000.xxxxx.xxxxx",
"refresh_token": "1000.yyyyy.yyyyy",
"expires_in": 3600,
"token_type": "bearer"
}
Save the refresh_token — this never expires and must be kept secret.
Step 2: Configure Environment Variables
The current production-friendly setup uses the legacy Zoho WebToLead form tokens for contact leads while keeping the OAuth API integration available as a standby option.
Add these to your .env file for WebToLead lead forwarding:
ZOHO_FORWARDING_MODE=webtolead
ZOHO_WEBTOLEAD_ENABLED=true
ZOHO_WEBTOLEAD_URL=https://crm.zoho.com/crm/WebToLeadForm
ZOHO_WEBTOLEAD_XNQSJSDP=<from Zoho WebToLead hidden field>
ZOHO_WEBTOLEAD_XMIWTLD=<from Zoho WebToLead hidden field>
ZOHO_WEBTOLEAD_ACTION_TYPE=TGVhZHM=
ZOHO_WEBTOLEAD_RETURN_URL=null
ZOHO_WEBTOLEAD_ZC_GAD=
Use these only if switching back to the Zoho CRM REST API/OAuth integration:
ZOHO_FORWARDING_MODE=api
ZOHO_ENABLED=false
ZOHO_API_DOMAIN=https://www.zohoapis.com
ZOHO_ACCOUNTS_DOMAIN=https://accounts.zoho.com
ZOHO_CLIENT_ID=<from Step 1>
ZOHO_CLIENT_SECRET=<from Step 1>
ZOHO_REFRESH_TOKEN=<from Step 3>
# Cases forwarding is also OFF by default
ZOHO_CASES_ENABLED=false
Note:
ZOHO_CASES_ENABLEDonly applies to the OAuth/API path. The WebToLead values found in the old site are for lead capture only.
Datacenter Variants
If your Zoho datacenter is outside the US, adjust the domains:
| Region | API Domain | Accounts Domain |
|---|---|---|
| US | www.zohoapis.com |
accounts.zoho.com |
| EU | www.zohoapis.eu |
accounts.zoho.eu |
| IN | www.zohoapis.in |
accounts.zoho.in |
| AU | www.zohoapis.com.au |
accounts.zoho.com.au |
Step 3: Test the Integration
Test Lead Capture
- Submit a lead on the contact form (name, email, phone, message)
- Wait ~5–10 seconds
- Log in to Zoho CRM → Leads tab
- Verify the new lead appears with correct data
Test Case Capture
- Submit a support request (e.g., booking inquiry, technical question)
- Wait ~5–10 seconds
- Log in to Zoho CRM → Cases tab
- Verify the new case appears with correct data
Troubleshooting
Token Errors
- "invalid_grant": Your authorization code expired. Generate a new one in Step 2 and repeat Step 3.
- "invalid_client": Double-check Client ID and Secret — no extra spaces.
- "invalid_scope": Re-run Step 2 with the exact scopes listed above.
Field Mismatches
- If leads/cases don't appear, check if Zoho requires custom fields like
Service_Interest - Edit the field mapping in
server/index.js—forwardToZoho()builds theFirst_Name/Last_Name/Lead_Sourcepayload, andforwardToZohoWebToLead()builds the form fields — to match your Zoho CRM field API names
Cases Not Appearing
- Ensure
ZOHO_CASES_ENABLED=trueis set - Verify the Cases tab is enabled in your Zoho CRM plan
- Check that your Zoho CRM user has Cases CREATE permissions
Lead Upsert Behavior
- Leads are upserted by email: duplicate email = update existing lead
- Cases are always inserted (new ticket each time)
- If you see duplicate leads, check for slight email variations (e.g.,
test@vstest+1@)
Architecture Notes
Flow Overview
Website Contact Form → SQLite (always saved)
↓
Zoho CRM (best-effort)
↓
Fire-and-forget (no failure blocking)
OAuth2 Refresh Token Flow
- Use
refresh_tokento get a newaccess_tokenwhen expired access_tokenexpires in 1 hourrefresh_tokennever expires — store it securely
Upsert Logic
- Leads: WebToLead creates leads through the legacy Zoho form endpoint. API mode uses email-based upsert.
- Cases: Always insert (new case per submission)
Fire-and-Forget Design
- Zoho failures do not block form submissions
- All data is saved to SQLite first
- Zoho attempts happen in the background
- No retry logic needed — users won't wait for Zoho
What Happens Next?
After configuration:
- Deploy the environment variables to production
- Set
ZOHO_WEBTOLEAD_ENABLED=truein production.env - Restart the application
- Submit a test lead and support case to verify data flows to Zoho CRM
- Check Zoho CRM Leads and Cases tabs to confirm both appear
Need help? Contact your site administrator.