## Documentation Index Access the complete documentation index at: https://www.zoho.com/es-mx/books/llms.txt Use this file to discover all available documentation pages before proceeding. # Pestañas Web ![](/books/images/ue-resources/llm-utility-menu/sparkle-ai.svg) Explore With AI ![](/books/images/ue-resources/llm-utility-menu/dd-arrow.svg) * [Open in ChatGPT (opens in new tab)](https://chatgpt.com/?prompt=Analyze+the+content+of+https%3A%2F%2Fwww.zoho.com%2Fes-mx%2Fbooks%2Fhelp%2Fsettings%2Fcustomization%2Fweb-tabs.html+and+understand+it+thoroughly+so+I+can+ask+questions+about+it.) * [Open in Claude (opens in new tab)](https://claude.ai/new?q=Analyze+the+content+of+https%3A%2F%2Fwww.zoho.com%2Fes-mx%2Fbooks%2Fhelp%2Fsettings%2Fcustomization%2Fweb-tabs.html+and+understand+it+thoroughly+so+I+can+ask+questions+about+it.) * ![Copy for LLM](/books/images/ue-resources/llm-utility-menu/copy.svg) Copy for LLM * [![View as Markdown](/books/images/ue-resources/llm-utility-menu/md.svg) View as Markdown (opens in new tab)](/es-mx/books/help/settings/customization/web-tabs.md) Available Plans ![](/books/images/ue-resources/llm-utility-menu/dd-arrow.svg) Prémium Élite Supremo 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](/books/help/images/web-tabs/create-web-tab.png) 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](/books/help/images/web-tabs/edit-web-tab.png) * 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: Resultado Qué significa **El token es válido** La 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álido** La 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 expirado** El 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 web** El **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](/books/help/images/web-tabs/validate-jwt-token.png) **Nota:** Si estás creando la integración del servidor para tu pestaña web, consulta [Autenticación JWT](/es-mx/books/help/settings/customization/web-tabs.html#jwt-authentication) 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](/books/help/images/web-tabs/mark-web-tab-inactive.png) * * * ## 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](/books/help/images/web-tabs/mark-web-tab-active.png) * * * ## 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](/books/help/images/web-tabs/delete-web-tab.png) * 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: ```json { "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: Claim Descripción organization\_id El ID de la organización de Zoho Books. Asigna este valor a tu inquilino (tenant) para cargar los datos correctos. user\_id El 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\_type Siempre access. Rechaza cualquier token en el que este valor sea diferente. tab\_id El ID de la pestaña web para la que se emitió este token. iat Hora de emisión como marca de tiempo Unix en segundos. exp Hora de expiración como marca de tiempo Unix en segundos. Rechaza el token después de esta hora. Configuración del token: Configuración Valor Algoritmo HS256 (HMAC-SHA256) Clave de firma La Clave Secreta configurada en la pestaña web, como bytes UTF-8 Formato JWT estándar (encabezado.payload.firma) Opciones de validez del token: Configuración de Validez del Token Duración 10 minutos 600 segundos 30 minutos 1800 segundos 1 hora 3600 segundos 3 horas 10800 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. Paso Quién Acción 1 Tu aplicación Detecta la expiración o recibe un error 401 de tu API 2 Tu aplicación Envía ZOHO\_WEBTAB\_REQUEST\_TOKEN\_REFRESH a la ventana principal de Zoho Books mediante postMessage 3 Aplicación de Zoho Books Llama internamente a la API de actualización 4 Aplicación de Zoho Books Devuelve un nuevo token mediante postMessage con ZOHO\_WEBTAB\_AUTH\_TOKENS 5 Tu aplicación Valida el nuevo token en tu servidor La solicitud de actualización que envía tu aplicación: ```json { "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: ```text 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. Regla Detalle Longitud mínima 32 caracteres Longitud máxima 500 caracteres Almacenamiento Variables 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** ```js 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** ```python 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** ```java 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** ```php $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** ```go 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étodo Propó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** ```html ``` 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** ```js 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 }; } ```