GUÍA DE INTEGRACIÓN · PILOTO 0.4

De tu teléfono
a tu proyecto.

Este servicio conecta el WhatsApp de cada cliente con su aplicación. Cada cuenta tiene su propio QR, API key, sesión e historial. No incluye acceso a SMS.

Estado del piloto: los envíos reales están apagados. Una respuesta simulated significa que no se envió nada a WhatsApp. La vinculación QR, si está habilitada por el operador, es independiente del envío. No hay pagos automáticos configurados.

1. Regístrate y conserva tu clave

  1. En Mi cuenta, indica el nombre del proyecto, correo y una contraseña de al menos 12 caracteres.
  2. Se crea una prueba de 7 días y una API key exclusiva. La clave se muestra una sola vez: cópiala a las variables privadas del backend.
  3. Para crear otra clave, usa “Generar nueva clave”. La anterior deja de funcionar inmediatamente; la sesión WhatsApp no cambia.

También puedes registrarte o entrar con Google cuando el operador lo active. Si ya tienes una cuenta con contraseña, entra primero y vincula Google desde tu panel usando el mismo correo; así conservas tu clave, prueba y número. La clave es un secreto, no un identificador público. Nunca la incluyas en una APK, JavaScript del navegador, capturas, repositorios o parámetros de una URL. Una app móvil llama a tu propio backend; tu backend llama a esta API.

2. Escanea el QR con tu teléfono

  1. Introduce tu número con prefijo internacional, por ejemplo +13015550123, y pulsa “Mostrar mi QR”. El ejemplo es ficticio.
  2. En ese teléfono, abre WhatsApp → Dispositivos vinculados → Vincular un dispositivo.
  3. Escanea el QR mostrado en otra pantalla. El panel renueva los códigos que caduquen.
  4. Espera el estado “Conectado”. Solo se acepta el número indicado. Si el número pertenece a otra cuenta del servicio, no se permite vincularlo de nuevo.

Necesitas una cuenta de WhatsApp registrada en tu teléfono. El QR no registra números ni sustituye el teléfono. Tu QR solo aparece con tu sesión de usuario; no se publica ni se registra en los logs. Para cambiar de número, pulsa “Desvincular o cambiar número” y confirma. Se cancela la cola pendiente y se retiran las credenciales locales; tu cuenta, API key y plan se conservan. Si el cierre remoto no pudo confirmarse, retira también el dispositivo desde WhatsApp. Solo hay una línea activa por cuenta. Si retiras el vínculo desde tu teléfono, el panel detecta la sesión cerrada y prepara un QR nuevo al volver. Puedes escanear la misma línea; para usar otra, desvincula primero y cambia el número en el formulario.

3. Configura tu backend

La URL base del portal es https://oswilink.com, sin /docs. El puerto directo 127.0.0.1:8788 está reservado al servidor y no es accesible desde Internet.

OSWI_API_URL=https://oswilink.com
OSWI_API_KEY=TU_CLAVE_PRIVADA

Authorization: Bearer TU_CLAVE_PRIVADA
Content-Type: application/json

Usa HTTPS fuera del servidor. No uses el puerto 8787 del gateway privado para los clientes del servicio: el portal WhatsApp usa su propia API y claves separadas.

4. Tu aplicación controla los envíos

OswiLink proporciona el canal técnico. Tu aplicación elige los destinatarios, el contenido y cuándo enviar. No necesitas registrar autorizaciones en un endpoint previo para usar esta API de WhatsApp.

Las respuestas, incluidos mensajes como STOP o START, se entregan a tu aplicación mediante eventos o webhooks. Tu integración gestiona sus listas, permisos y solicitudes de baja; estas palabras no crean un bloqueo automático en OswiLink. Los límites del piloto y los controles de seguridad del servicio siguen aplicándose.

5. Envía un mensaje

