Saltar al contenido
API & Webhooks

Integra AVEX en tus flujos

Documentación mínima para integradores. Bearer tokens scoped por cliente, webhooks firmados HMAC-SHA256 y tiers de rate-limit publicados — los mismos números que corren en producción.

01 — Autenticación

Bearer token por cliente

Cada API key de AVEX está limitada al cliente que la solicita. Envíala en el header Authorization como Bearer token y AVEX scopea automáticamente cada operación a los recursos de ese cliente. El ejemplo crea o refleja un pase de estudiante vía POST /api/v1/pass/upsert.

cURLpass-upsert.sh
curl -X POST https://www.avex.com.co/api/v1/pass/upsert \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Content-Type: application/json" \
  -d '{
    "institutionId": "11111111-1111-4111-8111-111111111111",
    "externalId": "EST-12345",
    "data": {
      "uniqueIdentifier": "1098765432",
      "careerId": "ING-SIS",
      "name": "Ana María Gómez",
      "email": "[email protected]",
      "semester": 6,
      "enrollmentYear": 2023,
      "studentStatus": "Active",
      "academicCalendarLink": null,
      "photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
    }
  }'
Reemplaza avex_k1_<tu_clave> con la API key real que generas en tu panel.
TypeScriptpass-upsert.ts
// Node 22+ / TypeScript — fetch nativo.
const res = await fetch("https://www.avex.com.co/api/v1/pass/upsert", {
  method: "POST",
  headers: {
    Authorization: "Bearer avex_k1_<tu_clave>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    institutionId: "11111111-1111-4111-8111-111111111111",
    externalId: "EST-12345",
    data: {
      uniqueIdentifier: "1098765432",
      careerId: "ING-SIS",
      name: "Ana María Gómez",
      email: "[email protected]",
      semester: 6,
      enrollmentYear: 2023,
      studentStatus: "Active",
      academicCalendarLink: null,
      photoUrl: "https://cdn.example.edu.co/fotos/est-12345.jpg",
    },
  }),
});

if (!res.ok) {
  throw new Error(`AVEX respondió ${res.status}`);
}

const data = await res.json();
Mismo endpoint desde Node 22 con fetch nativo — sin dependencias externas.
03 — Endpoints de ingesta

Los 6 endpoints v1

AVEX es un canal, no un CRM: tu sistema empuja los datos ya computados y AVEX los refleja en el pase de Apple y Google Wallet. Las 6 rutas v1 comparten la misma autenticación — Bearer API key scoped por cliente — y cada una exige el scope de su vertical. Reemplaza avex_k1_<tu_clave> por la API key real que generas en tu panel.

Referencia completa del API

Explora el contrato OpenAPI interactivo — esquemas de request/response, códigos de error y los headers de cada ruta pública.

Abrir referencia OpenAPI

Education — pases de estudiante

scope: passes:update

Refleja el pase del estudiante desde tu SIS. El upsert crea en el primer push y refleja los campos mutables en los siguientes; event empuja el cambio de estado; revoke retira el pase.

cURLpass-upsert.sh
curl -X POST https://www.avex.com.co/api/v1/pass/upsert \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Idempotency-Key: 00000000-0000-4000-8000-000000000001" \
  -H "Content-Type: application/json" \
  -d '{
  "institutionId": "11111111-1111-4111-8111-111111111111",
  "externalId": "EST-12345",
  "data": {
    "uniqueIdentifier": "1098765432",
    "careerId": "ING-SIS",
    "name": "Ana María Gómez",
    "email": "[email protected]",
    "semester": 6,
    "enrollmentYear": 2023,
    "studentStatus": "Active",
    "academicCalendarLink": null,
    "photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
  }
}'
POST /api/v1/pass/upsert — crea o refleja un pase. Idempotency-Key opcional para reintentos seguros.
TypeScriptpass-upsert.ts
// Node 22+ / TypeScript — fetch nativo, sin dependencias externas.
const res = await fetch("https://www.avex.com.co/api/v1/pass/upsert", {
  method: "POST",
  headers: {
    Authorization: "Bearer avex_k1_<tu_clave>",
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "institutionId": "11111111-1111-4111-8111-111111111111",
  "externalId": "EST-12345",
  "data": {
    "uniqueIdentifier": "1098765432",
    "careerId": "ING-SIS",
    "name": "Ana María Gómez",
    "email": "[email protected]",
    "semester": 6,
    "enrollmentYear": 2023,
    "studentStatus": "Active",
    "academicCalendarLink": null,
    "photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
  }
}),
});

