Zoho books logo Help Docs
/

Pestañas Web

Las pestañas web son similares a las pestañas del navegador, pero se puede acceder a ellas dentro de Zoho Books. Puedes abrir cualquier página web o aplicación que proporcione una URL de inserción directamente dentro de Zoho Books, para que tu equipo pueda acceder a herramientas externas sin cambiar de pestaña. También puedes habilitar la autenticación JWT en una pestaña web para que tu aplicación externa pueda verificar que las solicitudes provienen de Zoho Books para la organización y el usuario correctos.

Perspectiva: Un JWT es un formato estándar para transmitir información de forma segura como un objeto JSON firmado. Consta de tres partes: un encabezado, un payload y una firma, separadas por puntos.

Las pestañas web se pueden usar de muchas maneras según las necesidades de tu negocio.

Escenario: Zylker Manufacturing incorpora su sistema ERP como una pestaña web en Zoho Books. Sin autenticación JWT, el ERP no tiene forma de identificar qué empresa abrió la pestaña. Cualquiera que tenga la URL podría abrirla y ver potencialmente los datos de producción de otra empresa. Habilitar la autenticación JWT resuelve esto: Zoho Books firma cada solicitud con un token que identifica la organización y el usuario de Zylker, de modo que el ERP carga solo los datos de Zylker.

Notas:

  • Si las páginas web usan ‘http’, no se abrirán en las pestañas web.
  • Algunas páginas web o aplicaciones no se pueden abrir usando pestañas web porque están configuradas para impedir que se abran en otras aplicaciones. Esto es para evitar ataques de clickjacking.
  • Las pestañas web que crees no estarán vinculadas a ningún otro módulo de Zoho Books ni afectarán sus datos.

Crear una Pestaña Web

Para crear una nueva pestaña web:

  • Ve a Configuración.
  • Selecciona Pestañas Web en Personalización.
  • Haz clic en + Nueva Pestaña Web en la esquina superior derecha.
  • Ingresa un nombre para la pestaña web en el campo Nombre de Pestaña.
  • Ingresa la URL de la aplicación externa en el campo URL.
    • Para incluir valores dinámicos como el nombre de la organización o el ID del cliente en la URL, haz clic en Insertar Marcador y selecciona los valores que necesites.
  • Selecciona Esta URL pertenece a una aplicación o sitio web de Zoho si la URL apunta a un producto o sitio web de Zoho.
  • Para verificar que las solicitudes a tu aplicación externa provienen de Zoho Books para la organización y el usuario correctos, habilita la Autenticación JWT.
    • Ingresa una Clave Secreta de entre 32 y 500 caracteres. Zoho Books usa esta clave para firmar cada token que envía a tu aplicación. Consérvala únicamente en tu servidor y no la incluyas en el código del frontend.
    • Selecciona un período de Validez del Token: 10 minutos, 30 minutos, 1 hora o 3 horas.
  • En Visibilidad, selecciona quién puede ver esta pestaña web:
    • Solo yo: Solo tú puedes ver la pestaña web.
    • Solo Usuarios y Roles Seleccionados: Selecciona usuarios y roles específicos en el menú desplegable que aparece.
    • Todos: Todos los usuarios de tu organización pueden ver la pestaña web.
  • Haz clic en Guardar.
Crear Pestaña Web

Después de guardar, la pestaña web aparece en la barra lateral izquierda bajo Pestañas Web. Haz clic en ella para abrir tu aplicación externa dentro de Zoho Books.

Nota: Cuando la autenticación JWT está habilitada, Zoho Books adjunta un token firmado a cada solicitud que abre la pestaña web. Tu servidor verifica este token para confirmar que la solicitud proviene de Zoho Books para la organización y el usuario correctos. Los tokens expiran después del período de validez que hayas configurado. Cuando un token expira, tu aplicación de pestaña web puede solicitar uno nuevo de forma programática.

Editar una Pestaña Web

Puedes editar una pestaña web para actualizar su nombre, URL, configuración de autenticación JWT o visibilidad. Para editar una pestaña web:

  • Ve a Configuración.
  • Selecciona Pestañas Web en Personalización.
  • Haz clic en la pestaña web que deseas editar, o pasa el cursor sobre ella, haz clic en el ícono de menú desplegable y selecciona Editar.
Editar Pestaña Web
  • Realiza los cambios necesarios y haz clic en Guardar.

Validar un Token JWT

