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.

Lo que construirás

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

  1. Instalar y configurar el SDK — un cliente para llamadas de integrador, otro para llamadas de empresa.
  2. Registrar tu plataforma integradora — demuestra que eres dueño de tu hostname mediante un desafío de callback.
  3. Registrar una empresa — onboarding sin claves que devuelve instrucciones de pago.
  4. Activar la empresa — después del pago recibes el X-API-Key de la empresa.
  5. Configurar el endpoint de webhook — almacena el secreto HMAC para verificar eventos.
  6. Registrar la clave de cifrado del operador — tu clave pública RSA para E2EE.
  7. Crear una conversación — obtén un deeplink para compartir con el cliente.
  8. 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 callbackUrl accesible públicamente (HTTPS).
  • Un endpoint de webhook para recibir eventos (puede ser el mismo host).

Paso 1 — Instalar el SDK

Terminal
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.

client.ts
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

POST /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.

register-integrator.ts
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

POST /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.

register-company.ts
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

POST /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.

activate-company.ts
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

PUT /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.

update-webhook.ts
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

PUT /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.

operator-key.ts
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.

webhook.ts
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');
    }
  },
);
EventoCuándo se dispara
COMPANY_ACTIVATEDUna empresa completó el onboarding y recibió su clave de API.
CUSTOMER_JOINEDEl cliente abrió el deeplink y registró su clave pública.
MESSAGE_RECEIVEDEl cliente envió un mensaje (texto o multimedia).
MESSAGE_DELIVEREDEl dispositivo del cliente recibió un mensaje que enviaste.
MESSAGE_READEl cliente leyó un mensaje que enviaste.
TYPING / STOPPED_TYPING / RECORDING / CHAT_CLOSEDPresencia reportada por la ventana de chat del cliente.
CONVERSATION_CLOSEDLa conversación fue cerrada.
BALANCE_LOWEl saldo prepagado de la empresa cayó por debajo del umbral.

Paso 9 — Crear una conversación

POST /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.

create-conversation.ts
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

GET /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.

wait-for-customer.ts
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

POST /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.

send-message.ts
// 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

GET /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.

receive-messages.ts
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

POST /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.

media-and-rich.ts
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

presence-read-react.ts
// 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.

lifecycle.ts
// 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:

full-flow.ts
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íntomaCausa y solución
401 UNAUTHORIZEDX-API-Key o credenciales de integrador faltantes/inválidas.
403 FORBIDDENLa empresa existe pero aún no está activada.
409 KEYS_NOT_ACTIVEIntentaste cifrar antes de que el cliente entrara. Espera a CUSTOMER_JOINED.
402 INSUFFICIENT_BALANCERecarga con purchaseCredits e intenta de nuevo.
Falla la firma del webhookVerifica contra los bytes en bruto del body, no un objeto re-serializado.

Próximos pasos