curl "$OSWI_API_URL/v1/messages" \
  -H "Authorization: Bearer $OSWI_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: visita-123-confirmacion-v1' \
  --data '{"channel":"whatsapp","to":"+13015550123","text":"Tu visita está confirmada para mañana.","ttl_seconds":3600}'

Respuesta inicial: HTTP 202 con message.id y estado queued. Guarda ambos. El texto admite hasta 4096 unidades UTF-16. También se admiten archivos como se explica abajo. No se incluyen grupos, campañas ni listas de difusión.

Reintentos: guarda la clave de idempotencia por operación de negocio. Reutilizarla con el mismo cuerpo devuelve el mismo mensaje, sin duplicarlo. Cambiar el cuerpo con la misma clave da HTTP 409. La deduplicación se conserva durante 30 días; no reenvíes operaciones antiguas tras esa ventana. La clave admite 8–128 caracteres: letras, números, punto, guion, guion bajo y dos puntos.

6. Consulta el estado

curl "$OSWI_API_URL/v1/messages/ID_DEL_MENSAJE" \
  -H "Authorization: Bearer $OSWI_API_KEY"
EstadoSignificado
queued / sendingEn cola / intento en curso.
simulatedPrueba interna; no salió ningún mensaje real.
submittedEl proveedor aceptó el envío; todavía no confirma entrega.
delivered / read / playedConfirmación de entrega / lectura / reproducción, cuando WhatsApp la notifique. No se garantizan recibos de lectura.
unknownNo se pudo confirmar el resultado. No reenvíes con otra clave: podrías duplicarlo.
blocked / failed / expired / cancelledBloqueado, fallo confirmado, vencido o cancelado.
receivedMensaje recibido de tu propia cuenta WhatsApp.

Puedes cancelar un mensaje que siga en cola con POST /v1/messages/ID/cancel. No es posible retirar mensajes que ya estén en tránsito.

7. Recibe respuestas y cambios

Tu aplicación consulta eventos usando un cursor persistente. Solo verá los mensajes de su cuenta. No se importan conversaciones históricas ni mensajes de grupos.

curl "$OSWI_API_URL/v1/events?after=0" \
  -H "Authorization: Bearer $OSWI_API_KEY"

La respuesta contiene data y next_cursor. Procesa y guarda los eventos antes de guardar el nuevo cursor; en la siguiente consulta usa ese cursor. Deduplica por id del evento y conserva el cursor en tu base, no solo en memoria. Cada página devuelve hasta 100 eventos. Consulta cada 5–10 segundos cuando no haya nuevas páginas.

Para una vista rápida usa GET /v1/messages; para una sincronización completa usa eventos. Los eventos se conservan aproximadamente 24 horas. Si tu aplicación permanece desconectada más tiempo puede perder contenido: esto no es un archivo histórico permanente. El webhook permite recibir estos mismos eventos automáticamente.

Archivos, imágenes, audio y video

Usa POST /v1/messages con content_type y media. Para texto, el tipo predeterminado es text. El límite del piloto es 8 MiB por archivo, no el máximo de WhatsApp. Solo enlaces HTTPS públicos, sin redirecciones, credenciales en URL, puertos alternativos ni destinos de redes privadas. Debe existir una dirección IPv4 pública.

{
  "channel": "whatsapp",
  "to": "+13015550123",
  "content_type": "document",
  "text": "Adjunto tu factura.",
  "media": {
    "url": "https://archivos.TU-DOMINIO/factura-123.pdf",
    "mimetype": "application/pdf",
    "filename": "factura-123.pdf"
  },
  "ttl_seconds": 3600
}
content_typeFormatoTexto opcional
imageimage/jpeg, image/png, image/webpPie, máximo 1024 caracteres.
videovideo/mp4; usa un video compatible con WhatsApp.Pie, máximo 1024 caracteres.
audioaudio/mpeg, audio/ogg, audio/mp4, audio/aac; los códecs deben ser compatibles.No; omite text.
documentMIME del archivo, por ejemplo application/pdf o application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.Pie, máximo 1024 caracteres.