La función Validar Token JWT te permite verificar un token que tu aplicación recibió de Zoho Books. Úsala para confirmar los detalles del token o diagnosticar problemas antes de realizar cambios en tu servidor.

Para validar un token JWT:

  • Ve a Configuración.
  • Selecciona Pestañas Web en Personalización.
  • Haz clic en la pestaña web que deseas editar, o pasa el cursor sobre ella, haz clic en el ícono de menú desplegable y selecciona Editar.
  • Haz clic en Validar Token JWT en la esquina superior derecha de la página Editar Pestaña Web.
  • Pega tu token JWT en el campo Token JWT.
  • Haz clic en Verificar. Zoho Books verifica el token con base en la Clave Secreta guardada en esta pestaña web y muestra uno de los siguientes resultados:
ResultadoQué significa
El token es válidoLa firma es válida y el token no ha expirado. Zoho Books muestra la firma como Válida, la validez como Válido hasta {fecha y hora}, y el payload decodificado con el ID de la organización, el ID del usuario, el tipo de token y el ID de la pestaña web.
El token no es válidoLa firma del token no es válida. No fue firmado con la Clave Secreta de esta pestaña web, o el token se modificó después de haberse emitido. Zoho Books muestra la firma como No válida y el payload como No verificado, ya que no se puede confiar en él.
El token ha expiradoEl tiempo de expiración del token ya pasó. Zoho Books muestra la firma como Válida, pero la validez muestra Expiró el {fecha y hora}. Recarga la pestaña web para obtener un nuevo token.
El tab_id no coincide con esta pestaña webEl tab_id en el payload de este token JWT no coincide con tu pestaña web. Zoho Books muestra la firma como Válida y la validez como Válido hasta {fecha y hora}, pero el token pertenece a una pestaña web diferente.
Validar Token JWT

Nota: Si estás creando la integración del servidor para tu pestaña web, consulta Autenticación JWT en esta página para conocer los claims del payload, los pasos de validación del lado del servidor, el flujo de actualización del token y ejemplos de código en Node.js, Python, Java, PHP y Go.


Marcar una Pestaña Web como Inactiva

Si ya no necesitas una pestaña web, puedes marcarla como inactiva en lugar de eliminarla. Las pestañas web inactivas se ocultan de la barra lateral izquierda y no se pueden abrir, pero se pueden volver a marcar como activas más adelante si es necesario.

Para marcar una pestaña web como inactiva:

  • Ve a Configuración.
  • Selecciona Pestañas Web en Personalización.
  • Pasa el cursor sobre la pestaña web que deseas marcar como inactiva, haz clic en el ícono de menú desplegable y selecciona Marcar como Inactiva.
Marcar Pestaña Web como Inactiva

Marcar una Pestaña Web como Activa

Para marcar una pestaña web inactiva como activa:

  • Ve a Configuración.
  • Selecciona Pestañas Web en Personalización.
  • Pasa el cursor sobre la pestaña web inactiva que deseas marcar como activa, haz clic en el ícono de menú desplegable y selecciona Marcar como Activa.
Marcar Pestaña Web como Activa

Eliminar una Pestaña Web

Si ya no necesitas una pestaña web, puedes eliminarla. Eliminar una pestaña web la borra permanentemente de Zoho Books y esta acción no se puede deshacer.

Para eliminar una pestaña web:

  • Ve a Configuración.
  • Selecciona Pestañas Web en Personalización.
  • Pasa el cursor sobre la pestaña web que deseas eliminar, haz clic en el ícono de menú desplegable y selecciona Eliminar.
Eliminar Pestaña Web
  • Haz clic en Aceptar en la ventana emergente de confirmación.

Autenticación JWT

Las pestañas web se pueden crear en Configuración para los usuarios de tu organización, en la configuración del Portal del Cliente para tus clientes, y como componentes dentro de extensiones creadas en el Developer Portal de Zoho Books. Puedes habilitar la autenticación JWT en cualquiera de estas pestañas web para que tu aplicación externa pueda verificar que las solicitudes provienen de Zoho Books para la organización y el usuario correctos. El mecanismo del token es el mismo en los tres casos.

Cómo se Entrega el Token

El token JWT no se agrega a la URL de la pestaña web. Zoho Books lo entrega a tu aplicación mediante postMessage desde la ventana principal de Zoho Books, después de que tu aplicación se carga en el iframe.

Cuando un usuario abre la pestaña web, Zoho Books:

  • Carga la URL de tu aplicación en el iframe.
  • Envía el token JWT a tu aplicación usando el tipo de mensaje ZOHO_WEBTAB_AUTH_TOKENS.

