Queue-North-Website/docs/architecture/zoho-setup.md

7.1 KiB
Raw Blame History

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.example shows both. The reasoning behind the current shape is in docs/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.

  1. Go to https://api-console.zoho.com
  2. Click "Create Self Client"
  3. 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)
  4. Click Create
  5. Copy and save:
    • Client ID
    • Client Secret

⚠️ Store these securely — they're like a username and password.


Optional Standby: Generate an Authorization Code

  1. In the Self Client tab, click "Generate Code"
  2. Set the Scope to:
    ZohoCRM.modules.leads.CREATE,ZohoCRM.modules.leads.READ,ZohoCRM.modules.cases.CREATE,ZohoCRM.modules.cases.READ
    
  3. Set Expiry to 10 minutes (use it quickly)
  4. Click Generate
  5. 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_ENABLED only 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

  1. Submit a lead on the contact form (name, email, phone, message)
  2. Wait ~510 seconds
  3. Log in to Zoho CRM → Leads tab
  4. Verify the new lead appears with correct data

Test Case Capture

  1. Submit a support request (e.g., booking inquiry, technical question)
  2. Wait ~510 seconds
  3. Log in to Zoho CRM → Cases tab
  4. 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.jsforwardToZoho() builds the First_Name / Last_Name / Lead_Source payload, and forwardToZohoWebToLead() builds the form fields — to match your Zoho CRM field API names

Cases Not Appearing

  • Ensure ZOHO_CASES_ENABLED=true is 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@ vs test+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

  1. Use refresh_token to get a new access_token when expired
  2. access_token expires in 1 hour
  3. refresh_token never 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:

  1. Deploy the environment variables to production
  2. Set ZOHO_WEBTOLEAD_ENABLED=true in production .env
  3. Restart the application
  4. Submit a test lead and support case to verify data flows to Zoho CRM
  5. Check Zoho CRM Leads and Cases tabs to confirm both appear

Need help? Contact your site administrator.