omnibank / powens
Omnibank Powens: bank aggregation through Powens (ex Budget Insight) - its Webview for the consent, accounts, balances, transactions, CONNECTION_SYNCED webhooks.
Requires
- php: >=8.2
- glitchr/omnibank: ^1.0@dev
- symfony/http-client-contracts: ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.0
- symfony/http-client: ^6.4|^7.0|^8.0
Suggests
- symfony/http-client: The HTTP client the gateway talks through
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-03 16:12:01 UTC
README
Powens (ex Budget Insight) for glitchr/omnibank: the user connects their banks in Powens' Webview; their accounts, balances and transactions are read with the user's permanent token; CONNECTION_SYNCED webhooks say when the data is fresh or the consent must be renewed.
Unverified. Written from Powens' published documentation and recorded answers (
Tests/Fixtures); it has not yet been run against a real Powens sandbox. Try it with real keys (docker compose run --rm omnibank connect powensinglitchr/omnibank'sdocker/) before relying on it, and drop this notice once it holds - the webhook signature above all.
omnibank: gateways: banks: factory: powens options: domain: 'nakaya-sandbox.biapi.pro' # the client's domain in the Powens console client_id: '%env(POWENS_CLIENT_ID)%' client_secret: '%env(POWENS_CLIENT_SECRET)%' redirect_uri: 'https://app.example/bank/back' # when connect() is given no return URL webhook_secret: '%env(POWENS_WEBHOOK_SECRET)%' # for notify() webhook_url: 'https://app.example/bank/webhook' # the URL registered: its path is signed language: fr # the Webview's
connect()- the first time,POST /2.0/auth/initcreates the Powens user; its permanent token goes in the connection's state (auth_token,user_id): keep the connection. Each time,GET /2.0/auth/token/codegives a temporary code and the result's URL is the Webview:https://webview.powens.com/connect?domain=…&client_id=…&redirect_uri=…&code=…. When the consent is NEEDS_RENEWAL and the state hasconnection_id, it is the/reconnectpage of that connection. The Webview sends the user back withconnection_idin the query: put it in the state and mark the consent active ($connection->withState([...] + $connection->state)->withConsent(Consent::active())).accounts()-GET /2.0/users/me/accounts(deleted ones left out);balances()-GET /2.0/users/me/accounts/{id}:balance(booked), and withcoming(expected).transactions()-GET /2.0/users/me/accounts/{id}/transactions?min_date=&max_date=&limit=, every page (_links.next.href, else the next offset while pages come full); coming and deleted transactions left out;original_wordingis the label,counterpartyits name and IBAN.notify()- a body with aconnectionis CONNECTION_SYNCED: itsconnectionId, and its state as the consent -SCARequired,webauthRequired,decoupled,additionalInformationNeeded,actionNeeded,wrongpass,passwordExpiredare NEEDS_RENEWAL; none (or a bank outage:websiteUnavailable,rateLimiting,bug) is ACTIVE;expireis its expiry. Another body comes backUNKNOWN, consent NONE (it says nothing of it).- No
transfer()in this version.
Webhook signature - an assumption. Powens signs webhooks with the console's webhook secret in
BI-Signature, with BI-Signature-Date. This package takes base64(HMAC-SHA256(secret,
POST.<webhook path>.<BI-Signature-Date>.<body>)) - which needs webhook_url - and also the
HMAC-SHA256 of the body alone (base64 or hex). Check it against a real sandbox webhook and keep
only what Powens sends.
A 5xx, a 429 or a network failure is an UnavailableException, never an empty list.
Credentials: in the Powens console (console.powens.com), a domain (<x>-sandbox.biapi.pro
for the sandbox), a client application's client_id and client_secret with the return URL
whitelisted, and a webhook on CONNECTION_SYNCED with its secret.
License: LGPL-3.0-or-later.