Tu aplicación recibe el mensaje y reenvía el token a tu servidor para su validación. Tu servidor valida el token antes de mostrar cualquier dato.

El mensaje que recibe tu aplicación:

{
  "type": "ZOHO_WEBTAB_AUTH_TOKENS",
  "jwt_token": "eyJhbGciOiJIUzI1NiJ9..."
}

Cuando una sesión termina, Zoho Books envía jwt_token: null en el mismo tipo de mensaje.


Claims del Payload JWT

Después de que tu servidor verifica la firma del token, puedes leer los siguientes claims del payload:

ClaimDescripción
organization_idEl ID de la organización de Zoho Books. Asigna este valor a tu inquilino (tenant) para cargar los datos correctos.
user_idEl ID de usuario de Zoho Books para las pestañas web a las que acceden los usuarios de la organización, o el ID de contacto/cliente para las pestañas web del Portal del Cliente.
token_typeSiempre access. Rechaza cualquier token en el que este valor sea diferente.
tab_idEl ID de la pestaña web para la que se emitió este token.
iatHora de emisión como marca de tiempo Unix en segundos.
expHora de expiración como marca de tiempo Unix en segundos. Rechaza el token después de esta hora.

Configuración del token:

ConfiguraciónValor
AlgoritmoHS256 (HMAC-SHA256)
Clave de firmaLa Clave Secreta configurada en la pestaña web, como bytes UTF-8
FormatoJWT estándar (encabezado.payload.firma)

Opciones de validez del token:

Configuración de Validez del TokenDuración
10 minutos600 segundos
30 minutos1800 segundos
1 hora3600 segundos
3 horas10800 segundos

Qué Protege la Validación JWT

Validar el token JWT confirma que:

  • La solicitud de la pestaña web fue generada por Zoho Books.
  • El token se firmó usando la Clave Secreta configurada para esa pestaña web.
  • El token no se modificó durante la transmisión.
  • El token no ha expirado.
  • La solicitud pertenece a la pestaña web, organización y usuario esperados.

No confíes en los valores de los marcadores ni en los valores de los claims del JWT hasta que se aprueben la verificación de la firma y las comprobaciones de expiración.


Validar en tu Servidor

Ejecuta estas comprobaciones en orden en cada token JWT que tu frontend reciba mediante postMessage:

  • Rechaza el token si falta o está vacío.
  • Verifica la firma con la Clave Secreta de tu pestaña web y el algoritmo HS256.
  • Rechaza el token si exp está en el pasado.
  • Rechaza el token si token_type no es access.
  • Opcionalmente, rechaza el token si tab_id no coincide con el ID de tu pestaña web.
  • Solo entonces usa organization_id y user_id para cargar datos.

No muestres contenido confidencial antes de que estas comprobaciones se aprueben.


Actualización del Token

Los tokens JWT expiran después del período de Validez del Token que hayas configurado. Tu aplicación no llama directamente a las API de Zoho para obtener un nuevo token. En su lugar, envía un postMessage a la ventana principal de Zoho Books, y Zoho Books devuelve un nuevo token.

PasoQuiénAcción
1Tu aplicaciónDetecta la expiración o recibe un error 401 de tu API
2Tu aplicaciónEnvía ZOHO_WEBTAB_REQUEST_TOKEN_REFRESH a la ventana principal de Zoho Books mediante postMessage
3Aplicación de Zoho BooksLlama internamente a la API de actualización
4Aplicación de Zoho BooksDevuelve un nuevo token mediante postMessage con ZOHO_WEBTAB_AUTH_TOKENS
5Tu aplicaciónValida el nuevo token en tu servidor

La solicitud de actualización que envía tu aplicación:

{
  "type": "ZOHO_WEBTAB_REQUEST_TOKEN_REFRESH"
}

Reglas:

  • Solo la aplicación de Zoho Books llama a la API de actualización. No llames a los endpoints de actualización de Zoho desde tu servidor.
  • Si la actualización falla, pide al usuario que recargue la pestaña web.
  • No registres tokens JWT completos ni tu Clave Secreta.

Marcadores de URL y Confianza

Puedes incluir marcadores compatibles en la URL de la pestaña web usando Insertar Marcador al configurar la pestaña web. Por ejemplo:

https://yourapp.example.com/entry?customer_id=${CONTACT.CONTACT_ID}

