This module covers both subsidy fronts of a family compensation fund (caja de compensación):

  • Application-based subsidies (e.g. the unemployment subsidy): the affiliate requests them, you submit them, and an analyst or the rules engine decides them.
  • Monetary allowance: the cash subsidy per dependent that is settled every period. There is no application here: you generate the month’s settlement, review it and disburse each allowance.

The affiliate must exist in your entity’s registry (see Affiliates).

Operations and scopes #

OperationSDK method (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

In the other SDKs the monetary allowance resource is client.monetaryAllowance (TypeScript), $client->monetaryAllowance (PHP), client.MonetaryAllowance (.NET) and client.MonetaryAllowance (Go). Monetary allowance writes return the envelope with module: "subsidies".

Application-based subsidies #

stateDiagram-v2
  [*] --> RECEIVED: submit
  RECEIVED --> IN_REVIEW: manual review
  RECEIVED --> APPROVED: APPROVE
  IN_REVIEW --> APPROVED: APPROVE
  RECEIVED --> REJECTED: REJECT or rules
  IN_REVIEW --> REJECTED: REJECT
  APPROVED --> [*]: payment (subsidies.disbursed event)
  REJECTED --> [*]

Subsidy types #

The type goes in the path: POST /v1/gateway/subsidies/{subsidy_type}/. The gateway processes statutory subsidies; the main one is unemployment (the unemployment subsidy; the alias desempleo is also accepted). Other statutory types your entity has configured are submitted with their code. The entity’s own subsidies (custom programs) can’t be submitted through the API: they respond 422 and are managed from the contracted interface.

Submit #

Field (SDK)In the bodyRequiredNotes
subsidy_type(path)yesunemployment…
affiliateafiliadoyes{document_type, document_number}; as in credits, the key on the wire is afiliado.
periodperiodnoSettlement period AAAA-MM.
external_referenceexternal_referencenoYour application id. Recommended: it’s your key for reconciling.
extra(merged)noFields your engine asks for in the subsidy form.
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"}
}

Same as in credits: store data.application_id next to your external_reference, and if your key has affiliates:upsert the afiliado block registers the affiliate on the fly (with a notice in warnings if it fails).

Queue and detail #

The queue ( GET/v1/gateway/subsidies/applications/ ) accepts page, page_size (max. 100), search (document or name) and status; the detail ( GET/v1/gateway/subsidies/applications/{app_id}/ ) returns the engine’s body as is.

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"])

Decide #

POST/v1/gateway/subsidies/applications/{app_id}/decision/ takes decision (APPROVE or REJECT) and reason. Unlike credits, there is no approved amount in the contract: the subsidy’s value is set by regulation and by the program’s configuration. Use the same decision constants each SDK provides (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="Does not meet the minimum membership period requirement",
    idempotency_key="horizonte-sub-2026-000045-decision",
)
print(tx.data["status"])

Paying an approved subsidy has no operation of its own in the gateway: the subsidy engine executes it and you find out through the subsidies.disbursed event.

The period’s monetary allowance #

flowchart LR
  A[generate<br/>dry_run: true] --> B{Does it match<br/>your settlement?}
  B -- no --> A
  B -- yes --> C[generate<br/>dry_run: false]
  C --> D[list period<br/>paginating]
  D --> E[disburse<br/>one by one]
  E -.-> F((monetary_allowance.disbursed))
  C -.-> G((monetary_allowance.generated))

Generate (with dry_run) #

POST/v1/gateway/subsidies/monetary-allowance/generate/ evaluates eligibility and generates the period’s allowance. Body: period (AAAA-MM, required), dry_run (default false: with true it evaluates without persisting the disbursements) and extra. It’s a write: it carries an Idempotency-Key even when it’s a dry_run, so use different keys for the simulation and for the actual generation.

sim = client.monetary_allowance.generate(
    period="2026-10", dry_run=True, idempotency_key="horizonte-cuota-2026-10-simulacion-1")
print("Simulation:", 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"))

The response for an eligible generation carries eligible: true, the amount, the disbursement_id and the status; an ineligible one carries eligible: false with the reasons.

Before you rely on dry_run in production

dry_run is part of the contract, but in the sandbox it doesn’t change the response (the sandbox never persists anything). Before you use it as a side-effect-free simulation against your real base, confirm with your Compense account executive that your entity’s engine honors it.

Generate for a specific affiliate #

The contract accepts additional fields in the body. To evaluate and generate the allowance for one affiliate (for example, a reprocess or a change in their situation), send their document in extra as 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"))

List the period’s allowances #

GET/v1/gateway/subsidies/monetary-allowance/ filters by period and status, and paginates with page / page_size (max. 100). Each row carries at least disbursement_id, period, amount and 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"])

An allowance’s states in the engine are pending (generated, awaiting payment), processing, disbursed, failed, reversed and prescribed (the deadline to claim it has passed). To disburse what’s pending, filter by status=pending. In the sandbox, generated allowances show up as GENERATED.

Disburse an allowance #

POST/v1/gateway/subsidies/monetary-allowance/{disb_id}/disburse/ has no body and moves money (the allowance is credited to the affiliate’s payment method, usually their wallet). Give each allowance its own stable Idempotency-Key, derived from the disbursement_id: the recipe The period’s monetary allowance shows the full batch with safe retries.

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 (and subsidies:decide) support the optional X-Compense-Signature HMAC signature when your key has it enabled; the SDKs don’t implement it. The scheme is in Credits.

Typical errors #

HTTPException (Python)When
403AuthErrorThe scope is missing (subsidies:submit, subsidies:decide, subsidies:disburse…).
404NotFoundErrorThe application or the allowance doesn’t exist. In the sandbox, also when submitting or generating for 9999999999.
409IdempotencyConflictErrorSame Idempotency-Key in flight (title: Idempotency conflict), or disbursing something that isn’t in a disbursable state (title: Request rejected by engine).
422ValidationErrorperiod not in AAAA-MM format, unregistered affiliate, subsidy type not configured or custom, invalid decision.
400CompenseAPIErrorThe engine couldn’t pay out the allowance (for example, the affiliate has no active payment method); the reason comes in detail.
502 / 504UpstreamErrorThe engine didn’t respond: retry with the same key.

Sandbox #

DocumentSubsidy (submit → decide)Monetary allowance (generate)
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
  • With no document in the body, the generation is evaluated as eligible.
  • Ids are deterministic: sbx-… for applications and mad-<profile>-<hash> for allowances. Disbursing a mad-E-… allowance responds DISBURSED; one from an ineligible profile, 409.
  • The sandbox doesn’t keep state: the list always returns a sample allowance for the period and a decision doesn’t change the later detail. See Test data.

Webhooks #

Subsidies #

EventSinceWhen it's emitted
subsidies.application.received2026-06-01A subsidy application was submitted (legacy alias subsidy.application.received).
subsidies.decision2026-06-01An advisor or the rules engine decided the subsidy application.
subsidies.disbursed2026-06-01An approved subsidy was paid out.

Monetary allowance #

EventSinceWhen it's emitted
monetary_allowance.generated2026-06-01An affiliate’s monetary allowance was generated for a period.
monetary_allowance.disbursed2026-06-01A generated monetary allowance was paid out.
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 is the disbursement_id returned by the generation and the list: your key for marking the allowance as paid in your system.
  • subsidies.decision carries decision (approved or rejected), who decided, the amounts (or null) and the reasons.
  • The disbursement’s event_id is derived from the Idempotency-Key you used: retrying with the same key never produces a second event.

Verify the signature and deduplicate by X-Webhook-Id as explained in Verification.

What’s next #