Polivor

Enforcement points

An enforcement point is the system that releases money: a payment hub, an ERP payment run, an execution platform. Polivor does not control it. The authorization is what lets it prove the payment was approved, and refuse anything that was not.

What an authorization is

A compact JWS (three dot-separated parts) with header typ: polivor-authz+jwt, alg: EdDSA and a kid. The payload:

{
  "iss": "https://api.staging.polivor.com",
  "aud": "<your enforcement point audience>",
  "sub": "agent:<agent id>@3.7",
  "jti": "<authorization id>",
  "iat": 1794650400,
  "exp": 1794650700,
  "tenant": "<tenant id>",
  "principal": "<principal id>",
  "decision_id": "<decision id>",
  "mandate": { "id": "<mandate id>", "version": 1 },
  "policy_version": 1,
  "action": {
    "type": "PayInvoice",
    "amount_minor": 420000,
    "currency": "EUR",
    "debtor_account": "ES9121000418450200051332",
    "payment_type": "SEPA_CT",
    "beneficiary_iban": "ES7620770024003102575766",
    "reference": "INV-104"
  },
  "action_sha256": "<hex>"
}

The aud is your enforcement point audience, chosen when the tenant is created. The exp is 5 minutes after iat.

Verify it before you release funds

You need only the public keys: GET /.well-known/jwks.json (no key needed). Cache them, and refetch when you see an unknown kid. Keys with use: authz sign authorizations; use: evidence keys sign evidence and must not be accepted here.

Refuse the payment unless all of these hold:

  1. The signature is valid for the kid in the header, with a key whose use is authz and that is not revoked.
  2. The header typ is polivor-authz+jwt and alg is EdDSA. Reject any other algorithm.
  3. The current time is before exp.
  4. aud equals your enforcement point audience.
  5. The payment you are about to make matches action exactly: amount_minor, currency, debtor_account, payment_type, beneficiary_iban (normalised: no spaces, upper case) and reference. A different amount, account or reference means a different payment.
  6. action_sha256 equals the hex SHA-256 of the JSON Canonicalization Scheme (RFC 8785) form of the action object. This stops anyone swapping fields while keeping a valid signature on another part.
  7. The jti has not been used before. Remember used jti values until the authorization has expired, and refuse a second use.

Complete, tested code for these checks in Java and Go is in Verify in Java, Go and .NET. If any check fails, do not pay, and report REJECTED (see below) if you accepted the instruction earlier. Polivor itself ships a reference enforcement point in the console (the payment hub), which refuses a tampered amount, an expired authorization, a replay and a wrong IBAN, so you can compare behaviours.

Report the result

After you act, report with the enforcer key. Exactly one terminal report is accepted per authorization.

{ "status": "REJECTED", "external_ref": "hub-refusal-0042" }
StatusMeaningBudget
EXECUTEDThe payment was released.Stays used.
REJECTEDYou refused it (verification failed, or the bank refused).Given back.
FAILEDYou tried and it failed.Given back.

A second report returns 409 ALREADY_REPORTED. A report more than 24 hours after the authorization expired returns 410 REPORT_WINDOW_CLOSED. An authorization nobody reports on is shown in the console; an admin can release it with POST /v1/authorizations/{id}/not-executed once it has expired, which gives the budget back and writes evidence.

Unreported authorizations

An authorization that was issued but never reported is a signal that your integration has a gap. Polivor counts them and alerts its operators when many are older than an hour. Report every outcome, including refusals.