Zoho Books resuelve los marcadores antes de cargar tu aplicación. Trata los valores de los marcadores como no confiables hasta que el JWT se valide en tu servidor. Un atacante podría crear una URL con valores de marcadores arbitrarios. Lee organization_id y user_id únicamente desde el payload JWT verificado.


Seguridad de la Clave Secreta

La Clave Secreta se comparte únicamente entre Zoho Books y tu servidor.

ReglaDetalle
Longitud mínima32 caracteres
Longitud máxima500 caracteres
AlmacenamientoVariables de entorno o un gestor de secretos únicamente en tu servidor

Nunca almacenes la Clave Secreta en:

  • JavaScript del frontend
  • Aplicaciones móviles
  • Repositorios públicos
  • Registros (logs)
  • Respuestas visibles en el navegador
  • Archivos de configuración del lado del cliente

Ejemplos de SDK del Servidor

Usa estos ejemplos para validar un token JWT en tu servidor. Almacena tu Clave Secreta en una variable de entorno. Nunca la incluyas en el código del frontend.

Node.js

Dependencia: jsonwebtoken

const jwt = require("jsonwebtoken");

function validateJwtToken(token, secret, expectedTabId) {
  const claims = jwt.verify(token, secret, { algorithms: ["HS256"] });
  if (claims.exp * 1000 < Date.now()) throw new Error("Token expired.");
  if (claims.token_type !== "access") throw new Error("Invalid token type.");
  if (expectedTabId && String(claims.tab_id) !== String(expectedTabId)) {
    throw new Error("Invalid tab.");
  }
  return {
    organization_id: claims.organization_id,
    user_id: claims.user_id,
    tab_id: claims.tab_id,
  };
}

Python

Dependencia: PyJWT

import jwt
import time

def validate_jwt_token(token, secret, expected_tab_id=None):
    claims = jwt.decode(token, secret.encode("utf-8"), algorithms=["HS256"])
    if claims["exp"] < time.time():
        raise ValueError("Token expired.")
    if claims.get("token_type") != "access":
        raise ValueError("Invalid token type.")
    if expected_tab_id and str(claims.get("tab_id")) != str(expected_tab_id):
        raise ValueError("Invalid tab.")
    return {
        "organization_id": claims["organization_id"],
        "user_id": claims["user_id"],
        "tab_id": claims["tab_id"],
    }

Java

Dependencia: io.jsonwebtoken:jjwt

Claims claims = Jwts.parser()
    .setSigningKey(webTabSecret.getBytes(StandardCharsets.UTF_8))
    .parseClaimsJws(token)
    .getBody();

if (claims.getExpiration().before(new Date())) {
    throw new IllegalArgumentException("Token expired.");
}
if (!"access".equals(claims.get("token_type", String.class))) {
    throw new IllegalArgumentException("Invalid token type.");
}
if (expectedTabId != null && !expectedTabId.equals(claims.get("tab_id", String.class))) {
    throw new IllegalArgumentException("Invalid tab.");
}
// Usa claims.get("organization_id") y claims.get("user_id")

PHP

Dependencia: firebase/php-jwt

$claims = (array) JWT::decode($token, new Key($webTabSecret, 'HS256'));
if (($claims['exp'] ?? 0) < time()) {
    throw new InvalidArgumentException('Token expired.');
}
if (($claims['token_type'] ?? '') !== 'access') {
    throw new InvalidArgumentException('Invalid token type.');
}
if ($expectedTabId !== null && $expectedTabId !== (string) $claims['tab_id']) {
    throw new InvalidArgumentException('Invalid tab.');
}
// Usa $claims['organization_id'] y $claims['user_id']

Go

Dependencia: github.com/golang-jwt/jwt/v5

parsed, err := jwt.ParseWithClaims(token, &webTabClaims{}, func(t *jwt.Token) (interface{}, error) {
    return []byte(webTabSecret), nil
})
claims := parsed.Claims.(*webTabClaims)
if claims.ExpiresAt != nil && !claims.ExpiresAt.After(time.Now()) {
    return nil, ErrExpiredToken
}
if claims.TokenType != "access" {
    return nil, ErrInvalidType
}
if expectedTabID != "" && expectedTabID != claims.TabID {
    return nil, ErrInvalidTabID
}
// Usa claims.OrganizationID y claims.UserID

SDK de Cliente

Tu aplicación se ejecuta en un iframe dentro de Zoho Books o del Portal del Cliente. Usa el SDK de Cliente para recibir tokens JWT mediante postMessage y solicitar una actualización cuando expiren.

API