Validación del piloto: se obtuvieron recibos de lectura en pruebas de PNG, JPG, MP4, MP3, M4A, AAC, PDF, TXT y ZIP. WebP y OGG fueron aceptados por el proveedor, pero su entrega no quedó confirmada. La aceptación de un formato no garantiza su entrega o reproducción en todos los teléfonos.

Usa un nombre de archivo de hasta 120 caracteres ASCII: letras, números, espacios, punto, guion y guion bajo, empezando por letra o número. Aloja el archivo en tu infraestructura; si utilizas enlaces firmados, deben seguir vigentes hasta que salga de la cola. La URL se descarga al enviar, no al aceptar HTTP 202. Un archivo inaccesible, demasiado grande o bloqueado produce failed con media_rejected_before_send. No se descarga ningún enlace durante la simulación, por lo que simulated no certifica que el archivo exista o sea compatible.

Los adjuntos entrantes aparecen en message.received con data.media.filename, mimetype, size y download_path. Descárgalos desde tu backend con tu clave:

curl "$OSWI_API_URL/v1/messages/ID_ENTRANTE/media" \
  -H "Authorization: Bearer $OSWI_API_KEY" \
  --output archivo-recibido

La descarga exige sesión WhatsApp disponible, solo permite archivos de tu cuenta y caduca aproximadamente a las 24 horas. No devuelve la clave de cifrado ni la URL interna del proveedor. HTTP 410 significa que el adjunto no está disponible o venció; HTTP 413 indica exceso de tamaño. No se extrae contenido de visualización única. Tu aplicación debe validar los adjuntos recibidos; no se garantiza análisis antivirus.

Webhook: respuestas y estados sin consultas constantes

Configura la URL HTTPS de tu backend desde el panel, o con:

curl -X PUT "$OSWI_API_URL/v1/webhook" \
  -H "Authorization: Bearer $OSWI_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://TU-APP/webhooks/oswi"}'

Guarda webhook_secret en el backend. No es tu API key. GET /v1/webhook consulta la URL sin revelar el secreto; POST /v1/webhook/rotate-secret con {} lo cambia, invalidando la firma anterior. Para desactivar notificaciones, configura {"url":null}. La URL debe cumplir las mismas restricciones de red pública HTTPS que los archivos. No puede redirigir.

{
  "id": "UUID_DEL_EVENTO",
  "type": "message.received",
  "created_at": 1789850000000,
  "data": {
    "id": "UUID_DEL_MENSAJE",
    "direction": "inbound",
    "phone": "+13015550123",
    "body": "Sí, confirmo la visita",
    "content_type": "text",
    "status": "received"
  }
}

message.queued informa la aceptación; message.updated informa cambios de estado; message.received entrega respuestas entrantes. Son estados de mensajes, no publicaciones de Estado/Stories. Un archivo entrante añade data.media. No debe confundirse aceptación con entrega.

Cabeceras: X-OswiLink-Event-Id, X-OswiLink-Timestamp (segundos Unix) y X-OswiLink-Signature: sha256=.... Verifica HMAC-SHA256 de timestamp + "." + cuerpo HTTP original, compara en tiempo constante y rechaza timestamps con más de 5 minutos de diferencia. No vuelvas a serializar el JSON antes de verificar. El paquete incluye src/webhook-signature.mjs y un receptor funcional Node 24 en examples/webhook-receiver.mjs.

Guarda durablemente cada evento y deduplica por id antes de responder HTTP 2xx. Hay hasta 8 intentos con espera progresiva, timeout de 5 segundos y sin redirecciones. Pueden llegar duplicados o fuera de orden: no retrocedas el estado por un evento antiguo. Usa /v1/events para recuperar eventos dentro de la ventana de 24 horas. Si tu endpoint queda inaccesible, esa ventana no se extiende.

8. JavaScript / Node.js

