What you need first
1
An API key scoped to sending
Settings → API Keys, scoped to
conversations:send. Nothing more — this key will
live in your Zoho configuration, where any Zoho admin can read it. See
Authentication.2
Your channel id
From
GET /v1/connect/channels. You’ll paste
this into the Zoho webhook as a literal value.3
An approved template
From
GET /v1/templates. Note its exact
name, its language, and how many {{n}} placeholders its body has.Configure the Zoho webhook
In Zoho Desk: Setup → Automation → Webhooks → Create Webhook.
Then add the body parameters:
These are flat fields, not the nested
template object shown elsewhere in these docs.
Zoho’s webhook builder can only emit flat key/value pairs, so /v1/conversations/send
accepts templateName, templateLanguage and templateParams at the top level as an
equivalent form. See the send endpoint for both.templateParams also accepts a JSON array string — ["Ahmed","4821"]. Use that form when
a value can contain a comma, such as a ticket subject or an address.Trigger it on close
Setup → Automation → Workflows → Create Rule- Module —
Tickets - Execute on —
Edit, restricted to theStatusfield - Criteria —
Status is Closed(addResolvedtoo if your team uses both) - Action — the webhook you just created
The phone number is the hard part
to must be E.164 without a + — 201001234567. Zoho stores whatever the agent
typed, which in practice means all of these for the same person:
Add a validated WhatsApp Number field (recommended)
Add a validated WhatsApp Number field (recommended)
Add a custom field on the Ticket or Contact layout —
WhatsApp Number — with a
validation rule requiring E.164, and make it mandatory before a ticket can close. Then
use ${Ticket.WhatsApp Number} for to.This is the only option that fixes the problem at the source rather than guessing, and
it eliminates an entire category of “why didn’t the customer get the message” tickets.Normalize in a Deluge custom function
Normalize in a Deluge custom function
Replace the plain webhook with a custom function on the same workflow rule, and clean
the number before sending:Hard-coding
20 assumes every customer is in one country. If that isn’t true, this
approach will mangle the exceptions.Fall back through several fields
Fall back through several fields
Send
${Contact.Mobile} and fall back to ${Contact.Phone} or ${Ticket.Phone} when
it’s empty. This only helps with missing numbers, not badly formatted ones — the
formatting problem remains.Two things to watch in production
Duplicate sends. A ticket can be closed, reopened, and closed again — each close fires the rule. Zoho also retries on a non-2xx response. If double-messaging matters, add a custom checkbox field likeConfirmation Sent, include Confirmation Sent is false in the
rule criteria, and set it as a second workflow action.
Silent failures. Zoho records webhook failures in its own log, which nobody reads. Our
API returns 202 as soon as the message is queued, so a 202 does not mean delivered —
check status on the message if delivery confirmation matters. See
step 4 of the quickstart.
Testing it
Close a real ticket on a contact whose number is your own. If nothing arrives, check in this order:- Zoho’s webhook log — did the rule fire at all? If not, the criteria or the department is wrong.
- The response code —
401means the key or headers are wrong,403means the key lacksconversations:send,400means the payload is malformed and themessagefield says how. - The message’s
status— a202followed byfailedmeans we accepted it and the platform rejected it.error_reasonwill say why; the usual causes are a badly-formatted number or a template whose parameter count doesn’t match.