MétodoPropósito
createWebTabAuthClient({ parentOrigin, onToken, onSessionExpired })Crea un cliente. parentOrigin es obligatorio y debe configurarse con el host de Zoho Books o del portal.
init()Comienza a escuchar ZOHO_WEBTAB_AUTH_TOKENS desde la ventana principal.
destroy()Elimina el listener, borra el temporizador de expiración y descarta el token en memoria.
getAccessToken()Devuelve la cadena del token JWT actual, o null.
requestRefresh()Envía ZOHO_WEBTAB_REQUEST_TOKEN_REFRESH a la ventana principal para que Zoho Books emita un nuevo token.
onToken({ jwt_token })Se llama cuando llega un nuevo token en la carga inicial o después de una actualización.
onSessionExpired()Se llama cuando se alcanza el exp del token, o la ventana principal envía jwt_token: null.

El SDK valida event.origin con respecto a parentOrigin, mantiene un único JWT en memoria y programa onSessionExpired a partir del claim exp del token. No llama a tu servidor ni a las API de Zoho. Tu aplicación debe validar el JWT en el servidor.

Inicio Rápido

<script type="module">
  import { createWebTabAuthClient } from './zoho-webtab-auth-sdk.js';

  const JWT_VERIFICATION_API_ENDPOINT = '/api/webtab/session'; // endpoint: tu endpoint de verificación de Token JWT

  const auth = createWebTabAuthClient({
    parentOrigin: 'https://books.zoho.com', // portal: el origen de tu host de portal
    onToken({ jwt_token }) {
      fetch(JWT_VERIFICATION_API_ENDPOINT, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ jwt_token }),
      });
    },
    onSessionExpired() {
      auth.requestRefresh();
    },
  });

  auth.init();
</script>

Cuando tu API devuelva 401, llama a auth.requestRefresh(). No llames a los endpoints de actualización de Zoho desde tu servidor.

Código Fuente del SDK

const MESSAGE_AUTH_TOKENS = 'ZOHO_WEBTAB_AUTH_TOKENS';
const MESSAGE_REQUEST_REFRESH = 'ZOHO_WEBTAB_REQUEST_TOKEN_REFRESH';

function getTokenExpiryMs(token) {
  try {
    const segment = token.split('.')[1];
    if (!segment) return null;
    const payload = JSON.parse(atob(segment.replace(/-/g, '+').replace(/_/g, '/')));
    if (!payload.exp) return null;
    return payload.exp * 1000;
  } catch {
    return null;
  }
}

export function createWebTabAuthClient(config) {
  const { parentOrigin, onToken, onSessionExpired } = config || {};
  if (!parentOrigin) throw new Error('parentOrigin is required');

  let jwt_token = null;
  let initialized = false;
  let expiryTimerId = null;

  function clearExpiryTimer() {
    if (expiryTimerId) { clearTimeout(expiryTimerId); expiryTimerId = null; }
  }

  function scheduleExpiry(token) {
    clearExpiryTimer();
    const expiresAt = getTokenExpiryMs(token);
    if (!expiresAt) return;
    const delay = expiresAt - Date.now();
    if (delay <= 0) { onSessionExpired?.(); return; }
    expiryTimerId = setTimeout(() => { expiryTimerId = null; onSessionExpired?.(); }, delay);
  }

  function onMessage(event) {
    if (event.origin !== parentOrigin) return;
    const data = event.data;
    if (!data || data.type !== MESSAGE_AUTH_TOKENS) return;
    if (data.jwt_token === null || data.jwt_token === undefined) {
      clearExpiryTimer(); jwt_token = null; onSessionExpired?.(); return;
    }
    jwt_token = data.jwt_token;
    scheduleExpiry(jwt_token);
    onToken?.({ jwt_token });
  }

  function init() {
    if (initialized) return;
    initialized = true;
    window.addEventListener('message', onMessage);
  }

  function destroy() {
    if (!initialized) return;
    initialized = false;
    window.removeEventListener('message', onMessage);
    clearExpiryTimer();
    jwt_token = null;
  }

  function getAccessToken() { return jwt_token; }

  function requestRefresh() {
    window.parent.postMessage({ type: MESSAGE_REQUEST_REFRESH }, parentOrigin);
  }

  return { init, destroy, getAccessToken, requestRefresh };
}

export default createWebTabAuthClient;

if (typeof window !== 'undefined') {
  window.ZohoWebTabAuth = { createWebTabAuthClient };
}
Was this document helpful?
Yes
No

Thank you for your feedback!