Saltar al contenido
Tingu Alerta Tingu Alerta
Documentación técnica

API y Webhooks

Recibe en tu sistema cada pago de Yape, Plin y las demás billeteras que capture Tingu Alerta. Webhook en tiempo real firmado con HMAC-SHA256, o consulta REST cuando la necesites.

Qué puedes hacer

Tingu Alerta captura las notificaciones de pago que llegan al celular del negocio y las normaliza. La integración te da acceso a esos pagos ya estructurados, sin que tengas que tocar ninguna app bancaria.

  • Webhook — te enviamos un POST a tu URL cada vez que entra un pago. Es la vía recomendada: llega en segundos y no tienes que consultar nada.
  • API REST — consultas el historial de pagos con filtros de fecha, tipo de billetera y sucursal. Útil para conciliar o para cargar datos históricos.
Ambos servicios se activan por cuenta, bajo solicitud. No vienen incluidos en ningún plan: escríbenos y los habilitamos. Sirve cualquier plan de pago.

Inicio rápido

  1. 1

    Pide la activación

    El dueño de la cuenta escribe por WhatsApp indicando qué necesita: webhook, API o ambos. Se activa en el momento.

  2. 2

    Genera tus credenciales

    Desde la app, en Ajustes → Integraciones. El apiSecret se muestra una sola vez: guárdalo en ese momento.

  3. 3

    Prueba la conexión

    Una llamada al endpoint de pagos confirma que las credenciales funcionan.

# Base URL
https://api.tingualerta.com

# Primera llamada
curl https://api.tingualerta.com/api/notifications?limit=5 \
  -H "X-Api-Key: lpa_a1b2c3d4e5f6..." \
  -H "X-Api-Secret: 9f8e7d6c5b4a..."

Autenticación

Dos cabeceras en cada petición:

CabeceraFormatoDescripción
X-Api-Key lpa_ + 32 hex Identificador público de la credencial
X-Api-Secret 64 hex Secreto. Se guarda cifrado; solo se ve al crearlo
El secret no viaja nunca al navegador. Úsalo solo desde tu servidor. Si se filtra, regenéralo desde la app: la credencial anterior deja de funcionar al instante.

Buenas prácticas

  • Guarda las credenciales en variables de entorno, no en el código.
  • Crea una credencial por sistema (POS, ERP, panel). Así puedes revocar una sin tumbar las demás.
  • Ponle fecha de expiración a las credenciales de pruebas.
  • Revisa el uso desde la app: cada credencial registra su último uso y su total de peticiones.

Consultar pagos

GET /api/notifications

Lista los pagos recibidos, del más reciente al más antiguo, con paginación.

Parámetros de consulta

ParámetroTipoDescripción
pageenteroPágina. Por defecto 1
limitenteroResultados por página. Por defecto 20, máximo 100
tipotextoyape, plin, bim, lemon, luqea, agora, interbank_negocios, bbva_qr
desdeISO 8601Fecha mínima
hastaISO 8601Fecha máxima
idSucursalenteroFiltra por sucursal
El histórico está acotado por el plan de la cuenta. Si envías un desde anterior a ese límite, se ajusta automáticamente al máximo permitido. No devuelve error.

Respuesta

{
  "success": true,
  "alertas": [
    {
      "id": 1842,
      "tipo": "yape",
      "monto": 50.00,
      "nombre_cliente": "Juan Carlos R.",
      "codigo_seguridad": "482",
      "estado": "enviado",
      "fecha_hora": "2026-08-09T15:42:11.000Z",
      "id_billetera": 3,
      "id_sucursal": 1
    }
  ],
  "total": 248,
  "page": 1,
  "limit": 20,
  "totalPages": 13
}

Tiempo real (Socket.io)

Consultar el histórico sirve para cuadrar caja. Para que tu sistema reaccione en el momento en que entra un pago, conéctate por Socket.io con las mismas credenciales.

const { io } = require('socket.io-client');

const socket = io('https://api.tingualerta.com', {
  auth: {
    apiKey: 'lpa_a1b2c3d4...',
    apiSecret: '9f8e7d6c...',
    idSucursal: 3   // opcional: solo los pagos de esa sucursal
  }
});

socket.on('connect', () => socket.emit('solicitar-pendientes'));

socket.on('nueva-alerta', (pago) => {
  // pago.esPrueba  -> alerta de prueba, no la cuentes en caja
  // pago.esPendiente -> reenvío tras reconectar, no la vuelvas a anunciar
  console.log(pago.monto, pago.nombreCliente);
  socket.emit('alerta-recibida', { alertaId: pago.id });
});
Los campos vienen en camelCase (nombreCliente, idSucursal), al contrario que en la API REST, que los devuelve en snake_case. Son dos formatos distintos a propósito de la historia del proyecto; si mapeas a un objeto, necesitas dos definiciones.

Eventos

  • nueva-alerta — un pago. Responde siempre con alerta-recibida: sin ese acuse el pago queda marcado como pendiente y se te reenvía al reconectar.
  • joined-room{ room, idSucursal }. Si pediste una sucursal que no es de tu cuenta, llega idSucursal: null y escucharás todas.
  • cuenta-sin-servicio — el plan venció o se agotó la cuota: ese pago no se registró. Trae el monto para que puedas cobrarlo por otra vía en vez de quedarte a ciegas.

