Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pnpm 10.12.4
pnpm 10.29.3
nodejs 22.17.0
tilt 0.35.0
2 changes: 1 addition & 1 deletion integrations/chat/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# docker run -it -e SECRET_SIGNAL_URL=https://chat.botpress.dev botpress-chat

ARG NODE_VERSION=22.17.0
ARG PNPM_VERSION=10.12.4
ARG PNPM_VERSION=10.29.3

FROM node:${NODE_VERSION}-bullseye-slim AS base

Expand Down
121 changes: 30 additions & 91 deletions integrations/zoho/hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,45 @@ This Botpress integration allows seamless interaction with **Zoho CRM**. It enab
- **Emails:** Send emails and associate them with records.
- **Organization & User Management:** Retrieve organization details and user information.

## Register Your Application
## Connect Zoho CRM

Before making any API calls using the Zoho Botpress Integration, you must register your application with **Zoho CRM**.
Use the **Connect with OAuth** setup flow in Botpress. The integration will ask for your Zoho data center, redirect you to Zoho for consent, request the required CRM scopes, and securely store the returned OAuth tokens.

### **[Loom video walk through setting up the OAuth configuration.](https://www.loom.com/share/41c2811c047a48cbb08a2d1b0dc98f69?sid=8cb4d496-2cca-415d-be1d-536a87c73a3a)**
The Zoho OAuth app must allow the Botpress callback URL shown by your integration setup. It should use the stable callback path `/oauth/wizard/oauth-callback`; the OAuth `state` value is sent separately and should not be included in the registered redirect URI.

### Steps to Register
The scopes are requested during the authorization flow. They are not configured directly on the Zoho application:

