Este módulo cubre los dos frentes de subsidio de una caja de compensación:

  • Subsidios por solicitud (p. ej. el subsidio al desempleo): el afiliado los pide, tú los radicas, un analista o el motor de reglas los decide.
  • Cuota monetaria: el subsidio en dinero por persona a cargo que se liquida cada período. Aquí no hay solicitud: generas la liquidación del mes, la revisas y desembolsas cada cuota.

El afiliado debe existir en el registro de tu entidad (ver Afiliados).

Operaciones y scopes #

OperaciónMétodo SDK (Python)Scope
POST/v1/gateway/subsidies/{subsidy_type}/client.subsidies.submit_application(subsidy_type, ...)subsidies:submit
GET/v1/gateway/subsidies/applications/client.subsidies.list_applications(...)subsidies:read
GET/v1/gateway/subsidies/applications/{app_id}/client.subsidies.get_application(app_id)subsidies:read
POST/v1/gateway/subsidies/applications/{app_id}/decision/client.subsidies.decide_application(app_id, ...)subsidies:decide
POST/v1/gateway/subsidies/monetary-allowance/generate/client.monetary_allowance.generate(...)subsidies:submit
GET/v1/gateway/subsidies/monetary-allowance/client.monetary_allowance.list(...)subsidies:read
POST/v1/gateway/subsidies/monetary-allowance/{disb_id}/disburse/client.monetary_allowance.disburse(disbursement_id)subsidies:disburse

En los demás SDK el recurso de cuota monetaria es client.monetaryAllowance (TypeScript), $client->monetaryAllowance (PHP), client.MonetaryAllowance (.NET) y client.MonetaryAllowance (Go). Las escrituras de cuota monetaria devuelven el sobre con module: "subsidies".

Subsidios por solicitud #

stateDiagram-v2
  [*] --> RECEIVED: radicar
  RECEIVED --> IN_REVIEW: revisión manual
  RECEIVED --> APPROVED: APPROVE
  IN_REVIEW --> APPROVED: APPROVE
  RECEIVED --> REJECTED: REJECT o reglas
  IN_REVIEW --> REJECTED: REJECT
  APPROVED --> [*]: pago (evento subsidies.disbursed)
  REJECTED --> [*]

Tipos de subsidio #

El tipo va en la ruta: POST /v1/gateway/subsidies/{subsidy_type}/. Por el gateway se procesan los subsidios de ley; el principal es unemployment (el subsidio al desempleo; también se acepta el alias desempleo). Otros tipos de ley que tu entidad tenga configurados se radican con su código. Los subsidios propios de la entidad (programas custom) no se radican por API: responden 422 y se gestionan desde la interfaz contratada.

Radicar #

Campo (SDK)En el cuerpoObligatorioNotas
subsidy_type(ruta)síunemployment…
affiliateafiliadosí{document_type, document_number}; como en créditos, la clave en el cable es afiliado.
periodperiodnoPeríodo de liquidación AAAA-MM.
external_referenceexternal_referencenoTu id de la solicitud. Recomendado: es tu llave para conciliar.
extra(se fusiona)noCampos que tu motor pida en el formulario del subsidio.
from compense_gateway import AffiliateRef

tx = client.subsidies.submit_application(
    "unemployment",
    affiliate=AffiliateRef("CC", "1000000001"),
    period="2026-10",
    external_reference="HORIZONTE-SUB-2026-000045",
    idempotency_key="horizonte-sub-2026-000045",
)
sub_id = tx.data["application_id"]
print(tx.transaction_id, sub_id, tx.data["status"])
JSON
{
  "status": "success",
  "transaction_id": "0d9f7a10-3c2b-4e5f-8a71-000000000045",
  "module": "subsidies",
  "data": {"application_id": "sbx-E-fa4046ec0a84", "status": "RECEIVED", "subsidy_type": "unemployment"}
}

Igual que en créditos: guarda data.application_id junto a tu external_reference, y si tu llave tiene affiliates:upsert el bloque afiliado registra al afiliado al vuelo (con aviso en warnings si falla).

Bandeja y detalle #

La bandeja ( GET/v1/gateway/subsidies/applications/ ) acepta page, page_size (máx. 100), search (documento o nombre) y status; el detalle ( GET/v1/gateway/subsidies/applications/{app_id}/ ) devuelve el cuerpo del motor tal cual.