Errores de conexión

Llegan en connect_error. Estos tres son definitivos: reintentar con la misma credencial no puede funcionar, así que detén la reconexión y avisa en pantalla.

  • API_KEY_INVALIDA — credenciales incorrectas o key revocada.
  • API_NO_HABILITADA — la cuenta no tiene activo el Acceso API.
  • CUENTA_DESACTIVADA — cuenta suspendida.

Webhooks

Configuras una URL desde la app y te enviamos un POST con Content-Type: application/json cada vez que entra un pago.

Cabeceras que recibes

CabeceraContenido
X-Tingu-SignatureHMAC-SHA256 del cuerpo, en hexadecimal
X-Tingu-Eventalerta.nueva o test
X-Tingu-TimestampMilisegundos desde epoch

Cuerpo del envío

{
  "Id": 1842,
  "IdUsuarioNegocio": 17,
  "Tipo": "yape",
  "NombreCliente": "Juan Carlos R.",
  "Monto": 50.00,
  "CodigoSeguridad": "482",
  "Estado": "Completado",
  "FechaHora": "2026-08-09T15:42:11.000Z",
  "IdBilletera": 3,
  "IdSucursal": 1,
  "PackageName": "com.bcp.innovacxion.yapeapp",
  "NombreNegocio": "Bodega Central",
  "FechaRegistro": "2026-08-09T15:42:12.000Z"
}

Verificar la firma (obligatorio)

Sin verificar la firma, cualquiera que conozca tu URL puede inyectarte pagos falsos. Calcula el HMAC-SHA256 del cuerpo crudo con tu secret y compáralo en tiempo constante.

// Node.js + Express
const crypto = require('crypto');

app.post('/webhook-tingu',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const firma = req.headers['x-tingu-signature'];
    const esperada = crypto
      .createHmac('sha256', process.env.TINGU_SECRET)
      .update(req.body)
      .digest('hex');

    const a = Buffer.from(firma || '');
    const b = Buffer.from(esperada);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).send('Firma invalida');
    }

    const pago = JSON.parse(req.body);
    // Responde 200 YA. Procesa despues, en segundo plano.
    res.sendStatus(200);
    procesarEnSegundoPlano(pago);
  }
);
# Python + Flask
import hmac, hashlib, os
from flask import request, abort

@app.route('/webhook-tingu', methods=['POST'])
def webhook():
    esperada = hmac.new(
        os.environ['TINGU_SECRET'].encode(),
        request.get_data(),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(esperada, request.headers.get('X-Tingu-Signature', '')):
        abort(401)

    pago = request.get_json()
    return '', 200
// C# / .NET — para integrar con un POS Windows
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var esperada = Convert.ToHexString(
    hmac.ComputeHash(Encoding.UTF8.GetBytes(cuerpoCrudo))
).ToLowerInvariant();

if (!CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(esperada),
        Encoding.UTF8.GetBytes(firmaRecibida)))
    return Unauthorized();

Requisitos de tu endpoint

  • HTTPS con certificado válido y accesible desde internet.
  • Responde en menos de 10 segundos. Ese es el tiempo de espera; después se considera fallo.
  • Devuelve 2xx en cuanto recibas. Procesa en segundo plano: si tardas en responder porque estás guardando en tu base de datos, provocas reintentos innecesarios.
  • Sé idempotente. Un reintento puede reenviarte un pago que ya procesaste. Usa el campo Id como clave única.

Reintentos

Si tu endpoint no responde 2xx, reintentamos hasta 3 veces con 5 segundos de separación. Cada intento queda registrado con su código de respuesta y su error, consultable desde la app.

No se aceptan direcciones internas. Las URLs se resuelven por DNS y se rechazan si apuntan a localhost, redes privadas, enlace local o rangos CGNAT — tanto al guardarlas como antes de cada envío. Es una protección contra SSRF, y aplica también a redirecciones y a nombres que resuelvan a esas IPs.

Códigos de error

HTTPcodeQué significa
401Faltan las cabeceras, o la credencial no existe, está desactivada o expiró
403API_NO_HABILITADALa cuenta no tiene el acceso API activo
403CUENTA_DESACTIVADALa cuenta del negocio está desactivada
429Demasiadas peticiones. Espera y reintenta con espera progresiva
500Error del servidor. Reintenta

Todas las respuestas de error comparten forma:

{
  "success": false,
  "error": "El acceso API no esta habilitado en esta cuenta",
  "code": "API_NO_HABILITADA"
}
La validación es por petición, no por credencial. Si el servicio se desactiva en la cuenta, las credenciales existentes dejan de funcionar de inmediato, sin necesidad de borrarlas.

Activación y soporte

Ambos servicios se habilitan por cuenta. Escríbenos por WhatsApp indicando el correo del negocio y qué necesitas —webhook, API o los dos— y lo activamos.

Si estás integrando y algo no cuadra, cuéntanos qué endpoint llamas y qué respuesta recibes. Los registros de cada webhook y de cada credencial quedan guardados, así que podemos ver exactamente qué pasó.