1. **Go to the [Zoho Developer Console](https://accounts.zoho.com/developerconsole).**
2. Click **Add Client**.
3. Choose the client type: **Self Client**.
4. Click the **Generate Code** tab and enter the following scopes:
```text
ZohoCRM.modules.ALL,ZohoCRM.org.ALL,ZohoCRM.users.ALL,ZohoCRM.settings.ALL,ZohoCRM.send_mail.all.CREATE,ZohoCRM.files.CREATE,ZohoCRM.files.READ
```

```
ZohoCRM.modules.ALL,ZohoCRM.org.ALL,ZohoCRM.users.ALL,ZohoCRM.settings.ALL,ZohoCRM.send_mail.all.CREATE,ZohoCRM.files.CREATE,ZohoCRM.files.READ
```
### Zoho Accounts Domains

5. Set the **time duration** to **10 minutes**.
6. Provide a scope description (This is not vital to the registration).
7. Click **Create**, select your CRM, and click **Create** again.
8. Download your **credentials file**.
| Region | Accounts URL |
| ----------- | ------------------------------- |
| US | `https://accounts.zoho.com` |
| AU | `https://accounts.zoho.com.au` |
| EU | `https://accounts.zoho.eu` |
| IN | `https://accounts.zoho.in` |
| CN | `https://accounts.zoho.com.cn` |
| JP | `https://accounts.zoho.jp` |
| CA (Canada) | `https://accounts.zohocloud.ca` |

### Generate Refresh Token
## Manual Configuration

Now, execute the following **cURL** command to obtain a refresh token. Ensure you use the **correct region URL** for OAuth authentication.
Manual configuration is available as an advanced fallback. Use it only if you need to provide your own Zoho OAuth client credentials and refresh token.

#### **Zoho Accounts Domains:**
### Register Your Application

| Region | Accounts URL |
| ----------------- | ------------------------------- |
| US | `https://accounts.zoho.com` |
| AU | `https://accounts.zoho.com.au` |
| EU | `https://accounts.zoho.eu` |
| IN | `https://accounts.zoho.in` |
| CN | `https://accounts.zoho.com.cn` |
| JP | `https://accounts.zoho.jp` |
| SA (Saudi Arabia) | `https://accounts.zoho.sa` |
| CA (Canada) | `https://accounts.zohocloud.ca` |
1. Go to the [Zoho Developer Console](https://accounts.zoho.com/developerconsole).
2. Click **Add Client**.
3. Choose a server-based client type.
4. Add the Botpress OAuth callback URL as an authorized redirect URI.
5. Save the client ID and client secret.

### Execute cURL Request
### Generate Refresh Token

Create an authorization URL with the scopes above, `access_type=offline`, and `prompt=consent`. After granting consent, exchange the returned authorization code for OAuth tokens.

Replace the placeholders (`CLIENT_ID`, `CLIENT_SECRET`, and `AUTHORIZATION_CODE`) with your actual values before executing the request.

Expand All @@ -70,9 +69,7 @@ curl --request POST \
--data 'code=AUTHORIZATION_CODE'
```

### Expected Response

If the request is successful, you should receive a response similar to the following:
If successful, Zoho returns a response similar to:

```json
{
Expand All @@ -84,71 +81,13 @@ If the request is successful, you should receive a response similar to the follo
}
```

# Define the Zoho OAuth token endpoint based on your region

$uri = "https://YOUR_REGION_ACCOUNT_URL/oauth/v2/token"

# Define the request body with required parameters

$body = @{
grant_type = "authorization_code"
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
redirect_uri = "YOUR_REDIRECT_URI"
code = "AUTHORIZATION_CODE"
}

### Generate Refresh Token using PowerShell (Windows Users)

⚠️ Use this PowerShell command if you're on Windows. Do NOT use cURL—this is for PowerShell only! ⚠️

#### Define the Zoho OAuth token endpoint based on your region

$uri = "https://YOUR_REGION_ACCOUNT_URL/oauth/v2/token"

# Define the request body with required parameters

$body = @{
grant_type = "authorization_code"
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
redirect_uri = "YOUR_REDIRECT_URI"
code = "AUTHORIZATION_CODE"
}

##### Convert body to URL-encoded form data

```
$body = $body | ForEach-Object { "$( $_.Key )=$( $_.Value )" } -join "&"
```

#### Send the POST request using Invoke-RestMethod

```
$response = Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/x-www-form-urlencoded" -Body $body
```

#### Output the response

```
$response
```

## Configure Zoho Botpress Integration

Once you have the necessary credentials, navigate to the **Zoho Botpress Integration** configuration page and enter the following details:
Enter these values in the **Manual configuration** setup:

- **Client ID**
- **Client Secret**
- **Refresh Token**
- **Region**

This completes the registration and integration process for **Zoho Botpress**. You are now ready to start making authorized API calls.

---

For more details, refer to the [Zoho API Documentation](https://www.zoho.com/crm/developer/docs/).

---

## API Functions & Usage
Expand Down
47 changes: 33 additions & 14 deletions integrations/zoho/integration.definition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { IntegrationDefinition, z } from '@botpress/sdk'

import {
makeApiCallInputSchema,
makeApiCallOutputSchema,
Expand Down Expand Up @@ -36,25 +35,43 @@ import {
getFileInputSchema,
getFileOutputSchema,
} from './src/misc/custom-schemas'
import { DATA_CENTERS } from './src/misc/data-centers'
import { zohoCredentialsStateSchema, zohoOAuthWizardStateSchema } from './src/misc/oauth-schemas'

export default new IntegrationDefinition({
name: 'zoho',
version: '3.1.4',
version: '4.0.0',
title: 'Zoho',
readme: 'hub.md',
icon: 'icon.svg',
description:
'Integrate your Botpress chatbot with Zoho CRM to manage customer interactions. Add, update, and retrieve contacts, deals, orders, and appointments directly through your chatbot.',
configuration: {
schema: z.object({
clientId: z.string().title('Client ID').describe('Your Zoho Client ID'),
clientSecret: z.string().title('Client Secret').describe('Your Zoho Client Secret'),
refreshToken: z.string().title('Refresh Token').describe('Your Zoho Refresh Token'),
dataCenter: z
.enum(['us', 'eu', 'in', 'au', 'cn', 'jp', 'ca'])
.title('Data Center Region')
.describe('Zoho Data Center Region'),
}),
identifier: {
linkTemplateScript: 'linkTemplate.vrl',
required: true,
},
schema: z.object({}),
},
configurations: {
manual: {
title: 'Manual configuration',
description: 'Configure manually with Zoho OAuth client credentials and a refresh token',
schema: z.object({
clientId: z.string().title('Client ID').describe('Your Zoho Client ID'),
clientSecret: z.string().title('Client Secret').describe('Your Zoho Client Secret'),
refreshToken: z.string().title('Refresh Token').describe('Your Zoho Refresh Token'),
dataCenter: z.enum(DATA_CENTERS).title('Data Center Region').describe('Zoho Data Center Region'),
}),
},
},
secrets: {
CLIENT_ID: {
description: 'The OAuth Client ID provided by Zoho.',
},
CLIENT_SECRET: {
description: 'The OAuth Client Secret provided by Zoho.',
},
},
user: {
tags: {
Expand All @@ -67,9 +84,11 @@ export default new IntegrationDefinition({
states: {
credentials: {
type: 'integration',
schema: z.object({
accessToken: z.string().title('Access Token').describe('Your Zoho Access Token'),
}),
schema: zohoCredentialsStateSchema,
},
oauthWizard: {
type: 'integration',
schema: zohoOAuthWizardStateSchema,
},
},
actions: {
Expand Down
4 changes: 4 additions & 0 deletions integrations/zoho/linkTemplate.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)

"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}"
4 changes: 3 additions & 1 deletion integrations/zoho/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
"private": true,
"dependencies": {
"@botpress/client": "workspace:*",
"@botpress/common": "workspace:*",
"@botpress/sdk": "workspace:*",
"axios": "^1.4.0",
"form-data": "^4.0.0"
"form-data": "^4.0.0",
"preact": "^10.26.6"
},
"devDependencies": {
"@botpress/cli": "workspace:*",
Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/create-appointment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ export const createAppointment: IntegrationProps['actions']['createAppointment']
}) => {
const validatedInput = createAppointmentInputSchema.parse(input)

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().debug(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/delete-appointment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ export const deleteAppointment: IntegrationProps['actions']['deleteAppointment']
}) => {
const validatedInput = deleteAppointmentInputSchema.parse(input)

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().debug(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/delete-record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import type { IntegrationProps } from '../misc/types'
export const deleteRecord: IntegrationProps['actions']['deleteRecord'] = async ({ ctx, client, logger, input }) => {
const validatedInput = deleteRecordInputSchema.parse(input)

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().debug(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/get-appointment-by-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,7 @@ export const getAppointmentById: IntegrationProps['actions']['getAppointmentById
}) => {
const validatedInput = getAppointmentByIdInputSchema.parse(input)

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().debug(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/get-appointments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ export const getAppointments: IntegrationProps['actions']['getAppointments'] = a
const validatedInput = getAppointmentsInputSchema.parse(input)
const params = validatedInput.params ?? '{}'

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().debug(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/get-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import type { IntegrationProps } from '../misc/types'
export const getFile: IntegrationProps['actions']['getFile'] = async ({ ctx, client, logger, input }) => {
const validatedInput = getFileInputSchema.parse(input)

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().debug(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/get-organization-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@ export const getOrganizationDetails: IntegrationProps['actions']['getOrganizatio
client,
logger,
}) => {
const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

try {
const result = await zohoClient.getOrganizationDetails()
Expand Down
9 changes: 1 addition & 8 deletions integrations/zoho/src/actions/get-record-by-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import type { IntegrationProps } from '../misc/types'
export const getRecordById: IntegrationProps['actions']['getRecordById'] = async ({ ctx, client, logger, input }) => {
const validatedInput = getRecordByIdInputSchema.parse(input)

const zohoClient = getClient(
ctx.configuration.refreshToken,
ctx.configuration.clientId,
ctx.configuration.clientSecret,
ctx.configuration.dataCenter,
ctx,
client
)
const zohoClient = await getClient(ctx, client)

logger.forBot().info(`Validated Input - ${JSON.stringify(validatedInput)}`)

Expand Down
Loading
Loading