page = client.subsidies.list_applications(status="IN_REVIEW", search="1000000003", page_size=25)
print(page.count)
for app in page:
    print(app["application_id"], app["status"])

detail = client.subsidies.get_application(sub_id)
print(detail["status"])

Decidir #

POST/v1/gateway/subsidies/applications/{app_id}/decision/ recibe decision (APPROVE o REJECT) y reason. A diferencia de créditos, no hay monto aprobado en el contrato: el valor del subsidio lo fija la norma y la configuración del programa. Usa las mismas constantes de decisión de cada SDK (DECISION_REJECT, Decision::REJECT, Decisions.Reject, compense.DecisionReject).

from compense_gateway import DECISION_REJECT

tx = client.subsidies.decide_application(
    sub_id,
    decision=DECISION_REJECT,
    reason="No cumple el requisito de permanencia",
    idempotency_key="horizonte-sub-2026-000045-decision",
)
print(tx.data["status"])

El pago de un subsidio aprobado no tiene operación propia en el gateway: lo ejecuta el motor de subsidios y te enteras por el evento subsidies.disbursed.

Cuota monetaria del período #

flowchart LR
  A[generate<br/>dry_run: true] --> B{¿Cuadra con<br/>tu liquidación?}
  B -- no --> A
  B -- sí --> C[generate<br/>dry_run: false]
  C --> D[list period<br/>paginando]
  D --> E[disburse<br/>una por una]
  E -.-> F((monetary_allowance.disbursed))
  C -.-> G((monetary_allowance.generated))

Generar (con dry_run) #

POST/v1/gateway/subsidies/monetary-allowance/generate/ evalúa la elegibilidad y genera la cuota del período. Cuerpo: period (AAAA-MM, obligatorio), dry_run (por defecto false: con true evalúa sin persistir los desembolsos) y extra. Es una escritura: lleva Idempotency-Key aunque sea un dry_run, así que usa llaves distintas para la simulación y para la generación en firme.

sim = client.monetary_allowance.generate(
    period="2026-10", dry_run=True, idempotency_key="horizonte-cuota-2026-10-simulacion-1")
print("Simulación:", sim.data)

gen = client.monetary_allowance.generate(
    period="2026-10", idempotency_key="horizonte-cuota-2026-10-generacion")
print(gen.transaction_id, gen.data.get("disbursement_id"), gen.data.get("status"))

La respuesta de una generación elegible trae eligible: true, el amount, el disbursement_id y el status; una no elegible, eligible: false con los motivos.

Antes de confiar en dry_run en producción

dry_run es parte del contrato, pero en el sandbox no cambia la respuesta (el sandbox nunca persiste nada). Antes de usarlo como simulación sin efectos sobre tu base real, confirma con tu ejecutivo de Compense que el motor de tu entidad lo honra.

Generar para un afiliado concreto #

El contrato acepta campos adicionales en el cuerpo. Para evaluar y generar la cuota de un afiliado (por ejemplo, un reproceso o una novedad), envía su documento en extra como document_number:

tx = client.monetary_allowance.generate(
    period="2026-10",
    extra={"document_number": "1000000002"},
    idempotency_key="horizonte-cuota-2026-10-1000000002",
)
print(tx.data.get("eligible"), tx.data.get("reason_code") or tx.data.get("reasons"))

Listar las cuotas del período #

GET/v1/gateway/subsidies/monetary-allowance/ filtra por period y status, y pagina con page / page_size (máx. 100). Cada fila trae al menos disbursement_id, period, amount y status.

page = client.monetary_allowance.list(period="2026-10", page=1, page_size=100)
print(page.count)
for row in page:
    print(row["disbursement_id"], row["amount"], row["status"])

Los estados de una cuota en el motor son pending (generada, por pagar), processing, disbursed, failed, reversed y prescribed (venció el plazo para cobrarla). Para desembolsar lo pendiente, filtra status=pending. En el sandbox las cuotas generadas aparecen como GENERATED.

Desembolsar una cuota #

