Pull live contact and lead data from Zoho CRM into a WhatsApp chatbot. When a prospect messages you, search by their phone number, fetch their lead status and assigned owner, and reply with personalised context in seconds.
Before following this guide, read the External API Request step foundation guide. It covers every field in the step interface so this guide can focus on Zoho-specific values.
1. The auth header is Zoho-oauthtoken, not 'Bearer'. 2. The API domain depends on your data centre. Indian accounts must use zohoapis.in, not zohoapis.com. Getting either wrong returns authentication errors with no helpful message.
Before generating tokens, identify which Zoho data centre your account is on. This determines every URL you use.
| Data centre | Auth URL | API base URL | How to confirm |
|---|---|---|---|
| India (most Indian accounts) | accounts.zoho.in | www.zohoapis.in | Zoho CRM > Setup > Developer Space > API. Check the listed API domain. |
| US | accounts.zoho.com | www.zohoapis.com | Default for accounts created on zoho.com |
| EU | accounts.zoho.eu | www.zohoapis.eu | Accounts created on zoho.eu |
| Australia | accounts.zoho.com.au | www.zohoapis.com.au | Accounts created on zoho.com.au |
If you are an Indian business that signed up at zoho.com/in or zoho.in, your data centre is almost certainly India (.in). Verify in Setup > Developer Space > API before generating tokens.
A Zoho Self Client lets you generate an OAuth token for your own account without needing a hosted redirect URL. This is the simplest path for internal integrations like a WA.Expert bot.
ZohoCRM.modules.contacts.READ. Set expiry to 10 minutes. Click CREATE. Copy the grant code.POST https://accounts.zoho.in/oauth/v2/token
Content-Type: application/x-www-form-urlencoded
Body (form fields):
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
grant_type = authorization_code
code = YOUR_GRANT_CODE
Response:
{
"access_token": "1000.875cf8ea310ae70c6fb26e25a5a48df0.be3bc88...",
"refresh_token": "1000.ce79a5110c4097744b17aecbb95dcfeb.db3167...",
"api_domain": "https://www.zohoapis.in",
"token_type": "Bearer",
"expires_in": 3600
}
Save BOTH tokens. The access_token lasts 1 hour.
The refresh_token is permanent until revoked.Zoho access tokens expire after 3,600 seconds (1 hour). For a production WhatsApp bot, set up a refresh step that POSTs to accounts.zoho.in/oauth/v2/token with grant_type=refresh_token before each CRM lookup. Store the latest access_token in your automation and update it each time it is refreshed.
Zoho Self Client token generation: zoho.com/accounts/protocol/oauth/self-client
The most practical WhatsApp use case is searching Zoho CRM by phone number, since the customer's WhatsApp number is already available in the conversation. In your chatbot flow, store the customer's number as {{customer_phone}} and add an External API Request step:
| Field | Value | Notes |
|---|---|---|
| Select Method | GET | Searching existing contacts. |
| Request URL | https://www.zohoapis.in/crm/v8/Contacts/search?phone={{{customer_phone}}}&fields=... | Use your data centre domain (.in for India). ?phone= searches the Phone field. ?fields= lists what to return. |
| Select Auth Type | No Auth | Zoho auth goes in the Authorization header below. |
| Authorization | Zoho-oauthtoken 1000.875cf8... | IMPORTANT: the prefix is 'Zoho-oauthtoken', not 'Bearer'. Paste the access_token value from Step 2 after the prefix and a space. |
| Content-Type | application/json | Standard header for Zoho CRM API requests. |
| Select Body Type | None | GET requests carry no body. |
| Choose Response Type | JSON | Zoho returns structured JSON. |
A successful Zoho CRM contact search response looks like this:
{
"data": [
{
"First_Name": "Priya",
"Last_Name": "Sharma",
"Email": "priya@example.com",
"Mobile": "9820000001",
"Lead_Status": "Contacted",
"Account_Name": {
"name": "Sharma Textiles",
"id": "3652397000000411001"
},
"id": "3652397000009851001",
"Owner": {
"name": "Ravi Kumar",
"email": "ravi@business.com",
"id": "3652397000000411002"
}
}
],
"info": {
"count": 1,
"more_records": false
}
}Zoho wraps results in a 'data' array even for a single match. Map your variables using data[0].fieldname notation. If no contact is found, the response is an empty 'data' array. Add a conditions branch in your chatbot to handle this.
Map these response paths to variables in the External API Request step:
| Variable name | Response path | Example value |
|---|---|---|
| first_name | data[0].First_Name | Priya |
| last_name | data[0].Last_Name | Sharma |
| data[0].Email | priya@example.com | |
| lead_status | data[0].Lead_Status | Contacted |
| company | data[0].Account_Name.name | Sharma Textiles |
| assigned_to | data[0].Owner.name | Ravi Kumar |
| contact_id | data[0].id | 3652397000009851001 |
Field names in Zoho CRM use PascalCase with underscores (First_Name, Lead_Status). These are the API names, not the display labels shown in the Zoho UI.
If the contact messaged you first within the last 24 hours, your reply is a free service conversation. Use that window. For proactive outbound follow-up triggered by Zoho data, use an approved Utility or Marketing template.
Customer messages your WhatsApp number.
WA.Expert captures their phone as {{customer_phone}} = "9820000001".
External API Request step:
GET https://www.zohoapis.in/crm/v8/Contacts/search
?phone=9820000001
&fields=First_Name,Lead_Status,Account_Name,Owner
Authorization: Zoho-oauthtoken 1000.875cf8...
Response mapped:
first_name = "Priya"
lead_status = "Contacted"
company = "Sharma Textiles"
assigned_to = "Ravi Kumar"
Bot replies:
"Hi Priya, good to hear from you.
You are registered with us as a contact for Sharma Textiles.
Your account status: Contacted.
Your account manager is Ravi Kumar.
Reply CALLBACK to request a call back from Ravi."Zoho access tokens expire after 1 hour. Use the refresh token to get a new one without repeating the full grant-code flow:
POST https://accounts.zoho.in/oauth/v2/token
Content-Type: application/x-www-form-urlencoded
Body (form fields):
client_id = YOUR_CLIENT_ID
client_secret = YOUR_CLIENT_SECRET
grant_type = refresh_token
refresh_token = YOUR_REFRESH_TOKEN
Response:
{
"access_token": "1000.NEW_TOKEN_HERE...",
"api_domain": "https://www.zohoapis.in",
"token_type": "Bearer",
"expires_in": 3600
}
Store the new access_token. The refresh_token stays the same.| Symptom | Likely cause | Fix |
|---|---|---|
| Authentication failed / invalid_client | Wrong data centre URL or using 'Bearer' instead of 'Zoho-oauthtoken' | Confirm your data centre (Setup > Developer Space > API). Use zohoapis.in for Indian accounts. Change Authorization header to 'Zoho-oauthtoken YOUR_TOKEN'. |
| AUTHENTICATION_FAILURE in response body | Access token expired (1 hour limit) | Use the refresh token to get a new access token. Update the stored token in WA.Expert before the next CRM lookup. |
| No data / empty array | Contact not found by phone number | Zoho matches exactly. Try searching by Mobile field too: ?phone=9820000001 searches the Phone field. Use ?criteria=(Phone:equals:9820000001)or(Mobile:equals:9820000001) to search both. |
| Invalid scope error when generating grant code | Wrong scope name | The scope must match exactly: ZohoCRM.modules.contacts.READ. Check Zoho's API documentation for the exact scope name for other modules. |
| Field not in response | Field not included in ?fields= parameter | Add the field's API name to the ?fields= comma-separated list. Find API names in Zoho CRM Setup > Customisation > Modules > Contacts > Fields. |
| Grant code expired | Grant codes expire in 10 minutes | Generate a new grant code from the Self Client in api-console.zoho.in and exchange it immediately. |
HubSpot CRM API v3 with Private App token: contact lookup by ID or phone search.
Read guide →Shopify Admin API order lookup: single header, no token expiry to manage.
Read guide →Master every field in WA.Expert's HTTP action step.
Read foundation guide →Free trial, no credit card required. And if you ever get stuck, we are the only platform in India that answers you live on WhatsApp.