// Solo en el backend. Nunca en JavaScript del navegador.
const response = await fetch(process.env.OSWI_API_URL + '/v1/messages', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.OSWI_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'pedido-456-confirmacion-v1'
  },
  body: JSON.stringify({
    channel: 'whatsapp', to: '+13015550123', text: 'Pedido confirmado.'
  }),
  signal: AbortSignal.timeout(15000)
});
const result = await response.json();
if (!response.ok) throw new Error(result.error);
// Guardar result.message.id en la base de tu aplicación.

9. PHP / Laravel

En config/services.php declara oswi.url y oswi.key usando tus variables privadas.

use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.oswi.key'))
    ->timeout(15)
    ->withHeaders(['Idempotency-Key' => 'visita-'.$visit->id.'-v1'])
    ->post(config('services.oswi.url').'/v1/messages', [
        'channel' => 'whatsapp',
        'to' => $customer->phone_e164,
        'text' => 'Tu visita está confirmada.',
    ]);

if (!$response->successful()) {
    throw new RuntimeException($response->json('error', 'request_failed'));
}
$messageId = $response->json('message.id');

La operación se ejecuta desde una cola de tu backend. Ante timeout de HTTP, reintenta el mismo cuerpo con la misma clave; no generes otra. No registres la cabecera Authorization en los logs.

10. Prueba, mensualidad y errores

Configuración inicial: prueba de 7 días, 20 mensajes diarios y una conexión por cuenta. El piloto acepta hasta 25 cuentas y 5 conexiones simultáneas. Una cuenta activada manualmente como pagada tiene un límite de 100 mensajes diarios; estos límites no representan todavía una oferta comercial ni un precio.

Al vencer la prueba o el periodo pagado, los nuevos envíos responden HTTP 402 y la cola pendiente se bloquea. Se detiene la conexión WhatsApp, pero puedes iniciar sesión y consultar tus datos. No hay cargos automáticos ni se solicitan tarjetas. La mensualidad automática, los precios y la pasarela de pago siguen pendientes.

HTTPAcción
400 / 415Revisa el cuerpo JSON, formato internacional del teléfono y Content-Type.
401Clave incorrecta o revocada; en el panel, vuelve a iniciar sesión.
402Prueba o suscripción vencida; no reintentar hasta activar el servicio.
403Cuenta suspendida, canal distinto de WhatsApp o destinatario fuera de la lista piloto.
404Mensaje inexistente o perteneciente a otra cuenta.
409Conflicto de idempotencia, envíos reales desactivados o vinculación todavía no disponible.
429Límite diario o demasiadas solicitudes. Espera; no cambies de clave para intentar eludirlo.
503Capacidad o conexión temporalmente no disponible; aplica espera progresiva.

11. Antes de usarlo con clientes reales

Baileys es una biblioteca no oficial y no está afiliada ni autorizada por WhatsApp. Los propios mantenedores desaconsejan el spam y los usos contrarios a los términos de WhatsApp. El servicio puede sufrir desconexiones o restricciones de cuenta. Consulta el aviso oficial del proyecto Baileys.

El portal ya tiene HTTPS y pruebas reales puntuales de vinculación, envío y recepción. Antes del lanzamiento comercial faltan verificación y recuperación de correo, términos y privacidad completos, procedimiento de eliminación de cuenta, validación ampliada de retención, copias cifradas y restauración, pruebas de carga y de webhooks externos, controles de abuso y pasarela con verificación de pagos. Este piloto no debe venderse como API oficial de WhatsApp ni como servicio de disponibilidad garantizada.

12. Privacidad y conservación

No somos un archivo permanente de conversaciones. El contenido, las URLs y los eventos se conservan temporalmente alrededor de 24 horas; los datos técnicos de mensajes, hasta 30 días. Las cuentas y credenciales de conexión se conservan por separado. Consulta el aviso de privacidad y condiciones del piloto. Los bytes de archivos se procesan bajo demanda; tu aplicación decide su propio almacenamiento.