Inicio Rápido
Una guía completa, paso a paso, de toda la integración ixblix construida sobre el paquete oficial
@ixblix/sdk-js. Sígala de arriba a abajo para pasar de un proyecto vacío a una
conversación de overflow cifrada de extremo a extremo en producción.
Un backend de operador (tu CRM/help desk) que registra empresas, entrega un deeplink seguro a los clientes e intercambia mensajes cifrados de extremo a extremo, multimedia y mensajes enriquecidos con ellos — mientras recibe eventos en tiempo real mediante webhooks firmados.
Flujo de integración
- Instalar y configurar el SDK — un cliente para llamadas de integrador, otro para llamadas de empresa.
- Registrar tu plataforma integradora — demuestra que eres dueño de tu hostname mediante un desafío de callback.
- Registrar una empresa — onboarding sin claves que devuelve instrucciones de pago.
- Activar la empresa — después del pago recibes el
X-API-Keyde la empresa. - Configurar el endpoint de webhook — almacena el secreto HMAC para verificar eventos.
- Registrar la clave de cifrado del operador — tu clave pública RSA para E2EE.
- Crear una conversación — obtén un deeplink para compartir con el cliente.
- Intercambiar mensajes cifrados — texto, multimedia, contenido rico, reacciones, presencia.
Requisitos previos
- Node.js 18+ (el SDK usa el módulo incorporado
node:crypto). - Una cuenta de integrador con un
callbackUrlaccesible públicamente (HTTPS). - Un endpoint de webhook para recibir eventos (puede ser el mismo host).
Paso 1 — Instalar el SDK
npm install @ixblix/sdk-js Paso 2 — Crear los clientes de API
Las llamadas en el ámbito del integrador (registrar empresas, listar planes) usan HTTP Basic Auth, mientras que cada
llamada en el ámbito de la empresa usa el header X-API-Key. Crea dos clientes desde la misma URL base.
import { IxblixClient } from '@ixblix/sdk-js';
const BASE_URL = 'https://api.ixblix.app';
// Integrator client — company onboarding & catalog.
export const integrator = new IxblixClient({
baseUrl: BASE_URL,
integratorId: process.env.IXBLIX_INTEGRATOR_ID,
integratorAccessToken: process.env.IXBLIX_INTEGRATOR_TOKEN,
});
// Company client — conversations, messages, media, keys.
// The API key is obtained after activation (Step 5).
export const company = new IxblixClient({
baseUrl: BASE_URL,
apiKey: process.env.IXBLIX_API_KEY,
}); Paso 3 — Registrar tu plataforma integradora
/integrators/register HTTP Basic
El registro es un handshake de tres vías: ixblix hace POST de un challenge a tu
callbackUrl, tu endpoint debe devolver el payload de registro original, y luego
ixblix hace POST de las credenciales que tu endpoint debe devolver con un 200.
await integrator.registerIntegrator({
name: 'My CRM Platform',
hostname: 'mycrm.example.com',
callbackUrl: 'https://mycrm.example.com/ixblix/callback',
});
// The callback handler on your side must answer the challenge,
// then acknowledge the credentials payload.
// POST { type: "challenge" } -> respond with { name, hostname, callbackUrl }
// POST { integratorId, accessToken } -> respond 200 Paso 4 — Registrar una empresa
/companies/register HTTP Basic Las empresas se crean sin claves. Recibes el registro de la empresa más instrucciones de pago para redirigir al cliente al checkout alojado.
const { company: created, payment } = await integrator.registerCompany({
name: 'Acme Support',
planId: 'plan_starter',
email: 'billing@acme.example',
confirmationWebhookUrl: 'https://mycrm.example.com/ixblix/company-activated',
});
console.log(payment.transactionId, payment.checkoutUrl); Paso 5 — Activar la empresa
/companies/{id}/activate/{transactionId} HTTP Basic
Una vez que el pago es confirmado, activa la empresa para obtener su X-API-Key. Almacénalo
en tu gestor de secretos — otorga acceso completo a todos los recursos de la empresa.
const { apiKey } = await integrator.activateCompany(
created.id,
payment.transactionId,
);
// Persist the key and build the company-scoped client.
const company = new IxblixClient({ baseUrl: BASE_URL, apiKey }); Paso 6 — Configurar el endpoint de webhook
/companies/webhook X-API-Key
Establece (o rota) la URL donde ixblix entrega eventos. Cada cambio de URL — o
rotateSecret: true — genera un nuevo secreto HMAC que se devuelve una sola vez.
Almacénalo; lo necesitas para verificar firmas.
const { webhookUrl, webhookSecret } = await company.updateWebhook({
webhookUrl: 'https://mycrm.example.com/ixblix/webhook',
});
// webhookSecret looks like: whsec_xxxxxxxx...
// Store it in your secret manager. It is never returned again. Paso 7 — Generar y registrar la clave del operador
/companies/encryption-key X-API-Key Mantén un par de claves RSA persistente. La clave privada nunca sale de tu proceso; solo la clave pública se registra. Las nuevas conversaciones toman un snapshot de la clave activa, y rotarla mantiene la clave anterior para leer conversaciones antiguas.
import {
IxblixClient,
loadOrCreateOperatorKey,
} from '@ixblix/sdk-js';
// Load an existing keypair or create one on first run (stored with 0600 perms).
const operatorKey = loadOrCreateOperatorKey('./.ixblix-keys');
await company.registerEncryptionKey({
keyId: operatorKey.keyId,
publicKey: operatorKey.publicKeySpki,
});
// Build a client that can encrypt outgoing messages.
const companyWithKey = new IxblixClient({
baseUrl: BASE_URL,
apiKey: process.env.IXBLIX_API_KEY,
operatorKey,
}); Paso 8 — Recibir eventos
ixblix envía cada evento a tu endpoint con una firma HMAC-SHA256 en el header
X-Ixblix-Signature. Siempre verifícala contra el body en bruto antes de actuar. Usa el
middleware raw-body a continuación para que los bytes exactos estén disponibles para verificación.
import express from 'express';
import { verifyWebhook, WEBHOOK_SIGNATURE_HEADER } from '@ixblix/sdk-js';
const app = express();
const webhookSecret = process.env.IXBLIX_WEBHOOK_SECRET!;
// Keep the raw body so the signature can be verified byte-for-byte.
app.post(
'/ixblix/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.header(WEBHOOK_SIGNATURE_HEADER);
const body = JSON.parse(req.body.toString('utf8'));
let event;
try {
({ event } = verifyWebhook(body, webhookSecret, signature));
} catch (err) {
return res.status(401).send('invalid signature');
}
switch (event.event) {
case 'CUSTOMER_JOINED':
return void onCustomerJoined(event);
case 'MESSAGE_RECEIVED':
return void onMessageReceived(event);
case 'MESSAGE_DELIVERED':
case 'MESSAGE_READ':
return void onMessageRead(event);
case 'CONVERSATION_CLOSED':
return void onConversationClosed(event);
case 'BALANCE_LOW':
return void onBalanceLow(event);
default:
return void res.status(200).send('ok');
}
},
); | Evento | Cuándo se dispara |
|---|---|
COMPANY_ACTIVATED | Una empresa completó el onboarding y recibió su clave de API. |
CUSTOMER_JOINED | El cliente abrió el deeplink y registró su clave pública. |
MESSAGE_RECEIVED | El cliente envió un mensaje (texto o multimedia). |
MESSAGE_DELIVERED | El dispositivo del cliente recibió un mensaje que enviaste. |
MESSAGE_READ | El cliente leyó un mensaje que enviaste. |
TYPING / STOPPED_TYPING / RECORDING / CHAT_CLOSED | Presencia reportada por la ventana de chat del cliente. |
CONVERSATION_CLOSED | La conversación fue cerrada. |
BALANCE_LOW | El saldo prepagado de la empresa cayó por debajo del umbral. |
Paso 9 — Crear una conversación
/conversations X-API-Key
Una conversación representa el hilo de overflow para un contacto. Adjunta la identidad del operador para que
el cliente pueda mostrar quién está atendiendo el chat, luego comparte el deeplink devuelto.
const { conversation, contact, deeplink } = await companyWithKey.createConversation({
contact: {
externalId: 'whatsapp_5511999999999',
name: 'Jane Doe',
metadata: { phone: '+55 11 99999-9999', ticket: 'T-1234' },
},
operator: {
uuid: 'agent-42',
name: 'Maria Silva',
image: 'https://cdn.example.com/avatars/maria.png',
// or: gravatarHash: md5('maria@example.com')
},
});
// Send the deeplink back on the original channel (WhatsApp, Instagram, ...). Paso 10 — Esperar a que el cliente entre
/conversations/{conversationId}/keys X-API-Key
El navegador del cliente genera un par de claves cuando el deeplink se abre y registra la clave pública
con ixblix. Prefiere reaccionar al webhook CUSTOMER_JOINED; solo haz polling si es necesario.
sendEncryptedMessage obtiene las claves internamente — solo necesitas saber que el cliente
ha entrado antes de llamarlo.
async function waitForCustomer(conversationId: string) {
for (;;) {
const keys = await companyWithKey.getConversationKeys(conversationId);
if (keys.keyStatus === 'ACTIVE' && keys.customerPublicKey) {
return; // customer joined — safe to call sendEncryptedMessage
}
await new Promise((r) => setTimeout(r, 2000));
}
}
await waitForCustomer(conversation.id); Paso 11 — Enviar un mensaje cifrado
/messages/company X-API-Key
Usa sendEncryptedMessage — un único método de conveniencia que obtiene las claves de la conversación,
cifra el contenido (y/o multimedia y/o adjuntos) con una clave AES, y envía el
mensaje. El SDK envuelve la clave AES tanto para el cliente como para tu propia clave pública para que puedas
leer de vuelta lo que enviaste.
// Text message with operator identity embedded in the encrypted attachments.
await companyWithKey.sendEncryptedMessage(conversation.id, {
content: 'Hello Jane! How can we help you today?',
operatorUuid: '550e8400-e29b-41d4-a716-446655440000',
operatorIdentity: {
uuid: '550e8400-e29b-41d4-a716-446655440000',
name: 'Maria Silva',
gravatarHash: md5('maria@example.com'),
},
}); Paso 12 — Recibir mensajes cifrados
/messages/{conversationId} X-API-Key
Los mensajes siempre se almacenan como ciphertext. Descífralos con tu clave privada. Prefiere manejar
webhooks MESSAGE_RECEIVED en lugar de polling, luego obtén el hilo y descifra.
import { decryptEnvelope } from '@ixblix/sdk-js';
const messages = await companyWithKey.listMessages(conversation.id);
for (const message of messages) {
if (!message.contentEncrypted) continue; // system/legacy plaintext
const plaintext = decryptEnvelope(message, operatorKey.privateKey);
console.log(message.senderType, message.sentAt, plaintext);
} Paso 13 — Enviar multimedia y mensajes enriquecidos
/media/company X-API-Key sendEncryptedMessage maneja texto, multimedia y adjuntos enriquecidos a través de la misma llamada.
Pasa content para texto, file para multimedia, o ambos para un mensaje multimedia con
leyenda. Añade attachments para botones, vCard, ubicación o previews de enlaces — todo
comparte una única clave AES con IVs distintos.
import { readFile } from 'node:fs/promises';
// 1. Plain media (image/audio/document).
const bytes = await readFile('./invoice.pdf');
await companyWithKey.sendEncryptedMessage(conversation.id, {
file: { data: bytes, fileName: 'invoice.pdf', mimeType: 'application/pdf' },
operatorUuid: '550e8400-e29b-41d4-a716-446655440000',
});
// 2. Rich text message (quick replies, buttons, ...).
await companyWithKey.sendEncryptedMessage(conversation.id, {
content: 'Your ticket is ready.',
operatorUuid: '550e8400-e29b-41d4-a716-446655440000',
attachments: {
buttons: [{ type: 'reply', label: 'Open ticket', value: 'open' }],
},
});
// 3. Media + caption + rich attachments in one call.
await companyWithKey.sendEncryptedMessage(conversation.id, {
content: 'Here is your invoices.',
file: { data: bytes, fileName: 'invoice.pdf', mimeType: 'application/pdf' },
operatorUuid: '550e8400-e29b-41d4-a716-446655440000',
attachments: {
buttons: [{ type: 'reply', label: 'Open ticket', value: 'open' }],
},
}); Paso 14 — Presencia, acuses de entrega/lectura y reacciones
// Tell the customer you are typing / recording.
await companyWithKey.reportCompanyPresence(conversation.id, 'typing');
await companyWithKey.reportCompanyPresence(conversation.id, 'stopped');
// Mark a customer message as delivered as soon as your client receives it.
await companyWithKey.markMessageDeliveredByCompany(conversation.id, messageId);
// Mark it as read once the operator actually sees it (window focused and the
// message inside the viewport). The customer sees the receipt live.
await companyWithKey.markMessageReadByCompany(conversation.id, messageId);
// React with an emoji (encrypted to the customer, sent as contentType "react").
await companyWithKey.reactToMessage(conversation.id, messageId, '👍', '550e8400-e29b-41d4-a716-446655440000');
// Pass an empty string to clear your reaction:
// await companyWithKey.reactToMessage(conversation.id, messageId, '', '550e8400-e29b-41d4-a716-446655440000'); Paso 15 — Cerrar y borrar una conversación
Cerrar termina el hilo. Borrar elimina el contenido cifrado para satisfacer reglas de LGPD/retención de datos. Ambos son accionados por token del cliente o por la empresa dependiendo del endpoint — consulta la referencia de la API.
// Report a customer-side close/presence using the conversation token.
// (Company side uses the conversation id with its API key.)
// Erasing encrypted content is a company action:
// POST /api/conversations/{token}/erase Ejemplo completo
Uniendo todo el flujo en un solo archivo:
import {
IxblixClient,
loadOrCreateOperatorKey,
decryptEnvelope,
} from '@ixblix/sdk-js';
import { createHash } from 'crypto';
const md5 = (s: string) => createHash('md5').update(s).digest('hex');
const BASE_URL = 'https://api.ixblix.app';
async function main() {
// 1. Clients
const integrator = new IxblixClient({
baseUrl: BASE_URL,
integratorId: process.env.IXBLIX_INTEGRATOR_ID,
integratorAccessToken: process.env.IXBLIX_INTEGRATOR_TOKEN,
});
// 2. Register + activate a company
const { company: created, payment } = await integrator.registerCompany({
name: 'Acme Support',
planId: 'plan_starter',
});
const { apiKey } = await integrator.activateCompany(created.id, payment.transactionId);
// 3. Operator keypair + company client
const operatorKey = loadOrCreateOperatorKey('./.ixblix-keys');
const company = new IxblixClient({ baseUrl: BASE_URL, apiKey, operatorKey });
await company.registerEncryptionKey({
keyId: operatorKey.keyId,
publicKey: operatorKey.publicKeySpki,
});
await company.updateWebhook({ webhookUrl: 'https://mycrm.example.com/ixblix/webhook' });
// 4. Open a conversation and share the deeplink
const { conversation, deeplink } = await company.createConversation({
contact: { externalId: 'whatsapp_5511999999999', name: 'Jane Doe' },
operator: { uuid: '550e8400-e29b-41d4-a716-446655440000', name: 'Maria Silva' },
});
console.log('Share this link:', deeplink);
// 5. Wait for the customer, then talk
let keys = await company.getConversationKeys(conversation.id);
while (keys.keyStatus !== 'ACTIVE' || !keys.customerPublicKey) {
await new Promise((r) => setTimeout(r, 2000));
keys = await company.getConversationKeys(conversation.id);
}
// sendEncryptedMessage fetches keys, encrypts and sends in one call.
await company.sendEncryptedMessage(conversation.id, {
content: 'Hello! How can we help?',
operatorUuid: '550e8400-e29b-41d4-a716-446655440000',
operatorIdentity: {
uuid: '550e8400-e29b-41d4-a716-446655440000',
name: 'Maria Silva',
gravatarHash: md5('maria@example.com'),
},
});
// 6. Read the reply
const messages = await company.listMessages(conversation.id);
for (const message of messages) {
if (message.contentEncrypted) {
console.log(decryptEnvelope(message, operatorKey.privateKey));
}
}
}
main().catch(console.error); Solución de problemas
| Síntoma | Causa y solución |
|---|---|
401 UNAUTHORIZED | X-API-Key o credenciales de integrador faltantes/inválidas. |
403 FORBIDDEN | La empresa existe pero aún no está activada. |
409 KEYS_NOT_ACTIVE | Intentaste cifrar antes de que el cliente entrara. Espera a CUSTOMER_JOINED. |
402 INSUFFICIENT_BALANCE | Recarga con purchaseCredits e intenta de nuevo. |
| Falla la firma del webhook | Verifica contra los bytes en bruto del body, no un objeto re-serializado. |
Próximos pasos
- Referencia del SDK JavaScript — cada método y auxiliar de criptografía.
- Referencia de la API REST — cada endpoint expuesto.
- Cifrado de Extremo a Extremo — cómo funciona el sobre.
- Webhooks — payloads de eventos y verificación.