POST/v1/gateway/subsidies/monetary-allowance/{disb_id}/disburse/ no lleva cuerpo y mueve dinero (la cuota se abona al medio de pago del afiliado, normalmente su billetera). Dale a cada cuota su propia Idempotency-Key estable, derivada del disbursement_id: la receta Cuota monetaria del período muestra el lote completo con reintentos seguros.

out = client.monetary_allowance.disburse(
    "mad-E-fc874ab08f97", idempotency_key="horizonte-cuota-desembolso-mad-E-fc874ab08f97")
print(out.transaction_id, out.data.get("status"), out.idempotent_replay)

subsidies:disburse (y subsidies:decide) admiten la firma HMAC opcional X-Compense-Signature cuando tu llave la tiene activa; los SDK no la implementan. El esquema está en Créditos.

Errores típicos #

HTTPExcepción (Python)Cuándo
403AuthErrorFalta el scope (subsidies:submit, subsidies:decide, subsidies:disburse…).
404NotFoundErrorLa solicitud o la cuota no existen. En el sandbox, también al radicar o generar para 9999999999.
409IdempotencyConflictErrorMisma Idempotency-Key en vuelo (title: Idempotency conflict), o desembolso de algo que no está en estado desembolsable (title: Request rejected by engine).
422ValidationErrorperiod sin formato AAAA-MM, afiliado no registrado, tipo de subsidio no configurado o custom, decision inválida.
400CompenseAPIErrorEl motor no pudo dispersar la cuota (por ejemplo, el afiliado no tiene medio de pago activo); el motivo viene en detail.
502 / 504UpstreamErrorEl motor no respondió: reintenta con la misma llave.

Sandbox #

DocumentoSubsidio (radicar → decidir)Cuota monetaria (generar)
1000000001RECEIVED → APPROVEDeligible: true, amount: 50000, status: GENERATED
1000000002REJECTED (NOT_ELIGIBLE)eligible: false, NOT_ELIGIBLE
1000000003RECEIVED → IN_REVIEW (MANUAL_REVIEW)eligible: true
1000000004REJECTED (OUTSTANDING_DEBT)eligible: false, OUTSTANDING_DEBT
9999999999404404
  • Sin documento en el cuerpo, la generación se evalúa como elegible.
  • Los ids son deterministas: sbx-… para solicitudes y mad-<perfil>-<hash> para cuotas. Desembolsar una cuota mad-E-… responde DISBURSED; una de un perfil no elegible, 409.
  • El sandbox no guarda estado: el listado devuelve siempre una cuota de muestra del período y una decisión no cambia el detalle posterior. Ver Datos de prueba.

Webhooks #

Subsidios #

EventoDesdeCuándo se emite
subsidies.application.received2026-06-01Se radicó una solicitud de subsidio (alias legado subsidy.application.received).
subsidies.decision2026-06-01Un asesor o el motor de reglas resolvió la solicitud de subsidio.
subsidies.disbursed2026-06-01Se pagó un subsidio aprobado.

Cuota monetaria #

EventoDesdeCuándo se emite
monetary_allowance.generated2026-06-01Se generó la cuota monetaria de un afiliado para un período.
monetary_allowance.disbursed2026-06-01Se pagó una cuota monetaria generada.
JSON
{
  "event": "monetary_allowance.disbursed",
  "version": "1",
  "tenant_id": "horizonte",
  "occurred_at": "2026-11-05T09:00:00Z",
  "resource": {
    "allowance_id": "5b2e9c1d-7f3a-4d8b-a6c2-9e0f1a2b3c4d",
    "disbursement_id": "5b2e9c1d-7f3a-4d8b-a6c2-9e0f1a2b3c4d",
    "amount": "47000.00",
    "currency": "COP",
    "method": "wallet",
    "disbursed_at": "2026-11-05T09:00:00Z",
    "status": "disbursed"
  }
}
  • resource.allowance_id es el disbursement_id que devolvió la generación y el listado: tu llave para marcar la cuota como pagada en tu sistema.
  • subsidies.decision trae decision (approved o rejected), quién decidió, los montos (o null) y los motivos.
  • El event_id del desembolso se deriva de la Idempotency-Key que usaste: reintentar con la misma llave nunca produce un segundo evento.

Verifica la firma y deduplica por X-Webhook-Id como se explica en Verificación.

Lo que sigue #