Welcome back to another week of Kaizen!
In our
sample project, we used the
file-based persistence method, a simple setup where the access and refresh tokens are stored in a local file. While this method is great for getting started, it might not always fit your business requirements.
That is why our SDKs offer multiple ways to persist your tokens.
This week, we will explore why token persistence matters for your app’s secure operation, and how to implement custom token persistence methods, including a practical example using SQLite.
Why does token persistence matter?
When a user logs in via OAuth, Zoho returns two tokens:
- An access token (valid for one hour), used to access Zoho CRM data.
- A refresh token, used to get a new access token when the current one expires.
If your app does not store these tokens properly, your users will be forced to log in again every time they make an API call. Or every time their access token gets expired. That is not inconvenient; it is a poor user experience.
When you use Zoho CRM SDKs, this is all handled for you behind the scenes. When you first authenticate with Zoho, the SDK stores your access and refresh tokens. Later, when a token expires, the SDK automatically uses the refresh token to get a new one. All you have to do is configure and initialize the SDK, and you are ready to start making API calls using the different methods offered by our SDKs!
From the user’s perspective, it means:
- They do not have to log in every time.
- Their sessions are automatically renewed without interruption.
- Token revocation can be done centrally.
From a developer’s perspective:
- You can control how and where tokens are stored.
- You have control to enforce policies like session timeouts or token cleanup.
Supported token persistence options
The Zoho CRM SDKs support three token persistence mechanisms:
File Persistence:
As we have already seen in last week's Kaizen, in this method, the tokens are stored in a local file of your choice. This can be configured while configuring and initializing the SDK. While this is simple and great for internal and local use, it might not always meet the needs of a growing business. For instance, if the file gets deleted or corrupted, you lose the tokens. It also poses a security risk, as storing tokens in files may expose them to unauthorised access if the file is not properly secured.
Database Persistence:
This stores tokens in a MySQL database, making it better suited for production environments. It is more robust and can handle larger-scale user management.
Using this persistence method, you can only provide the following connection parameters - host, DB name, table name, username, password, and port number.
Custom Persistence:
But what if neither of these options fits your needs? Maybe you are working in an environment without traditional storage like
AWS's Secret Manager, or you prefer any other Database, or running a microservice in a container where local storage is more practical. That is where Custom Token Persistence comes in.
Custom Token Persistence
Custom persistence means you can implement your own logic for storing and retrieving OAuth tokens, instead of relying on the SDK’s default mechanism. To do this, you should create a class that implements the TokenStore interface and override a standard set of methods, each handling a specific part of the token lifecycle.
Here’s what your custom class must implement:
Method
| Purpose | Return Type |
find_token(self, token) | Given a token, return a full Token (OAuthToken) object from storage. Used before making any CRM API call. | Token(OAuthToken) object |
save_token(self, token) | Called right after Zoho returns a new access/refresh token. Your implementation must persist it. | None |
delete_token(self, id) | Delete a specific token using its unique ID. | None |
get_tokens(self) | Return all stored tokens. | A list of Token(OAuthToken) objects |
delete_tokens() | Delete all stored tokens. Useful during cleanup or logout. | None |
find_token_by_id(id) | Retrieve a token by its unique identifier. | Token(OAuthToken) object |
The token object is an instance of
OAuthToken. The SDK will invoke these methods automatically as part of its flow. You just have to focus on where and how to store the tokens. With this, you can persist tokens to any storage as long as your class handles these methods correctly.
Understanding the token object
Before we dive deeper into custom token persistence, let's clarify what this token (OAuthToken) object is and how you should work with it.
The token object is an instance of
OAuthToken. This class bundles all the credentials and details the SDK needs to authenticate your API requests. Here’s what it holds:
- access_token
- refresh_token
- client_id
- client_secret
- redirect_url
- expires_in
- user_signature
- id
- api_domain
Implementing Custom Token Persistence with SQLite
Now that we've covered the basics of token persistence and how Zoho SDK supports custom stores, let’s dive into a practical, real-world example using SQLite as the backend for storing tokens.
SQLite is a lightweight, file-based database engine. It is perfect when you want a persistent store without the complexity of a full database server.
The CustomStoreSQLite Class
This class implements all six required methods of the TokenStore interface using SQLite as the backend.
1. Initialization and Table Setup
When you create a CustomStoreSQLite object, it immediately checks if the token table exists in the SQLite database file zohooauth.db. If the DB or the table is missing, its __init__() method creates one with all the necessary columns to store token details like id, user_name, client_id, client_secret, refresh_token, access_token, grant_token, expiry_time, redirect_url and api_domain.
def __init__(self):
""" Initializes the SQLite database and sets up the oauthtoken table if needed. """ self.db_name = 'zohooauth.db' if not self.check_table_exists(): connection = sqlite3.connect(self.db_name) cursor = connection.cursor() cursor.execute("CREATE TABLE oauthtoken (id varchar(10) NOT NULL,user_name varchar(255), client_id " "varchar(255), client_secret varchar(255), refresh_token varchar(255), access_token " "varchar(255), grant_token varchar(255), expiry_time varchar(20), redirect_url varchar(" "255), api_domain varchar(255), primary key (id))") cursor.close() |
This means the first time your app runs, it sets up its own database schema automatically.
2. Saving a Token - save_token(self, token)
Purpose:
This method is called every time Zoho returns a new token, whether after a login or a token refresh. Your implementation is responsible for safely persisting this token, typically by upserting (inserting or updating) a row in your database that uniquely identifies the token’s user and client combination.
Expected behaviour:
The method must store the token in your custom database or storage system.
- If a matching token already exists (based on user, refresh token, or client credentials), it should be updated.
- If no match exists, a new entry must be created.
- Tokens should not be duplicated. Multiple users should be managed separately.
Input Parameters: An instance of Token(OAuthToken) class containing details like access token, refresh token, user signature, client ID/secret, etc.
Return value: None. But must raise exceptions on failure.
Sample Implementation using SQLite:
Here is the logic used in the implementation of save_token() method:
- If the user name is available, use it to update the token.
- If no user name but the access token is available in the table, update by the access token.
- If there is a refresh or grant token with the same client credentials, then update accordingly.
- If none of these match, insert as a new row.
def save_token(self, token):
if not isinstance(token, OAuthToken): return cursor = None connection = None try: connection = sqlite3.connect(self.db_name) oauth_token = token query = "update oauthtoken set " if oauth_token.get_user_signature() is not None: name = oauth_token.get_user_signature().get_name() if name is not None and len(name) > 0: query = query + self.set_token(oauth_token) + " where user_name='" + name + "'" elif oauth_token.get_access_token() is not None and len(oauth_token.get_access_token()) > 0 and \ self.are_all_objects_null([oauth_token.get_client_id(), oauth_token.get_client_secret()]): query = query + self.set_token( oauth_token) + " where access_token='" + oauth_token.get_access_token() + "'" elif ((oauth_token.get_refresh_token() is not None and len(oauth_token.get_refresh_token()) > 0) or (oauth_token.get_grant_token() is not None and len( oauth_token.get_grant_token()) > 0)) and oauth_token.get_client_id() is not None \ and oauth_token.get_client_secret() is not None: if oauth_token.get_grant_token() is not None and len(oauth_token.get_grant_token()) > 0: query = query + self.set_token( oauth_token) + " where grant_token='" + oauth_token.get_grant_token() + "'" elif oauth_token.get_refresh_token() is not None and len(oauth_token.get_refresh_token()) > 0: query = query + self.set_token( oauth_token) + " where refresh_token='" + oauth_token.get_refresh_token() + "'" query = query + " limit 1" try: cursor = connection.cursor() cursor.execute(query) if cursor.rowcount <= 0: if oauth_token.get_id() is not None or oauth_token.get_user_signature() is not None: if oauth_token.get_refresh_token() is None and oauth_token.get_grant_token() is None \ and oauth_token.get_access_token() is None: raise SDKException(Constants.TOKEN_STORE, Constants.GET_TOKEN_DB_ERROR1) if oauth_token.get_id() is None: newId = str(self.generate_id()) oauth_token.set_id(newId) query = "insert into oauthtoken (id,user_name,client_id,client_secret,refresh_token,access_token," \ "grant_token,expiry_time,redirect_url,api_domain) values (?,?,?,?,?,?,?,?,?,?);" val = (token.get_id(), token.get_user_signature().get_name() if token.get_user_signature() is not None else None, token.get_client_id(), token.get_client_secret(), token.get_refresh_token(), token.get_access_token(), token.get_grant_token(), token.get_expires_in(), token.get_redirect_url(), token.get_api_domain()) cursor.execute(query, val) except Error as e: raise e finally: connection.commit() cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Exception as ex: raise SDKException(Constants.TOKEN_STORE, Constants.SAVE_TOKEN_DB_ERROR, cause=ex) |
3: Fetching a Token - find_token(self, token)
Purpose:
This is the method the SDK calls whenever it needs to make an API call on behalf of a user, but has only partial token information.
Depending on the token flow - Grant Token, Refresh Token, Access Token, or ID-based - only a specific token or ID may be provided during the API call. In such cases, find_token(self, token) method locates and return the complete OAuthToken object from storage if a matching one exists. If no matching token exists in the storage, this method will return None, and the SDK will proceed to generate a new token with the provided details and save it using the save_token(self, token) method.
Expected behavior:
- Based on the available details in the input token (user name, access token, refresh or grant token), this method should query storage and return a complete token object.
- If no match is found, it should return None.
Input Parameters: A partially filled Token(OAuthToken) object.
Return value: A fully populated Token object if found, or None.
Sample Implementation using SQLite:
The find_token(self, token) method implementation does the following:
- Dynamically builds a WHERE clause based on available attributes.
- Queries the database for a matching record.
- Fetches the matching record, if any, and populates the Token object with the full set of stored values (access token, refresh token, expiry time, etc.).
- Returns the Token object if a matching record is found, or return None.
Without this method, your app wouldn’t know which token to use during API calls. For example, consider the case when a user reopens your app after hours. You have their refresh token stored. The SDK calls find_token(self, token) to get the full token and proceeds without requiring a fresh login.
def find_token(self, token):
cursor = None connection = None try: connection = sqlite3.connect(self.db_name) if isinstance(token, OAuthToken): oauth_token = token query = "select * from oauthtoken" if oauth_token.get_user_signature() is not None: name = oauth_token.get_user_signature().get_name() if name is not None and len(name) > 0: query = query + " where user_name='" + name + "'" elif oauth_token.get_access_token() is not None and self.are_all_objects_null( [oauth_token.get_client_id(), oauth_token.get_client_secret()]): query = query + " where access_token='" + oauth_token.get_access_token() + "'" elif oauth_token.get_refresh_token() is not None or oauth_token.get_grant_token() is not None and \ oauth_token.get_client_id() is not None and oauth_token.get_client_secret() is not None: if oauth_token.get_grant_token() is not None and len(oauth_token.get_grant_token()) > 0: query = query + " where grant_token='" + oauth_token.get_grant_token() + "'" elif oauth_token.get_refresh_token() is not None and len(oauth_token.get_refresh_token()) > 0: query = query + " where refresh_token='" + oauth_token.get_refresh_token() + "'" query = query + " limit 1" cursor = connection.cursor() cursor.execute(query) result = cursor.fetchone() if result is None: return None self.set_merge_data(oauth_token, result) except Exception as ex: raise SDKException(Constants.TOKEN_STORE, Constants.GET_TOKEN_DB_ERROR1, cause=ex) finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None return token |
4: Deleting a Token - delete_token(self, id)
Purpose:
Delete a specific token record from storage based on a unique token ID. It is commonly used when a user logs out or an admin revokes access for a user.
Expected behaviour:
- Locate the token record by its unique ID.
- Delete the corresponding record from storage.
Input Parameters: The token ID to be deleted.
Return values: None
Sample Implementation using SQLite:
def delete_token(self, id):
cursor = None try: connection = sqlite3.connect(self.db_name) try: cursor = connection.cursor() query = "delete from oauthtoken where id= " + id + ";" cursor.execute(query) connection.commit() except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.DELETE_TOKEN_DB_ERROR, cause=ex) |
5: Deleting All Tokens - delete_tokens(self)
Purpose: Delete all tokens from storage, typically used for global logout or cleanup scenarios.
Expected behaviour: Remove all token records from storage in a single operation.
Input Parameters: None
Return Values: None
Sample Implementation using SQLite:
def delete_tokens(self):
cursor = None try: connection = sqlite3.connect(self.db_name) try: cursor = connection.cursor() query = "delete from oauthtoken;" cursor.execute(query) self.connection.commit() except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.DELETE_TOKENS_DB_ERROR, cause=ex) |
6: Fetch all tokens - get_tokens(self)
Purpose: Retrieve all currently stored tokens.
Expected behaviour:
- Query storage for all token records.
- Construct and return a list of token objects
Input Parameters: None
Return Value: A list of Token objects representing all stored tokens.
Sample Implementation using SQLite:
def get_tokens(self):
cursor = None try: connection = sqlite3.connect(self.db_name) tokens = [] try: cursor = connection.cursor() query = "select * from oauthtoken;" cursor.execute(query) results = cursor.fetchall() for result in results: oauth_token = object.__new__(OAuthToken) self.set_oauth_token(oauth_token) self.set_merge_data(oauth_token, result) tokens.append(oauth_token) return tokens except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.GET_TOKENS_DB_ERROR, cause=ex) |
7. Finding a Token by ID - find_token_by_id(self, id)
Purpose: Retrieve a specific token by its unique id.
Expected behaviour:
- Search storage for a token with the given ID.
- If found, return the complete token object; if not, return None.
Input Parameters: The unique identifier of the token (id)
Return Values: Returns a fully populated Token(OAuthToken) object if found; otherwise, returns None.
Sample Implementation using SQLite:
This method should follows a similar pattern to find_token, but use the unique id as the search key.
def find_token_by_id(self, id):
cursor = None try: connection = sqlite3.connect(self.db_name) try: query = "select * from oauthtoken where id='" + id + "'" oauth_token = object.__new__(OAuthToken) self.set_oauth_token(oauth_token) cursor = connection.cursor() cursor.execute(query) results = cursor.fetchall() if results is None or len(results) <= 0: raise SDKException(Constants.TOKEN_STORE, Constants.GET_TOKEN_BY_ID_DB_ERROR) for result in results: self.set_merge_data(oauth_token, result) return oauth_token except Error as ex: raise ex finally: cursor.close() if cursor is not None else None connection.close() if connection is not None else None except Error as ex: raise SDKException(code=Constants.TOKEN_STORE, message=Constants.GET_TOKEN_BY_ID_DB_ERROR, cause=ex)
|
Please find the complete custom_store_sqlite.py file
here.
How to use this in your project
To start using this custom token persistence class in your own Python project, follow these steps:
- Download the custom_store_sqlite.py and place this inside your project directory.
- Import the class in the script where you initialize the SDK. In our sample project, this is the record.py file.
from store.custom_store_sqlite import CustomStoreSQLite - In the SDK configuration, use the CustomStoreSQLite method instead of the FireStore method:
def init(self, client_id, code, location, redirect_url):
environment = DataCenter.get(location) client_secret = "17565609051856218813123b9a98de52c301722b7d" logger = Logger.get_instance(level=Logger.Levels.INFO, file_path="./logs.txt") store = CustomStoreSQLite() token = OAuthToken(client_id=client_id, client_secret=client_secret, grant_token=code, redirect_url=redirect_url) Initializer.initialize(environment=environment, token=token, logger=logger, store=store) |
That’s it! With this, all token operations (save, fetch, delete) will be routed through your custom store backed by SQLite.
The above video demonstrates this is in action. You can see what the database looks like when populated.
More Custom Persistence Implementations
The advantage of using Zoho CRM SDKs is that it doesn't box you in. You are free to implement token persistence in a way that fits your business logic, team expertise, or project requirements. Whether you prefer SQLite, NoSQL, or something entirely different, the SDK gives you full control through the TokenStore interface.
In the SQLite example above, we walked through how to implement a custom store using a persistent file-based database. You need to implement all the methods as explained in the previous section, no matter where you decide to persist your tokens.
To make things easier, we have included two additional reference implementations:
- An in-memory store, where tokens are stored in a dictionary
- A list-based store, which keeps token records as simple lists
Each one fully implements the required methods of the TokenStore interface.
SQLite In-Memory DB
This implementation uses SQLite's in-memory mode (using ":memory:") to store tokens in RAM. Here, we have implemented all the required methods from the TokenStore interface: find_token(), save_token(), delete_token(), get_tokens(), delete_tokens() and find_token_by_id().
Please find the
custom_store_in_memory.py file
here.
List-Based Persistence Using Simple Lists
The second reference implementation is a list-based token store that keeps token records in an in-memory Python list of lists. Each inner list represents a token’s attributes, such as ID, user signature, client ID, access token, refresh token, and so on.
This custom store fully implements all required methods from the TokenStore interface.
Please find the
custom_store_list.py file
here.
We hope this was useful and gives you enough info to build your own token persistence methods tailored to your needs. We used Python SDK here, but you can apply the same logic with any of our other SDKs. It is all the same logic, just different programming languages. Just remember to implement the required methods exactly as expected by the SDK, as explained here.
Give it a try, and please let us know how it goes or if you hit any bumps! Comment below, or send an email to
support@zohocrm.com. We will be waiting to hear from you!
Happy coding!
We are excited to be approaching the 200th post in our Kaizen series! As we get closer to this milestone, we would love to hear from you. Have questions, suggestions, or topics you would like us to cover in our future Kaizen posts? Your feedback helps us make the series even better.
Please take a moment to share your thoughts with us using this form - we'd really appreciate it!
Recent Topics
How do I insert a cross-reference link to a different section within one Knowledge Base article using Zoho Desk?
I would like to insert a link within a Knowledge Base article to a different section of that same article. The section I want to link to is formatted with the Heading 3 style and is displayed within my TOC. However, I do not see any way to add a link
Problem Adding Facebook Account
Hi, I'm new here, I'm having trouble setting up my Facebook account as a social channel. I think the issue is down to how my Facebook is set up, which is pretty confusing. I have a personal Facebook account (let’s called it A) which is my main Facebook
Zoho Desk Teams App is not loading
Hi Zoho Desk support. Need an assistance on the Zoho Desk Teams app. Once I click View Ticket, it isn't showing anything. Kindly refer to attached: ZohoDesk Teams App_View Ticket Error.jpg For our Dashboard, we are still experiencing the same issue. Kindly
About Meetings (Events module)
I was working on an automation to cancel appointments in zoho flow , and in our case, we're using the Meetings module (which is called Events in API terms). But while working with it, I'm wondering what information I can display in the image where the
Zoho People - Retrieve the Leave Details - get("LeaveCount")
Hi, Zoho People I need to collect all of an employee's leave requests for the calendar year and check how many half-days they have taken. If I run the script on the query he just modified, I can retrieve the information related to that query and use the
Mapping a new Ticket in Zoho Desk to an Account or Deal in Zoho CRM manually
Is there any way for me to map an existing ticket in Zoho desk to an account or Deal within Zoho CRM? Sometimes people use different email to put in a ticket than the one that we have in the CRM, but it's still the same person. We would like to be able
Which WhatsApp API works seamlessly with Zoho CRM?
I’m exploring WhatsApp API solutions that integrate seamlessly with Zoho CRM for customer communication, lead nurturing, and automation. I would love to hear insights from those who have successfully implemented WhatsApp within Zoho CRM. My Requirements:
Allow people to sign a zoho form by using esign or scanned signature
Allow people to sign a zoho form by using esign or scanned signature
Button to Reapply Filters on Sheet(s)
I wrote a macro that I attached to a button to reapply the filters on all my sheets and it says it works, but it doesn't actually do anything. What is wrong with it? Is there another way? Or even make it work for one sheet? Sub UniversalFilterRefresh()
Integrate Multiple ZohoBooks organization with zoho projects
We have successfully connected our Zoho Books with Zoho Projects for synronizing timesheet data. Our Business specialty is, that the staff of the Main company (A) is working on several projects, but the Clients are sometimes contracted and paying to a
Zoho OAuth Connector Deprecation and Its Impact on Zoho Desk
Hello everyone, Zoho believes in continuously refining its integrations to uphold the highest standards of security, reliability, and compliance. As part of this ongoing improvement, the Zoho OAuth default connector will be deprecated for all Zoho services
Flexible Partial-Use Coupons (Stored Value Credits)
Subject: Feature Request: Ability for users to apply partial coupon balances per transaction Problem Statement Currently, our coupons are "one-and-done." If a user has a $50 coupon but only spends $30, they either lose the remaining $20 or are forced
Unable to Assign Multiple Categories to a Single Product in Zoho Commerce
Hello Zoho Commerce Support Team, I am facing an issue while assigning categories to products in Zoho Commerce. I want to assign multiple categories to a single product, but in the Item edit page, the Category field allows selecting only one category
How do I add todays date to merge field
I don't see any selection of todays date when creating a letter. Surely the date option of printing is standard? John
Add RTL and Hebrew Support for Candidate Portal (and Other Zoho Recruit Portals)
Dear Zoho Recruit Team, I hope you're doing well. We would like to request the ability to set the Candidate Portal to be Right-to-Left (RTL) and in Hebrew, similar to the existing functionality for the Career Site. Currently, when we set the Career Site
Tracking Emails sent through Outlook
All of our sales team have their Outlook 365 accounts setup with IMAP integration. We're trying to track their email activity that occurs outside the CRM. I can see the email exchanges between the sales people and the clients in the contact module. But
Whats that
Price?
The same Contact associated to multiple Companies - Deals
Hi, I would like to know if there is an option to associate the same contact with multiple companies (two or more) deals, using the same contact details for all. This is because we have contacts who are linked to different companies or branches of the
Convert invoice from zoho to xml with all details
How to convert an Invoice to XML format with all details
Portals-Adjust Column Sizes
I am trying to adjust the column widths in Portals tabs. Columns that don't need to be wide are wide and longer ones are very short. I thought adding more to the digits box in Edit would widen them, but it doesn't. Anyone know how to adjust these?
Add link/button to open approved record from approval list and detail views?
Hi, How do I allow users to click on an approval record and open that submission? For example, userA submits a quotation then userB approves/rejects. They both can see the quotation on "completed task" list & detail views, but there's no way for them
record submitted from creator and invoice is creating in books , but the workflow of books is not tiggering on create of record in books
record submitted from creator and invoice is creating in books , but the workflow of books is not tiggering on create of record in books headermap = Map(); headermap.put("X-ZOHO-Execute-CustomFunction","true"); response_inv = invokeurl [ url :"https://www.zohoapis.com/books/v3/invoices/fromsalesorder?salesorder_id="
Prevent editing of a record after getting approved/rejectedr
Hi, I'd like to block any user from editing a record after it was approved or rejected, how can I do that?
Formula Field/Campo de Fórmula
Hello everyone, I have a purchase requisition form in which each department submits a request, and the request is automatically routed to the person responsible for that department. In this form, I have several fields with the following link names: Quantidade1,
Free Webinar: Zoho Sign for Zoho Projects: Automate tasks and approvals with e-signatures
Hi there! Handling multiple projects at once? Zoho Projects is your solution for automated and streamlined project management, and with the Zoho Sign extension, you can sign, send, and manage digital paperwork directly from your project workspace. Join
Formatting Problem | Export to Zoho Sheet View
When I export data to Zoho Sheet View, ID columns are automatically formatted as scientific notation. Reformatting them to text changes the actual ID values. For example, 6557000335603071 becomes 6557000335603070. I have attached screenshots showing this
Introducing Workqueue: your all-in-one view to manage daily work
Hello all, We’re excited to introduce a major productivity boost to your CRM experience: Workqueue, a dynamic, all-in-one workspace that brings every important sales activity, approval, and follow-up right to your fingertips. What is Workqueue? Sales
Feature Request - The Ability to Link A Customer with a Vendor
Hi Finance Suite Team, Many businesses buy and sell products from the same companies or individuals. For example, a car sales business may buy a car from a member of the public, and that member of the public may also buy a new car from us. This makes
Long table name (sync from Zoho Creator)
Dears, How can I remove the suffix in parentheses? These tables are synced from Zoho Creator and are treated as system tables, so their names cannot be changed. This issue makes the aggregation formulas look awful.
[Free Webinar] Learning Table Series - Streamlining incident management process with Zoho Creator
Hello everyone, We’re excited to invite you to another edition of the Learning Table Series webinar. As you may already know, we've moved to a purpose-based approach in the Learning Table Series this year. Each session now focuses on how a Zoho Creator
Unattended - Silent
How can I hide the tray icon / pop up window during unattended remote access for silent unattended remote access?
Importing into Multiselect Picklist
Hi, We just completed a trade show and one of the bits of information we collect is tool style. The application supplied by the show set this up as individual questions. For example, if the customer used Thick Turret and Trumpf style but not Thin Turret,
Text snippet
There is a nice feature in Zoho Desk called Text Snippet. It allows you to insert a bit of text anywhere in a reply that you are typing. That would be nice to have that option in Zoho CRM as well when we compose an email. Moderation Update: We agree that
Marketing Tip #18: Make your online store mobile-friendly to improve traffic
Most online shoppers browse on their phones first. If your store is hard to read, slow to load, or tricky to navigate on mobile, they’ll bounce fast. A mobile-friendly store doesn’t just look nice; it improves engagement, reduces drop-offs, and helps
[Need help] Form closed. Please contact your form administrator for further assistance.
https://forms.zohopublic.com/cceinfoifly1/form/CCE2025CCEFocusGroupRegistrationForm2025Fall/formperma/s_8XcLETTbFxZ_TAS4r_W6W5UBl8o5oxEnIX35IBKg4 I checked we didn't exceed the usage limit and form availability is enabled, Please help us enable this form
Zoho People. Updating TabularData
I am trying to update tabular data in the record. I always have the same response. I have checked many times. Section ID is correct. May be something wrong with request structure itself. Can someone help me. Body content type: form urlencoded query params
Automatically CC an address using Zoho CRM Email Templates
Hi all - have searched but can't see a definitive answer. We have built multiple email templates in CRM. Every time we send this we want it to CC a particular address (the same address for every email sent) so that it populates the reply back into our
Unable to Send Different Email Templates for Different Documents in Zoho Sign
Hello Zoho Community, I am facing a limitation with Zoho Sign regarding email notifications sent to customers when a document is sent for signing. Currently, whenever I send any template/document for signing, the email notification that goes to the customer
Reminder needs 0 minute choice
I most use 0 minute reminders. Every other calender service has this choice. If I create an event in my Android calendar with 0 minute reminder it will change to 5 minute. Please ad 0 as a reminder choice, this should be a 5 minute fix. Thanks.
Customer ticket creation via Microsoft Teams
Hi all, I'm looking to see if someone could point me in the right direction. I'd love to make it so my customers/ end users can make tickets, see responses and respond within microsoft teams. As Admin and an Agent i've installed the zoho assist app within
Next Page