API de chat web
Conecta un formulario propio al widget de chat o crea una interfaz de chat propia con el SDK del widget, REST, indicador de escritura, SSE y actividad del visitante.
Última actualización el
El widget oficial se instala en la app: consulta Poner el chat en tu sitio web. Las opciones del snippet están en Ajustes del chat web. Esta página es para desarrolladores: el SDK del widget para un formulario propio y la capa REST para una interfaz de chat propia. Consulta OpenAPI, etiqueta inbox.
SDK del widget
Tras cargar el script del widget, window.TillorInbox está disponible. Con él pasas datos de contacto desde tu propio formulario (página de contacto, detalle de venta, solicitud de presupuesto). Todos los métodos esperan a que se haya cargado la configuración del widget.
| Método | Descripción |
|---|---|
ready() | Promise que se resuelve cuando el widget está cargado |
getVisitor() | Lee los datos de contacto guardados en localStorage y los campos del widget rellenados; síncrono, sin red |
setVisitor({ name?, firstName?, lastName?, email?, phone?, message?, customFields? }, options?) | Rellena los campos del chat sin enviar |
open() | Abre la ventana del chat |
close() | Cierra la ventana del chat |
toggle() | Abre o cierra la ventana del chat |
send({ body, name?, firstName?, lastName?, email?, phone?, message?, customFields?, open?, silent?, markSubmitted?, metadata? }) | Envía un mensaje con datos de contacto |
getVisitor() devuelve este objeto:
| Campo | Descripción |
|---|---|
visitorId | ID de este visitante en este navegador (localStorage) |
name | Nombre completo (un solo campo en el widget) |
firstName / lastName | Derivados de name (primera palabra / resto) |
email, phone | Datos de contacto |
customFields | Campos adicionales de los ajustes del widget |
message | Borrador en el campo de mensaje |
detailsSubmitted | Si el formulario del visitante se considera rellenado |
Usa getVisitor() para rellenar de antemano un formulario propio desde localStorage (mismo navegador, también tras recargar la página).
Opciones de setVisitor (segundo argumento):
| Opción | Por defecto | Explicación |
|---|---|---|
open | false | Abrir la ventana del chat directamente |
markSubmitted | false | Ocultar el formulario del visitante si todos los campos obligatorios están rellenados |
onlyEmpty | false | Sobrescribir solo los campos vacíos |
persist | false | Guardar los datos en localStorage (mismo navegador, también en una pestaña nueva) |
Opciones de send:
| Opción | Por defecto | Explicación |
|---|---|---|
open | false | Abrir la ventana del chat antes de enviar |
silent | true si open es false | Sin sonido ni mensaje de error en el widget; usa la Promise para el éxito o el error |
markSubmitted | true | Los datos de contacto no hace falta volver a introducirlos en el widget |
metadata | - | Pistas opcionales para Tillie (no visibles en la ventana del chat); consulta metadata |
Con open: false (por defecto) el botón del chat sigue cerrado. El mensaje sí llega a Tillor; tu propio formulario puede dar las gracias o mostrar un error. Con open: true se abre la ventana del chat y el visitante ve el comportamiento de envío normal (sonido, ticks, posibles errores del widget).
¿Quieres usar un botón propio en lugar del botón flotante del chat? Pon data-launcher="false" en el snippet y llama a TillorInbox.open().
metadata (formulario de reserva)
Usa metadata cuando tengas un formulario propio en el sitio web y Tillie deba enviar solo una confirmación, sin preguntas de seguimiento (por ejemplo porque la pestaña se cierra después).
| Clave | Tipo | Explicación |
|---|---|---|
forceHandover | boolean | Mensaje de confirmación y traspaso a un compañero, sin preguntas de admisión. También ignora la pausa de la IA tras un mensaje del equipo (consulta ignoreAiPause) |
ignoreAiPause | boolean | Tillie responde también cuando está en pausa en modo Automático tras un mensaje del equipo (30 minutos). Útil para un envío silencioso de formularios; combínalo con forceHandover o deja que Tillie haga la admisión normal |
handoverIntent | "reservation" | "caravan_rental" | "sale" | "other" | Opcional: flujo para el traspaso (por defecto: se deduce automáticamente del mensaje) |
source | string | Etiqueta de integración opcional para tu equipo (minúsculas, cifras, -, _; máx. 64 caracteres) |
await TillorInbox.send({
name: "Jan Janssens",
email: "jan@voorbeeld.be",
phone: "+32 3 779 81 64",
body: structuredBookingMessage,
silent: true,
metadata: {
forceHandover: true,
handoverIntent: "reservation",
source: "website_booking_form",
},
});Sustituir el formulario de contacto (envío silencioso)
Úsalo cuando quieras mantener el formulario de contacto existente, pero hacer que los mensajes pasen por Tillor sin abrir la ventana del chat.
<form id="site-contact-form">
<input name="name" type="text" required />
<input name="email" type="email" required />
<input name="phone" type="tel" />
<textarea name="message" required></textarea>
<button type="submit">Verstuur</button>
<p id="contact-status" hidden></p>
</form>
<script>
document.getElementById("site-contact-form").addEventListener("submit", async (event) => {
event.preventDefault();
const form = event.currentTarget;
const status = document.getElementById("contact-status");
const data = new FormData(form);
try {
await TillorInbox.send({
name: String(data.get("name") ?? ""),
email: String(data.get("email") ?? ""),
phone: String(data.get("phone") ?? ""),
body: String(data.get("message") ?? ""),
});
status.textContent = "Bedankt, we nemen snel contact op.";
status.hidden = false;
form.reset();
} catch (error) {
status.textContent = error instanceof Error ? error.message : "Versturen mislukt.";
status.hidden = false;
}
});
</script>No hace falta que pongas open ni silent: sin open: true la ventana permanece cerrada y Tillor trata la llamada como un envío silencioso.
Conectar el formulario de contacto (abrir el chat)
Pasar los campos y abrir la ventana del chat, para que el visitante vea la conversación:
<form id="site-contact-form">
<input name="name" type="text" required />
<input name="email" type="email" required />
<input name="phone" type="tel" />
<textarea name="message" required></textarea>
<button type="submit">Verstuur</button>
</form>
<script>
document.getElementById("site-contact-form").addEventListener("submit", (event) => {
event.preventDefault();
const form = event.currentTarget;
const data = new FormData(form);
void TillorInbox.send({
name: String(data.get("name") ?? ""),
email: String(data.get("email") ?? ""),
phone: String(data.get("phone") ?? ""),
body: String(data.get("message") ?? ""),
open: true,
});
});
</script>O solo rellenar campos y abrir la ventana, sin enviar de inmediato:
void TillorInbox.setVisitor(
{
name: "Jan Janssens",
email: "jan@voorbeeld.be",
phone: "+32 3 779 81 64",
message: "Ik heb een vraag over de stacaravan te koop.",
},
{ markSubmitted: true, open: true },
);Una página de venta con envío directo y apertura del chat:
void TillorInbox.send({
name: "Jan Janssens",
email: "jan@voorbeeld.be",
phone: "+32 3 779 81 64",
body: "Ik heb interesse in deze stacaravan.",
open: true,
});Sincronizar en vivo mientras alguien escribe:
const form = document.getElementById("site-contact-form");
form.addEventListener("input", () => {
void TillorInbox.setVisitor({
name: form.name.value,
email: form.email.value,
phone: form.phone.value,
message: form.message.value,
});
});Llamar pronto (antes del script del widget)
Si llamas a un método antes de que se haya cargado el script, pon la llamada en la cola:
<script>
window.TillorInbox = window.TillorInbox || { q: [] };
window.TillorInbox.q.push([
"setVisitor",
{ name: "Jan", email: "jan@voorbeeld.be" },
{ open: true },
]);
</script>
<script src="https://tillor.eu/inbox-widget.js" data-org-id="org_abc123" data-widget-key="iwk_xxx" async defer></script>Interfaz de chat propia
Si creas una interfaz de chat propia en lugar del widget, usa la capa REST de abajo.
Autenticación: encabezado X-Tillor-Inbox-Widget-Key (valor de Configuración > Integraciones > Chat web).
Indicador de escritura
POST /api/orgs/{orgId}/inbox/website/typing
X-Tillor-Inbox-Widget-Key: iwk_xxx
Content-Type: application/json
{
"visitorId": "vis_…",
"active": true,
"visitorName": "Jan",
"visitorEmail": "jan@voorbeeld.nl",
"visitorPhone": "+32470123456"
}active: truecuando el visitante escribe;falsecuando el campo está vacío o el visitante deja de escribir (el widget envíafalsetras unos 4 segundos sin entrada)visitorName/visitorEmail/visitorPhoneson opcionales- Usa el mismo
visitorIdque enPOST …/inbox/website/messagesyGET …/inbox/website/messages
Tiempo real (SSE)
GET /api/orgs/{orgId}/inbox/website/subscribe?visitorId=vis_…
X-Tillor-Inbox-Widget-Key: iwk_xxx
Accept: text/event-stream| Evento | Cuándo |
|---|---|
inbox:website:message-created | Nuevo mensaje del equipo o de la IA |
inbox:website:message-updated | Mensaje del equipo o de la IA modificado o eliminado |
inbox:website:typing-changed | Cambia data.staffTyping o data.staffOutboundSenderType |
La forma del payload es la misma que en el SSE de la organización (event, data, timestamp). Vuelve a conectarte tras una interrupción; no hay reanudación.
El widget oficial usa SSE como capa principal y vuelve a obtener GET …/messages cada 5 segundos como respaldo (cada 1 segundo con el panel abierto cuando SSE se interrumpe un momento). Así, las respuestas se marcan como entregadas mientras el visitante está en la página.
Actividad del visitante
POST /api/orgs/{orgId}/inbox/website/events
X-Tillor-Inbox-Widget-Key: iwk_xxx
Content-Type: application/jsonevent: chat_closed, tab_hidden o navigation (en la navegación también url). Tu equipo ve estos eventos como texto informativo en gris en la conversación, consulta Conversaciones por chat web.
Límites
POST …/inbox/website/messages y POST …/inbox/website/attachments/upload responden 429 cuando un visitante o una red envía demasiado. El cuerpo incluye data.reason = INBOX_WIDGET_RATE_LIMITED y data.retryAfterSeconds. Límites por organización:
| Ruta | Por visitante (visitorId) | Por red (dirección IP) |
|---|---|---|
messages | 10 por minuto, 200 por día | 30 por minuto, 600 por día |
attachments/upload | 10 por hora | 50 por hora |
Espera retryAfterSeconds antes de volver a intentarlo y muestra un aviso al visitante en lugar de reintentar automáticamente.