// 201 en la primera creación, 200 cuando reflejas un pase ya existente.
if (!res.ok) {
  throw new Error(`AVEX respondió ${res.status}`);
}
Mismo upsert desde Node 22 con fetch nativo. crypto.randomUUID() genera la Idempotency-Key.
cURLpass-event.sh
curl -X POST https://www.avex.com.co/api/v1/pass/EST-12345/event \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Content-Type: application/json" \
  -d '{
  "institutionId": "11111111-1111-4111-8111-111111111111",
  "type": "status.changed",
  "data": {
    "studentStatus": "Graduated"
  }
}'
POST /api/v1/pass/{externalId}/event — empuja el cambio de estado (tu SIS es la autoridad).
cURLpass-revoke.sh
curl -X POST https://www.avex.com.co/api/v1/pass/EST-12345/revoke \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Content-Type: application/json" \
  -d '{
  "institutionId": "11111111-1111-4111-8111-111111111111",
  "reason": "Retiro voluntario del programa"
}'
POST /api/v1/pass/{externalId}/revoke — retira el pase con una razón auditable.

Loyalty — miembros y saldos

scope: loyalty:write

Refleja al miembro y su saldo desde tu POS/CRM. AVEX nunca calcula puntos: el upsert mantiene los datos de contacto y el snapshot empuja el balance ya computado más el nivel resuelto.

cURLloyalty-upsert.sh
curl -X POST https://www.avex.com.co/api/v1/loyalty/member/upsert \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Idempotency-Key: 00000000-0000-4000-8000-000000000002" \
  -H "Content-Type: application/json" \
  -d '{
  "programId": "22222222-2222-4222-8222-222222222222",
  "externalId": "SOCIO-789",
  "data": {
    "name": "Carlos Restrepo",
    "email": "[email protected]",
    "phone": "+573001234567",
    "accountNumber": "ACC-0099"
  }
}'
POST /api/v1/loyalty/member/upsert — crea o refleja un miembro por su externalId.
cURLloyalty-snapshot.sh
curl -X POST https://www.avex.com.co/api/v1/loyalty/member/SOCIO-789/snapshot \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Content-Type: application/json" \
  -d '{
  "balance": 4500,
  "tierId": null
}'
POST /api/v1/loyalty/member/{id}/snapshot — empuja el balance entero ya calculado. tierId null limpia el nivel.

Real estate — cuotas

scope: installments:write

Refleja el avance de la cuota desde tu sistema de cobranza. El snapshot empuja el acumulado pagado, el saldo restante y el estado — AVEX los refleja verbatim en el pase.

cURLinstallment-snapshot.sh
curl -X POST https://www.avex.com.co/api/v1/installment/CUOTA-456/snapshot \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Content-Type: application/json" \
  -d '{
  "accumulatedPaid": 1500000,
  "remainingBalance": 8500000,
  "status": "Active"
}'
POST /api/v1/installment/{id}/snapshot — status ∈ Active | Defaulted | Completed.

Sandbox de validación (dry-run)

Antes de empujar datos reales, valida tu integración con el header X-AVEX-Dry-Run: true. Corre toda la autenticación y validación, devuelve un 200 con la previsualización del efecto y no persiste nada — ningún pase llega a tus usuarios finales.

cURLdry-run.sh
# Sandbox de validación: agrega X-AVEX-Dry-Run: true a cualquier
# endpoint de ingesta. AVEX corre auth + validación + el efecto teórico
# y responde 200 con la previsualización — sin crear pases, sin push,
# sin email. Quita el header para ejecutar de verdad.
curl -X POST https://www.avex.com.co/api/v1/installment/CUOTA-456/snapshot \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "X-AVEX-Dry-Run: true" \
  -H "Content-Type: application/json" \
  -d '{"accumulatedPaid":1500000,"remainingBalance":8500000,"status":"Active"}'
El mismo body que el endpoint real; solo cambia el header X-AVEX-Dry-Run.
02 — Webhooks

Firmados con HMAC-SHA256

AVEX firma cada webhook con HMAC-SHA256 sobre el body crudo. Verifica la firma con timingSafeEqual antes de leer el payload — nunca confíes en el header sin validar.

