Guía de integración
Guía del widget de chat
Tu chatbot responde a los visitantes en tu propio sitio web. Dos líneas lo ponen ahí; todo lo demás es opcional.
Las secciones 1 a 5 son todo el trabajo para la mayoría de los sitios web. Las secciones 6 y 7 son la pareja que permite a tu chatbot responder «¿dónde está mi pedido?»: necesitan a alguien de desarrollo y una tarde. El resto es referencia, política y qué hacer cuando algo va mal.
01Tu ID de chat
Un solo ID identifica tu chatbot. Lo encontrarás en el fragmento de Código para tu sitio, en la pestaña Inicio de tu página de chat, junto al botón Copiar.
Identifica tu chatbot y nada más: ni cuenta, ni clave de API, ni inicio de sesión en ninguna parte de tu sitio web.
Se puede publicar. Está en el HTML de tu página, donde cualquiera puede leerlo. Lo único que permite es hacer una pregunta a tu chatbot, y solo en los sitios web que hayas autorizado.
02Ponlo en tu sitio web
Añade las dos líneas a cualquier página, justo antes de la etiqueta de cierre body.
<script src="https://www.obbo.me/obbochat.js" async></script>
<div data-obbo-chat="YOUR_CHAT_ID"></div>El script se carga una vez por página y monta una burbuja de chat en la esquina. Ponlo en la plantilla de tu sitio y lo tendrán todas las páginas.
Autoriza tu sitio web primero.Tu chatbot solo responde en los sitios web de la pestaña Nombre y bienvenida de la página del chat de tu Brand Center; al principio, el propio sitio de tu Brand Center. Añade cualquier otro sitio que muestre el chat, como una copia de pruebas. En cualquier otro sitio declina con amabilidad, y la página del chat muestra qué sitio lo intentó.
¿El marcado llega después de cargar la página? El script lo busca una sola vez, cuando la página está lista. Si tu sitio inserta el chat más tarde —una aplicación de una sola página, un gestor de etiquetas, un banner de consentimiento— llama a ObboChat.mount() después.
03Cómo se ve
Define el aspecto en los ajustes de Apariencia de tu chatbot y copia el código: escribe estos atributos y valores de estilo por ti. Solo el ID del chat es obligatorio, y el resto puedes editarlo a mano.
<script src="https://www.obbo.me/obbochat.js" async></script>
<div data-obbo-chat="YOUR_CHAT_ID"
data-obbo-title="Acme Help"
data-obbo-position="left"
data-obbo-launcher="Ask us a question"
style="--obbo-accent: #0f766e; --obbo-accent-text: #ecfdf5; --obbo-bottom: 96px"></div>| Atributo | Qué es |
|---|---|
| data-obbo-chat | Tu ID de chat. Obligatorio: es lo que selecciona tu chatbot. |
| data-obbo-title | El texto del encabezado del panel, normalmente el nombre de tu empresa. |
| data-obbo-theme | "dark" para un panel oscuro. El predeterminado es claro. |
| data-obbo-position | "left" para anclar la burbuja abajo a la izquierda. El predeterminado es la derecha. |
| data-obbo-launcher | El texto del botón del chat, como "Pregúntanos". Sin él, el botón es un icono redondo. |
| data-obbo-icon | "lines" o "dots" para un botón de chat cuya burbuja lleva líneas o puntos. Por defecto, una burbuja simple. |
| data-obbo-font | "site" para usar la tipografía de tu propia página. La predeterminada es una tipografía estándar del sistema. |
| style | Los valores de estilo del aspecto: color de marca, colores de texto, distancia a los bordes, esquinas y tamaño de la ventana. Consulta Estilos propios más abajo. |
¿Has cambiado el aspecto? Copia el código otra vez. El aspecto va dentro del código, así que tu sitio mantiene el anterior hasta que lo reemplaces.
Colores
La tabla de Colores fija seis colores: un fondo y un color de texto para tu marca, para las respuestas del chatbot y para la ventana del chat. Tu marca cubre todo lo que un visitante toca o envía: el botón del chat, el botón Enviar, las preguntas sugeridas y sus propios mensajes. La ventana del chat cubre su título, la caja de texto, los enlaces y la letra pequeña, y sus líneas son una versión tenue de su texto. Toca cualquier casilla para abrir el selector. Igualar mi sitio web fija tu color de marca y muestra lo que encontró en tu página de inicio, como el color de tu logo, al principio de cada selector.
Empezar con Claro u Oscuro fija cada color que esté en Automático. El texto en Automático es blanco o negro, el que se lea mejor sobre su fondo. Cada casilla de texto indica lo fácil que es de leer y, cuando un color es difícil de leer, el selector vuelve a ofrecer Automático.
Estilos propios
Cada color, tamaño y distancia del widget lee su propio valor de estilo. Define cualquiera en el div del chat o en tu propia hoja de estilos, y los tuyos tienen prioridad sobre los del código.
[data-obbo-chat] {
--obbo-accent: #0f766e;
--obbo-assistant-text: #1f2937;
--obbo-font: "Inter", sans-serif;
--obbo-radius: 0;
--obbo-bottom: 96px;
}| Valor de estilo | Qué cambia |
|---|---|
| --obbo-accent --obbo-accent-text | Los botones del chat y Enviar, las preguntas sugeridas, los mensajes del visitante y el texto sobre ellos |
| --obbo-panel-bg --obbo-text --obbo-border | El fondo, el texto, la caja de texto, los enlaces y las líneas de la ventana del chat |
| --obbo-assistant-bg --obbo-input-bg | Las burbujas de respuesta y el cuadro de escritura |
| --obbo-visitor-text --obbo-assistant-text | Texto de los mensajes del visitante y de las respuestas del chatbot |
| --obbo-radius --obbo-launcher-radius --obbo-chip-radius | Lo redondeadas que son las esquinas: la ventana, el botón y los botones de respuesta |
| --obbo-font --obbo-font-size | Tipo de letra y tamaño del texto |
| --obbo-width --obbo-height | Tamaño de la ventana del chat abierta |
| --obbo-bottom --obbo-side | Distancia desde abajo y desde el lado elegido |
| --obbo-shadow --obbo-z | La sombra y lo que queda por debajo |
Lo que no cambia
Las palabras del propio panel —el saludo, el botón de enviar, la petición de valoración— están hoy en inglés, y su estructura es la nuestra. Para un marcado totalmente tuyo, crea tu propia interfaz con el motor sin interfaz de más abajo: el mismo chatbot, tu HTML y tu CSS.
Las respuestas tienen un solo idioma. Tu chatbot responde en el idioma de tu Brand Center, en cualquier idioma que escriba el visitante: tu empresa hablando con sus propias palabras y no una traducción de ellas.
04Construye tu propia interfaz
La burbuja es opcional. El mismo script expone un motor sin interfaz: envía preguntas, suscríbete al estado de la conversación y muéstrala como lo haga tu sitio.
<script src="https://www.obbo.me/obbochat.js" async></script>
<script>
addEventListener('load', function () {
var chat = ObboChat.createChat({ widgetId: 'YOUR_CHAT_ID' });
chat.subscribe(function (state) {
// state.turns is the conversation; state.busy while answering.
});
chat.ask('What do you sell?');
});
</script>| Llamada | Qué es |
|---|---|
| createChat({ widgetId }) | Un motor por chatbot; devuelve el manejador de abajo. |
| ask(text) | Envía una pregunta del visitante. Se resuelve cuando la respuesta llega al estado. |
| subscribe(fn) | Se llama con el estado de la conversación ahora y en cada cambio; devuelve una función para cancelar la suscripción. |
| getState() | El estado actual de la conversación: los turnos y si hay una pregunta en curso. |
| startEstimate() | Empieza un presupuesto, como al tocar Pedir presupuesto: después del saludo, y no de nuevo mientras haya uno en curso o si el chatbot no da presupuestos. |
| destroy() | Desconecta todos los oyentes. Llámalo cuando tu framework desmonte la interfaz. |
| ObboChat.mount() | Vuelve a buscar el fragmento en la página, cuando el marcado llegó tarde. |
| ObboChat.startEstimate(id?) | Abre un chat de la página directamente en un presupuesto: el chat cuyo ID indiques, o el primero. Devuelve false si no hay ninguno. |
Un motor por chatbot. Todo lo que hace la burbuja —la conversación hasta ese momento, una foto para un presupuesto, un visitante con sesión iniciada— te llega en el estado al que te suscribes, así que tu propio panel no renuncia a nada.
05Estimaciones de costes
Tu chatbot puede dar un precio dentro de la conversación, a partir de las preguntas y los precios que configures.
Si tu chatbot tiene un estimador de costes, al visitante que pregunta cuánto cuesta algo se le invita a describir el trabajo en pocas palabras o a añadir una foto. La foto se lee según tus propias preguntas del estimador —el tramo de tamaño, un recuento, un rango aproximado para una cantidad medida— y lo que resuelve se muestra al visitante para que lo confirme o lo cambie antes de que cuente. Nada se tasa solo a partir de una foto: la cifra siempre la fija el visitante, y sus palabras prevalecen sobre la imagen.
Qué recibe el visitante
Un rango detallado en tu moneda, con una línea por cada parte del trabajo —o, si lo prefieres, una sola cifra y en qué se basa— y tu propia frase debajo de cada presupuesto. Es una estimación y nunca una oferta cerrada: el cálculo sale de tus precios, y lo que tus precios no cubren aparece como presupuestado aparte.
Qué configuras tú
Cinco pasos cortos en Presupuestos de la página de tu chat, sin nada de código:
- Escanear mi sitio: se leen tus precios, cargos extra y condiciones de las páginas que tu chatbot ya conoce, y se te sugieren las preguntas que cambian la cifra.
- Lista de precios: todos tus precios en una sola hoja. Cambia una celda, o descárgala, edítala en Excel o Google Sheets y vuelve a subirla. Los precios calculados para ti quedan marcados como Estimación hasta que los revises.
- Tus presupuestos: un trabajo de ejemplo tal como lo ve un visitante. Cambia, quita o añade cualquier línea, y elige qué ve el visitante: cada línea, o una sola cifra y en qué se basa.
- Preguntas: lo que se pregunta al visitante, en tu orden: elegir una opción, elegir varias, un número, una fecha, sí o no, palabras o una foto. Cada pregunta indica qué hace su respuesta con la cifra.
- Cargos y límites: cargos extra y límites, la frase que aparece debajo de cada presupuesto, el tamaño de trabajo que prefieres presupuestar tú mismo y el interruptor que activa los presupuestos.
Las cifras siguen siendo tuyas. Cada precio calculado para ti aparece como Estimación en tu Lista de precios, y cada presupuesto en Conversaciones con clientes indica cuánto viene de tarifas habituales y no de tus propios precios. Un rango que no respaldarías queda a la vista, no oculto.
Dónde acaban los presupuestos
Cada presupuesto aparece en Conversaciones con clientes con las respuestas a partir de las que se hizo —cada una marcada según se leyó de las palabras del visitante, de su foto o se buscó— y con sus datos de contacto si los dejó. También puedes recibir por correo cada presupuesto terminado.
Las fotos se conservan 90 días y puedes borrarlas allí una a una. Cada presupuesto también anota lo que mostraba la foto y que ninguna pregunta pedía.
Nada que programar. Tanto la burbuja como el motor sin interfaz llevan el presupuestador —tu propia interfaz recibe cada presupuesto en el estado de la conversación—, así que aquí no hay nada que programar.
Un enlace «Pedir presupuesto» en tu sitio
Cualquier botón o enlace de una página que tenga tu chat puede abrirlo directamente en un presupuesto. Pega esto donde lo quieras y dale el estilo del resto de tu sitio; la zona Obtener el código de la página de tu chat lo tiene listo para copiar.
<a href="#get-estimate" data-obbo-estimate>Get an estimate</a>El enlace también funciona desde cualquier otro lugar: otra página, un correo, un anuncio o una publicación en redes. Añade #get-estimate al final de la dirección de una página que tenga tu chat, y el chat se abrirá en un presupuesto al cargar esa página.
Tu propio botón. Pon data-obbo-estimate en cualquier elemento, o llama a ObboChat.startEstimate() desde tu propio código. Un visitante con un presupuesto en curso vuelve a él en lugar de empezar de nuevo, y un chatbot sin estimador activo simplemente se abre.
06Dile al chat quién ha iniciado sesión
Tu servidor responde por quién es el visitante, y tu chatbot contesta teniendo en cuenta sus propios datos.
Si los visitantes inician sesión en tu sitio, el chat puede responder preguntas sobre su propia cuenta —«¿dónde está mi pedido?»— con los datos que tu sitio decida compartir. Activa Visitantes con sesión iniciada en la página del chat de tu Brand Center para obtener tu clave de visitante.
Tu clave de visitante es un secreto del servidor.Tu clave de visitante debe estar solo en tu servidor. Nunca la pongas en una página web, una app ni un repositorio de código. Si se filtra, sustitúyela en la página del chat y elige desactivar ya la clave anterior.
Cómo encaja todo
| Quién | Qué hace |
|---|---|
| Tu servidor | Firma un token breve sobre el visitante con sesión iniciada usando tu clave de visitante. |
| Tu página | Pasa el token al chat y le avisa cuando el visitante cierra sesión. |
| El chat | Comprueba la firma y la caducidad y responde teniendo en cuenta los datos de ese visitante. Un token que no supera la comprobación se ignora y el visitante recibe respuesta como invitado. |
Qué contiene el token
| Campo | Qué es |
|---|---|
| sub | Obligatorio. Tu propio ID para este visitante. |
| name | Opcional. Cómo llamarlo. |
| account | Opcional. Lo que decidas compartir —pedidos, reservas, plan— como un objeto JSON de hasta 6 KB. |
| exp | Obligatorio. Caducidad en segundos Unix, como máximo dentro de una hora. Diez minutos bastan: el chat pide a tu página un token nuevo cuando caduca. |
| iat | Opcional. Cuándo lo emitiste, en segundos Unix. |
Fírmalo como JWT con HS256, usando tu clave de visitante como secreto. Cualquier biblioteca JWT lo hace. En Node:
// Your server (Node + Express). npm install jsonwebtoken
import jwt from 'jsonwebtoken';
app.post('/chat-token', (req, res) => {
res.set('Cache-Control', 'no-store');
const user = req.user; // your own sign-in
if (!user) return res.json({ token: null });
const token = jwt.sign(
{
sub: String(user.id),
name: user.firstName,
account: { orders: recentOrdersFor(user) }, // whatever you choose to share
},
OBBO_VISITOR_KEY, // from your server's secret settings
{ algorithm: 'HS256', expiresIn: '10m' },
);
res.json({ token });
});En Python:
# Your server (Python + Flask). pip install PyJWT
import time
import jwt
@app.post("/chat-token")
def chat_token():
user = current_user() # your own sign-in
if user is None:
return {"token": None}
now = int(time.time())
claims = {
"sub": str(user.id),
"name": user.first_name,
"account": {"orders": recent_orders_for(user)}, # whatever you choose to share
"iat": now,
"exp": now + 600,
}
return {"token": jwt.encode(claims, OBBO_VISITOR_KEY, algorithm="HS256")}En tu página
Dale al chat una función que obtenga un token de tu servidor. El chat solo la llama cuando se hace una pregunta y no tiene un token vigente, nunca solo porque se haya cargado una página.
<script>
addEventListener('load', function () {
ObboChat.identify(async function () {
const res = await fetch('/chat-token', { method: 'POST' });
return (await res.json()).token;
});
});
// When the visitor signs out: ObboChat.identify(null);
</script>Si tu sitio inicia la sesión de los visitantes con una cookie, un solo atributo hace lo mismo: el chat envía un POST a esa dirección de tu propio sitio. Haz que esa ruta acepte solo POST, compruebe tu propio inicio de sesión, no envíe cabeceras CORS y responda con un token nulo cuando nadie haya iniciado sesión.
<script src="https://www.obbo.me/obbochat.js" async></script>
<div data-obbo-chat="YOUR_CHAT_ID" data-obbo-identity-url="/chat-token"></div>Cuando un visitante cierre sesión, llama a ObboChat.identify(null). El chat borra su conversación al instante, así que la siguiente persona en el mismo ordenador empieza de cero.
Comprobar que funciona
Cada respuesta del chat le dice a tu página qué ha pasado con el token: visitor.status es signed_in, anonymous o refused, y un token rechazado incluye un motivo. La página del chat en tu Brand Center también muestra el último token aceptado y el último rechazado.
| Motivo | Qué significa |
|---|---|
| bad_signature | No está firmado con tu clave de visitante actual (o con una clave sustituida cuyo día de gracia ya pasó). |
| expired | Ha superado su exp. |
| not_yet_valid | Su iat o nbf está en el futuro. Revisa el reloj de tu servidor. |
| too_long_lived | Su exp está a más de una hora. |
| invalid_claims | Falta sub o exp, o un campo tiene un tipo o una longitud incorrectos. |
| too_large | El token, o los datos de su cuenta, superan el límite de tamaño. |
| malformed | No es un JWT bien formado. |
| unsupported_algorithm | Firmado con algo distinto de HS256. |
| not_enabled | Los visitantes con sesión iniciada están desactivados para este chatbot. |
| keys_unavailable | No pudimos comprobar tu clave en ese momento. No tienes que corregir nada. |
Pruébalo antes de publicar. En la pestaña Probar de tu página de chat, elige «Un cliente con sesión iniciada», escribe un nombre y los datos de cuenta que enviaría tu sitio web, y el chat de prueba pregunta con un token real firmado con tu clave, comprobado igual que el de un visitante.
Qué guardamos.Los datos de la cuenta se envían a nuestro proveedor de IA para redactar cada respuesta. No se guardan con tus conversaciones ni en nuestros registros, aunque una respuesta puede repetirlos.
07Responder preguntas sobre una cuenta
«¿Dónde está mi pedido?», respondido desde tus propios sistemas.
Tu chat puede responder preguntas sobre la cuenta del propio visitante —dónde está su pedido, si se completó un pago— consultando un único punto de acceso en tu propio servidor. Nunca nos conectamos a tus sistemas ni tenemos ninguna clave de ellos: enviamos una descripción de lo que necesita una pregunta y tú decides qué devolver.
- Activa los visitantes con sesión iniciada (sección 6). Tu chatbot solo puede preguntar por alguien a quien tu propio sitio web haya nombrado.
- Crea un único punto de datos en tu servidor. Un GET devuelve tu catálogo; un POST responde a una petición tomada de él.
- En la pestaña Nombre y bienvenida de tu página de chat, abre Para desarrolladores y, en Preguntas sobre la cuenta, pega su dirección en Tu punto de datos y pulsa Conectar dirección: leerá enseguida qué puede responder.
- Pulsa Activar. Desde entonces ese panel muestra cuántas consultas se hicieron, cuánto tarda tu punto de datos y el último problema en lenguaje claro.
Limita cada respuesta al token.Limita cada respuesta al sub del token de visitante que hayas verificado con tu propia clave, e ignora cualquier identidad implícita en el resto de la petición. Eso es lo que hace imposible mostrar a un visitante los datos de otro.
Describe tus datos
Un GET a tu punto de acceso devuelve tu catálogo: los tipos de datos que puedes devolver y los campos de cada uno. Por eso nunca tienes que enumerar las preguntas que podrían hacer tus visitantes: el chat compara la pregunta con tu catálogo en el momento en que se formula.
// GET https://your-site.com/obbo-chat/data
// Describe the data you have. You never list the questions your visitors
// might ask — the chat matches a question to one of these itself.
{
"version": 1,
"resources": [{
"name": "orders",
"title": "Orders",
"description": "Orders on this customer's account, newest first.",
"scope": "visitor",
"fields": [
{"name": "status", "type": "string", "description": "Where it stands.",
"values": ["paid", "shipped", "delivered"]},
{"name": "ship_date", "type": "date", "description": "When it shipped."},
{"name": "tracking", "type": "string", "description": "Carrier tracking link."}
],
"filters": [{"field": "status", "ops": ["eq"]}],
"default_request": {"fields": ["status", "ship_date", "tracking"], "limit": 10},
"max_limit": 25
}]
}| Campo | Qué es |
|---|---|
| name | Identificador en minúsculas de este tipo de datos. Las peticiones lo nombran. |
| title | Cómo llamarlo en una respuesta. |
| scope | visitor: el único ámbito que existe; todo se refiere a quien pregunta. |
| description | Qué contiene, en una frase. Las preguntas se comparan con esto, así que escríbelo como hablan tus visitantes. |
| fields[] | Cada campo: un nombre, un tipo, una descripción breve y sus valores cuando son un conjunto fijo. |
| filters[] | Qué campos se pueden filtrar y con qué operadores: eq, neq, gte, lte, contains, in. |
| default_request | Los campos y el límite que usar cuando la pregunta no dice más. |
| max_limit | El máximo de elementos que devolverás de una vez. |
Responde a una petición
Un POST lleva una petición estructurada tomada de tu catálogo y el token de visitante que firmó tu propio servidor. Todo lo que contiene viene de tu catálogo: una petición que tu catálogo no describe no se envía nunca.
// POST https://your-site.com/obbo-chat/data
// "Where is my order?" becomes this. Operators are only:
// eq, neq, gte, lte, contains, in. There is no SQL and no free text.
{
"version": 1,
"visitor_token": "<the token YOUR server signed for this visitor>",
"request": {
"resource": "orders",
"filters": [{"field": "status", "op": "eq", "value": "shipped"}],
"fields": ["status", "ship_date", "tracking"],
"limit": 10
},
"request_id": "chat_9f2c41b8"
}Responde con esa porción y nada más. Mantén la respuesta por debajo de 64 KB.
// Answer with that slice, and nothing else.
{
"version": 1,
"resource": "orders",
"items": [
{"status": "shipped", "ship_date": "2026-09-14",
"tracking": "https://carrier.example/track/4471"}
],
"item_count": 1,
"truncated": false
}| Campo | Qué es |
|---|---|
| version | Siempre 1. |
| resource | El recurso sobre el que respondes. |
| items[] | Las filas, cada una solo con los campos solicitados. Cualquier cosa que envíes aquí puede leerse al visitante. |
| item_count | Cuántas filas hay en items. |
| truncated | true si te has guardado alguna. |
Comprobar una petición
Dos comprobaciones, en este orden: que la petición está firmada con tu clave de visitante y de quién se trata. Ambas usan la misma clave con la que ya firmas los tokens de visitante: no hay nada nuevo que instalar. Las dos llamadas van firmadas; la de GET, sobre un cuerpo vacío.
| Encabezado | Qué es |
|---|---|
| X-Chat-Data-Signature | Marca de tiempo y firma. t son segundos unix y v1 el HMAC-SHA256 en hexadecimal de más abajo. Acéptalo solo dentro de los cinco minutos siguientes a t. |
| X-Chat-Key-Version | Cuál de tus claves lo firmó. Cambia cuando reemplazas tu clave, y durante el día de gracia llegan las dos versiones. |
X-Chat-Data-Signature: t=1760000000,v1=4b1f…9c // hex, 64 chars
X-Chat-Key-Version: 3
signature = HMAC_SHA256(
your_visitor_key,
"<t>.<METHOD> <path and query>." + <raw request body>
).hex()
// GET "1760000000.GET /obbo-chat/data." + ""
// POST "1760000000.POST /obbo-chat/data." + the bytes that arrivedconst crypto = require('crypto');
const KEY = process.env.OBBO_VISITOR_KEY; // the key from your chat page
// 1. Is this really from the chat service? The signature covers the method,
// the path as sent, and the RAW body — so check before you parse.
function fromObbo(req, rawBody) {
const fields = Object.fromEntries(
(req.get('X-Chat-Data-Signature') || '').split(',').map(p => p.trim().split('='))
);
const t = Number(fields.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const mine = crypto.createHmac('sha256', KEY)
.update(`${t}.${req.method} ${req.originalUrl}.`)
.update(rawBody)
.digest('hex');
const a = Buffer.from(mine), b = Buffer.from(fields.v1 || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// 2. Who is it about? From the token YOU signed — never from the body.
function visitorId(token) {
const [head, payload, sig] = String(token || '').split('.');
if (!head || !payload || !sig) return null;
const mine = crypto.createHmac('sha256', KEY)
.update(`${head}.${payload}`).digest('base64url');
if (mine !== sig) return null;
const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
if (!claims.exp || Date.now() / 1000 > claims.exp) return null;
return claims.sub;
}
// Your catalog. Signed too, over an empty body.
app.get('/obbo-chat/data', (req, res) => {
if (!fromObbo(req, '')) return res.sendStatus(403);
res.set('Cache-Control', 'no-store').json(CATALOG);
});
// One request. express.text keeps the raw bytes the signature covers;
// express.json would re-encode them and the check would fail.
app.post('/obbo-chat/data', express.text({ type: '*/*' }), async (req, res) => {
if (!fromObbo(req, req.body)) return res.sendStatus(403);
const body = JSON.parse(req.body);
const customer = visitorId(body.visitor_token);
if (!customer) return res.sendStatus(403);
const asked = body.request || {};
if (asked.resource !== 'orders') return res.sendStatus(422); // not mine to answer
// Scope EVERY query by `customer`. Ignore any id in the request body.
const rows = await db.orders.findAll({
where: { customer_id: customer, ...where(asked.filters) },
limit: Math.min(asked.limit || 10, 25),
});
// Return the fields that were asked for, and never a field you did not
// declare: everything you send is read out to the visitor.
const items = rows.map(row => pick(row, asked.fields));
res.set('Cache-Control', 'no-store')
.json({ version: 1, resource: 'orders', items, item_count: items.length });
});Qué responder
Tu código de estado es lo que tu página de chat te informa, así que úsalos tal cual:
| Respuesta | Qué significa |
|---|---|
| 200 | Aquí están los datos. Una lista items vacía es una respuesta perfectamente válida: tu chatbot dirá que no encontró nada en su cuenta. |
| 403 | La firma o el token de visitante no cuadran. Responde igual ante cualquier rechazo; nunca digas qué comprobación falló. |
| 404 | No hay nada escuchando. Tu página de chat lo interpreta como una dirección equivocada. |
| 422 | Tu catálogo describe esta petición pero no la vas a servir. Tu chatbot responde sin ella y el visitante no nota ningún error. |
| 500 | Algo se rompió en tu lado. Cuenta como un fallo. |
| 301 / 302 | Nunca se siguen: danos la dirección final. |
Cinco fallos seguidos y dejamos de preguntar. Las consultas se pausan cinco minutos y tu página de chat lo indica, con el último motivo. Se reanudan solas; una consulta correcta pone el contador a cero, mientras que Leer qué puede responder solo prueba el punto de datos. Tu chatbot sigue respondiendo todo lo demás mientras tanto.
Lento cuenta como apagado.Una petición que enviamos se responde en 2,5 segundos o el chat responde sin ella: la pregunta del visitante nunca falla porque tu punto de acceso fuera lento.
08Límites y tiempos
Todo lo que el contrato limita, en un solo sitio.
| Límite | A qué se aplica |
|---|---|
| 2.5 s | El tiempo que tiene tu punto de datos para una petición. Si tarda más, tu chatbot responde sin él. |
| 5 s | El tiempo que tiene tu punto de datos cuando se lee tu catálogo. |
| 8 KB | El cuerpo de petición más grande que enviamos. |
| 64 KB | Lo máximo que leeremos de tu respuesta. |
| 8 KB | Cuánto de tu respuesta llega a la contestación: elementos completos, descartados desde el final hasta que el resto quepa. |
| 32 KB | El catálogo más grande que leeremos. |
| 12 | Tipos de datos en un catálogo. |
| 40 | Campos en un tipo de datos. |
| 10 | Filtros en una petición. |
| 120 | Caracteres en el valor de un filtro. |
| 10 | Valores en un filtro in. |
| 6 KB | Datos de cuenta que lleva un token de visitante. |
| 1 h | Lo máximo que puede vivir un token de visitante. Diez minutos sobran. |
| 5 | Fallos seguidos antes de pausar las consultas. |
| 5 min | Cuánto dura la pausa. |
09Content Security Policy
Si tu sitio usa una CSP, permite estas dos. No hace falta nada más: ni marcos, ni tipografías, ni imágenes nuestras.
| Directiva | Por qué |
|---|---|
| script-src https://www.obbo.me | Carga el script del widget. |
| connect-src https://www.obbo.me | Las preguntas y respuestas del chat. |
Los valores de estilo del código los aplica el script del widget, así que una política que bloquea los estilos en línea conserva tu aspecto. Es posible que el navegador siga mostrando un aviso sobre el atributo style; no tiene importancia.
A quién se identifica.Los invitados nunca se identifican: una clave de sesión aleatoria en su navegador agrupa las preguntas de un visitante en una conversación de tu panel, y eso es todo lo que hace. Los visitantes con sesión iniciada solo se identifican por el ID y el nombre que envía tu sitio.
10Cuando no funciona
No aparece nada
Comprueba que el div lleva data-obbo-chat con tu ID de chat y que la etiqueta script está en la página. Si el marcado llega después de que la página esté lista, llama a ObboChat.mount() una vez después. Tu página de chat también nombra el último sitio web donde se vio funcionar el chat, lo que aclara si el fragmento está activo.
Dice que no puede responder en este sitio web
El sitio web no está en tu lista de permitidos. Añádelo en la pestaña Nombre y bienvenida de tu página de chat: allí se nombra el sitio que fue rechazado, para que compruebes que es realmente tuyo.
Dice una y otra vez que no está seguro
Tu chatbot responde con lo que ha leído de tu sitio web y nada más. Revisa la pestaña Tu sitio web, publica después de cada cambio y lee la pestaña Conversaciones: están todas las preguntas de los visitantes, y las que no pudo responder son las páginas que vale la pena escribir.
Mis tokens de visitante son rechazados
Cada respuesta dice por qué: visitor.status es refused, con uno de los motivos de la sección 6. Los dos habituales son un reloj del servidor que no coincide con el nuestro y una clave reemplazada en un solo lado. Tu página de chat muestra el último rechazo y su motivo.
Las preguntas sobre la cuenta dicen que no se pudo contactar con mi punto de datos
Tu página de chat nombra el problema en lenguaje claro. Si es la firma, comprueba tres cosas: que cubre el cuerpo en bruto y no un JSON recodificado, que la ruta incluye su cadena de consulta y que el método está en mayúsculas.
Habéis dejado de preguntar a mi punto de datos
Cinco fallos seguidos pausan las consultas cinco minutos, y se reanudan por sí solas. Corrige la causa: la siguiente consulta correcta pone el contador a cero.
11¿Dudas?
Dinos la dirección de la página, tu ID de chat y la pregunta que hiciste, y miraremos lo mismo que estás mirando tú. Leemos todo lo que llega a hello@obbo.me