PSD2 promised one API for every bank in Europe. What we got is one spec family and a few hundred interpretations of it. After five years of building and running account-information and payment-initiation connectors for clients across the Baltics, the pattern is clear: the first bank takes a week, the fifth takes a day, and then the next one unexpectedly takes a month.
The consent flow is where projects stall
The API calls are the easy part. The hard part is the consent lifecycle - how a customer authorizes your access, how long that authorization lives, and what happens when it expires at 03:00 on a Sunday.
- Some banks issue 90-day consents and renew silently; others force full re-authentication.
- Sandbox consent flows routinely differ from production ones. Budget for a second integration pass after go-live.
- Error taxonomies are inconsistent: the same expired consent can surface as
401,403, or - in one memorable case - a200with an empty transaction list.
That last one deserves emphasis: an empty response is not the same as a successful response. If your reconciliation depends on a transaction feed, treat "no transactions" as a condition to verify, not a fact to record.
What we standardize on
Every connector we ship now runs behind the same internal interface, with per-bank adapters kept deliberately thin. The same discipline applies on the payment-initiation side - one canonical payment object, mapped explicitly to each bank's dialect:
The adapter handles authentication quirks and pagination dialects; everything else - retries, idempotency, alerting, consent-expiry warnings - lives in the shared layer, written once and tested once.
If a bank integration needs custom retry logic, that logic belongs in the shared layer with a feature flag - never in the adapter. Adapters rot; shared layers get maintained.
The result is boring, and boring is the goal. A new bank is a new adapter, a fixture file recorded from its sandbox, and a checklist - not a new project.