Remuneración de saldos · Integración PSP

Guía de integración para billeteras

Todo lo necesario para conectar tu PSP al circuito de remuneración: credenciales, firma de requests, endpoints y webhooks. La referencia completa de la API está en Swagger (/docs).

01Cómo funciona el circuito

Tu billetera envía suscripciones (el cliente invierte su saldo) y rescates (el cliente lo recupera) por API, a medida que ocurren. La plataforma las netea por cuenta comitente y, al horario de corte, arma un batch que — previa aprobación humana — se envía al Back Office de la ALyC. El efectivo de cada comitente queda siempre en 0: solo se mueve la posición en el fondo.

  1. Enviás órdenes → quedan QUEUED en el batch del día.
  2. Al corte se congelan (BATCHED) y se calcula el hash del detalle.
  3. Un aprobador verifica y aprueba → los netos van al Back Office (SENT).
  4. Te notificamos por webhook (batch.sent) y liquidamos el neto: positivo, nos transferís; negativo, te devolvemos.

02Credenciales que vas a recibir

CredencialFormatoPara qué
API keysk_… Autentica cada request. Está atada a tu billetera: solo ves tus órdenes y batches.
Signing secret (opcional)whsec_… Firma HMAC de tus requests, si tu key la exige (recomendado en producción).
Webhook secretwhsec_… Verificás con él la firma de los webhooks que te enviamos.
Rotación: las keys pueden tener vencimiento. Al rotarse, recibís la nueva y la anterior sigue válida durante un período de gracia; después responde 401 API key expirada. Guardalas en un secret manager — nunca en el código.

03Autenticación y firma

Cada request lleva la API key en un header:

Authorization: Bearer sk_xxx        # o alternativamente:
X-Api-Key: sk_xxx

Firma HMAC (si tu key la exige)

Agregá dos headers: X-Timestamp (epoch en segundos, tolerancia ±5 minutos) y X-Signature = HMAC-SHA256 en hexadecimal del string "{timestamp}.{body}" con tu signing secret. Para GET el body es la cadena vacía.

// Node.js
import { createHmac } from "node:crypto";

const ts = Math.floor(Date.now() / 1000);
const body = JSON.stringify(payload);       // exactamente lo que envías
const signature = createHmac("sha256", SIGNING_SECRET)
  .update(ts + "." + body)
  .digest("hex");

// Headers: X-Timestamp: ts · X-Signature: signature

04Endpoints

MétodoPathDescripción
GET/api/v1/funds Catálogo de fondos activos (con fund_id, moneda, mínimos y último VCP).
POST/api/v1/subscriptions Alta de suscripción (idempotente).
POST/api/v1/redemptions Alta de rescate (idempotente).
GET/api/v1/orders/{id} Estado de una orden.
POST/api/v1/orders/{id}/cancel Anulación (solo antes del corte).
GET/api/v1/batches/{id} Detalle de un corte: totales, hash y órdenes (para conciliar).
GET/api/v1/accounts Rendimiento por cuenta comitente: saldo valorizado, ganancia y % por fondo.
GET/api/v1/accounts/{comitente} Detalle de una cuenta + evolución histórica del saldo.

Schemas completos, códigos de error y ejemplos por endpoint: Swagger.

05Crear una orden

curl -X POST {BASE_URL}/api/v1/subscriptions \
  -H "Authorization: Bearer sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "sub-2026-07-28-000123",
    "comitente":       "COM-000123",
    "amount":          150000.50,
    "fund_id":         "9be2…-uuid del fondo (GET /api/v1/funds)",
    "client_id":       "tu id interno del cliente",
    "client_name":     "Juan Pérez",          // opcional
    "external_ref":    "op-987654",           // opcional, tu referencia
    "cvu":             "0000003100010000000001" // opcional
  }'

Idempotencia

La idempotency_key es obligatoria y única por orden (mín. 8 caracteres). Un reintento con la misma clave devuelve la misma orden con replayed: true y status 200 (en lugar de 201). Nunca duplica: reintentá con confianza ante timeouts.

Estados de la orden

EstadoSignificado
QUEUEDRecibida, en cola para el próximo corte. Se puede anular.
BATCHEDIncluida en un corte, esperando aprobación. Ya no se anula.
SENTEnviada al Back Office (neteada por comitente).
CANCELLED / REJECTEDAnulada por vos u operador / corte rechazado.

06Rate limiting

Cada key tiene un máximo de requests por minuto. Al excederlo la API responde 429 con Retry-After (segundos) y los headers X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset. Implementá backoff exponencial respetando Retry-After.

07Webhooks (te avisamos nosotros)

Configuranos una URL HTTPS y te notificamos los eventos en lugar de que hagas polling:

EventoCuándoPayload (data)
batch.sentTu corte fue enviado al Back Office. batch_id, business_date, detail_hash, cantidades y montos (suscripto, rescatado, neto).
order.cancelledUn operador anuló una orden tuya. order_id, idempotency_key, comitente, type, amount, reason.
testPrueba de integración manual.Mensaje de prueba.

Cada entrega es un POST con body { event, created_at, data } y estos headers:

X-Webhook-Event:     batch.sent
X-Webhook-Delivery:  id único de la entrega (para deduplicar reintentos)
X-Webhook-Signature: t=1791234567,v1=<hmac hex>

Verificación de la firma

// Node.js — mismo esquema que la firma de requests
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(headers, rawBody, secret) {
  const m = /t=(\d+),v1=([0-9a-f]+)/.exec(headers["x-webhook-signature"]);
  if (!m) return false;
  const expected = createHmac("sha256", secret)
    .update(m[1] + "." + rawBody)
    .digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}
Requisitos: respondé 2xx en menos de 8 segundos (procesá async si hace falta). Ante error reintentamos con backoff exponencial (30s → 1h, hasta 8 intentos). Deduplicá por X-Webhook-Delivery: un mismo evento puede llegar más de una vez.

08Checklist de go-live