cURLdebug-signature.sh
# Ejemplo de llamada que AVEX envía a tu webhook.
# El body se firma con HMAC-SHA256 sobre el secreto compartido.
curl -X POST https://tu-servidor.com/webhooks/avex \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$AVEX_WEBHOOK_SECRET" -r | cut -d' ' -f1)" \
  -d "$BODY"
Reproducción local de la firma para depurar — usa exactamente la misma fórmula que el backend.
TypeScriptverify-webhook.ts
import crypto from "node:crypto";

/**
 * Verifica la firma HMAC-SHA256 que AVEX envía en X-Webhook-Signature.
 * Usamos crypto.timingSafeEqual para evitar ataques de timing.
 */
export function verifyAvexSignature(rawBody: string, headerValue: string, secret: string): boolean {
  if (!headerValue) return false;

  // AVEX puede enviar "sha256=<hex>" o el hex crudo. Toleramos ambos.
  const hex = headerValue.includes("=") ? headerValue.split("=").pop()! : headerValue;
  if (!/^[0-9a-fA-F]+$/.test(hex)) return false;

  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const expectedBuf = Buffer.from(expected, "hex");
  const receivedBuf = Buffer.from(hex, "hex");

  if (expectedBuf.length !== receivedBuf.length) return false;
  return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
Implementación de referencia. Mismo algoritmo que corre en producción dentro de AVEX.
05 — Endpoints de entrada

El contrato de entrada completo en un solo mapa

Todo endpoint al que tu sistema llama hacia AVEX, en una sola tabla, con su mecanismo de autenticación por fila y el enlace al panel donde vive la credencial. Tres familias de auth: API key (Bearer avex_k1_* con scope por vertical) para la ingesta v1, HMAC-SHA256 global para los webhooks de pago, y HMAC con un secreto entrante por cliente para sus variantes. AVEX no reconstruye el self-serve aquí: cada fila enlaza al panel que ya generas en Configuración.

Inventario de endpoints de entrada — método, autenticación y credencial por ruta
EndpointMétodoAutenticaciónCredencial
Ingesta v1 — API key Bearer avex_k1_* + scope por vertical
/api/v1/pass/upsertPOSTAPI key · scope passes:updateRotar API key
/api/v1/pass/{externalId}/eventPOSTAPI key · scope passes:updateRotar API key
/api/v1/pass/{externalId}/revokePOSTAPI key · scope passes:updateRotar API key
/api/v1/loyalty/member/upsertPOSTAPI key · scope loyalty:writeRotar API key
/api/v1/loyalty/member/{id}/snapshotPOSTAPI key · scope loyalty:writeRotar API key
/api/v1/installment/{id}/snapshotPOSTAPI key · scope installments:writeRotar API key
/api/v1/verify/{institutionId}/{uniqueIdentifier}/{careerCode}GETAPI key · scope passes:readRotar API key
Webhooks de pago — HMAC-SHA256 X-Webhook-Signature, secreto global
/api/payments/utility-webhookPOSTHMAC globalVariable de entorno
/api/webhooks/installment-paymentPOSTHMAC globalVariable de entorno
/api/webhooks/payment-confirmationPOSTHMAC globalVariable de entorno
Webhooks de pago por cliente — HMAC-SHA256, un secreto entrante por cliente
/api/payments/utility-webhook/{clientSlug}POSTHMAC secreto por clienteRotar secreto entrante
/api/webhooks/installment-payment/{clientSlug}POSTHMAC secreto por clienteRotar secreto entrante
/api/webhooks/payment-confirmation/{clientSlug}POSTHMAC secreto por clienteRotar secreto entrante
Webhook de Resend — HMAC Svix, gestionado por la plataforma
/api/webhooks/resendPOSTHMAC Svix (svix-signature)Gestionado por plataforma

Las filas con secreto auto-gestionado enlazan al self-serve ya desplegado: la rotación de API keys vive en Configuración → API Keys, y el secreto entrante por cliente (uno por cliente, cubre las tres URLs por-cliente) en Configuración → Webhooks. Los webhooks de secreto global usan una variable de entorno gestionada en el despliegue, y el webhook de Resend lo administra la plataforma. Esta tabla es documentación: la gestión de cada credencial ocurre solo en su panel enlazado.

03 — Flujos

Tres patrones que ya están en producción

Cada diagrama refleja un flujo que clientes integran hoy. Las cajas en azul AVEX son código nuestro; las cajas neutras son tu sistema o un proveedor externo.

Carga masiva desde CSV

Una llamada autenticada con Bearer crea N pases y los entrega en paralelo a Apple Wallet y Google Wallet.

Confirmación de pago

La pasarela firma el body con HMAC, AVEX verifica con timingSafeEqual y delega al servicio de install del vertical para activar el pase.

Install token

AVEX firma un token corto con HMAC, lo entrega por email o SMS y la ruta pública /install/[token] valida firma y expiry antes de emitir el pase.

04 — Rate limits

Tiers públicos sincronizados con producción

Los números de esta tabla se importan en caliente del runtime — un cambio en producción rompe CI hasta que la página se actualice. Cada respuesta 429 incluye el header Retry-After con los segundos restantes.

Rate limits por tier — AVEX API pública
TierLímiteVentanaAlcanceEndpoint ejemplo
userUsuario autenticado100 req1 minPor sesiónGET /api/institution
publicEndpoints públicos (lectura)30 req1 minPor IPGET /api/v1/verify/...
public_strictEndpoints públicos (escritura)10 req1 minPor IPPOST /api/contact
strictEndpoints críticos5 req1 minPor IP o usuarioPOST /api/auth/login
api_integrationIntegraciones por API key300 req1 minPor API keyPOST /api/utility-bills

Los límites se aplican por IP en endpoints públicos y por API key en endpoints autenticados. Si tu integración requiere un tier más alto, escríbenos al correo del final de la página.

05 — Errores

Una sola shape para toda la API

Todos los endpoints públicos responden con el mismo shape JSON. El campo code es estable y machine-readable; error es el mensaje en el idioma del request; details es opcional y depende del código.

JSONerror-shape.json
{
  "error": "Límite de solicitudes excedido",
  "code": "RATE_LIMITED",
  "details": {
    "retryAfterSeconds": 12
  }
}
Ejemplo de respuesta 429. El campo details es opcional y específico al código.

Códigos comunes

  • HTTP 401UNAUTHORIZED

    Falta Bearer, API key inválida, expirada o revocada.

  • HTTP 429RATE_LIMITED

    El caller excedió el tier aplicable. La respuesta incluye Retry-After con los segundos restantes.

  • HTTP 400VALIDATION_ERROR

    El body no cumple con la validación Zod del endpoint. details enumera cada campo con su mensaje.

  • HTTP 404NOT_FOUND

    El recurso no existe o no pertenece al cliente scoped por la API key.

06 — Idempotencia

Reintenta sin duplicar efectos

Envía Idempotency-Key en POST y PATCH para que tus reintentos sean seguros aunque la red falle a medio camino. AVEX cachea la respuesta original y la devuelve byte por byte si repites la misma clave.

cURLcreate-pass.sh
curl -X POST https://www.avex.com.co/api/v1/pass/upsert \
  -H "Authorization: Bearer avex_k1_<tu_clave>" \
  -H "Idempotency-Key: 00000000-0000-4000-8000-000000000001" \
  -H "Content-Type: application/json" \
  -d '{
    "institutionId": "11111111-1111-4111-8111-111111111111",
    "externalId": "EST-12345",
    "data": {
      "uniqueIdentifier": "1098765432",
      "careerId": "ING-SIS",
      "name": "Ana María Gómez",
      "email": "[email protected]",
      "semester": 6,
      "enrollmentYear": 2023,
      "studentStatus": "Active",
      "academicCalendarLink": null,
      "photoUrl": "https://cdn.example.edu.co/fotos/est-12345.jpg"
    }
  }'
Misma clave = misma respuesta. Reemplaza el UUID por uno generado por tu cliente.
  • Usa un UUID v4

    Genera la clave con un UUID v4 único por operación lógica. La misma clave vuelta a enviar dentro de la ventana devuelve la respuesta original sin ejecutar el efecto dos veces.

  • Ámbito por API key

    El espacio de claves es por API key, no global. Tu Idempotency-Key no puede colisionar con la de otro cliente.

  • Aplica a POST y PATCH

    GET y DELETE son idempotentes por definición — el header se ignora en esos métodos para no inducir falsa seguridad.

Próximos pasos

¿Listo para integrar?

Estos son los tres pasos que normalmente siguen los equipos técnicos cuando han terminado de leer esta documentación.