ZuloOne
English
Sign in
Menu

🏦 PrivatBank statement — for the developer

Updated

← Localizations

User: how to switch it on. Analyst: the package scope · setting up PrivatBank.

Package LocalizationUkraine, service IUaBank. The integration wire is PrivatBank Autoclient API v3.0.0, not an invented JSON. The API description comes from the Privat24 for Business integration cabinet / the Autoclient v3 Google Doc. There is no live acp.privatbank.ua in CI.

Why there is no sandbox#

Autoclient v3 documents one HOST: https://acp.privatbank.ua. The Environment=test field on the card is a label, not another URL. "MSB-TEST LLC" in the sample JSON is data from the API description, not a cabinet. Privat's sandbox (Open Banking XS2A + QWAC) is a different contract; pasting it into UaBankConnection will break the headers, not "almost work".

Why this is not the Unified Register of Tax Invoices (ЄРПН)#

The Unified Register of Tax Invoices (ЄРПН) is a document flow: the IOutboundGateway queue, MakeDoc → ToGov → receipt. A statement is a poll: the answer is needed now, a retry an hour later is a different batch. So IOutboundCall and channel pb-statements with method: GET.

Payments are not born from the statement. CustomerPayment / VendorPayment without a matching company registry code (ЄДРПОУ) are somebody else's money on somebody else's contract. This integration writes the UaBankStatement information register only — it does not create CustomerPayment / VendorPayment rows from statement lines.

Outbound payment creation (POST /api/proxy/payment/create + add-sign КЕП) is out of scope here: a script cannot produce a Ukrainian DSTU (ДСТУ) signature, and inventing one is a bank fine, not our validation.

Official names, not third-party libraries#

Required headers in the v3 description: User-Agent, token, Content-Type. There is no separate id header — third-party clients added that. The channel is authMode: Header, authHeader: token. The value lives in Integration:Credentials, the name on UaBankConnection.CredentialRef.

HOST https://acp.privatbank.ua, path /api/statements/transactions. On the card BaseUrl = https://acp.privatbank.ua/api, RelativePath appends /statements/transactions?.... Dates DD-MM-YYYY. Page limit 100 (the official recommendation, max 500). Pagination: exist_next_page + next_page_id → followId. Transaction uniqueness: REF+REFN. TRANTYPE C/D. At most 1 request per second. Before the statement — GET /statements/settings: phase must be WRK, work_balance must not be Y.

GET with no body#

HttpOutboundTransport (and the agent hop) must not always attach StringContent. Autoclient GET with Content-Length is a 400. The host builds the request through OutboundHttpRequest: GET/HEAD have no Content. The attempt journal masks the token header the same way as authorization / x-api-key.

Without a Core rebuild the GET body still goes out as before — a live ACP statement will not be imported, and the integration test does not go red for that: it feeds ImportBodyAsync a sample JSON.

Channel#

Channel Autoclient method
pb-statements GET /statements/settings and /statements/transactions. Key pb-settings: / pb-tx:{code}:{page}

connectionTable: UaBankConnection. The three Unified Register of Tax Invoices (ЄРПН) channels still read TaxAuthorityConnection. A bank token does not go on the tax card.

Job PullUaBankStatements (20 6 * * *) calls IUaBank.PullAllAsync. It imports only FL_REAL=r and PR_PR=r. Reversals and interim rows stay out of the register.

Tests: UkraineBankStatementTest (sample from the API description → IR_UaBankStatement, REF+REFN dedup, channel metadata) and UkraineBankConnectionTest (the Unified Register of Tax Invoices (ЄРПН) ≠ bank).

Same topic for other readers: Analyst · User

Where next#

Was this page helpful?