Skip to main content

Payment and account confirmation

Two session purposes that answer one question: does this bank account really belong to this person or company?

Session purposeWhat it doesWhat it proves
activation_paymentInitiates a transfer from the customer's account (PIS)The customer can access the account and ordered a transfer from it
account_ownershipSigns the customer in to their bank (AIS)The account holder's name and account number

The purposes are independent - use one or both. Together they give the strongest evidence: the money left the account, and the account belongs to the right person.

You do not pass a provider

For both purposes you do not send the provider field - the hub picks the provider based on the purpose. A provider change on our side therefore requires no change on yours.

Step 1: fetch the list of banks

We return only the banks available at that moment - switched off and temporarily unavailable ones are already filtered out. The list changes rarely, so cache it.

GET/api/v1/banksBanks available for a given session purpose.Full contract in the API Reference
curl "https://hub.dpay.pl/api/v1/banks?purpose=activation_payment&country=PL" \
-H "Authorization: Bearer deid_live_xxx"

Bank coverage can differ between purposes, so ask for the list for the purpose you will actually use.

Step 2: show both banking categories

Banks differ in where they serve businesses. Some keep company accounts in ordinary online banking, others run a separate corporate system. This cannot be inferred from the customer's legal form - show both categories and let them choose:

metadata.account_variantLabel for the customerWhen
retailPersonal bankingOrdinary sign-in; in many banks company accounts too
companyBusiness bankingA separate system for companies

Defaults to retail. The wrong variant means the customer cannot see their account.

Verification payment (activation_payment)

curl -X POST https://hub.dpay.pl/api/v1/verifications \
-H "Authorization: Bearer deid_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"purpose": "activation_payment",
"external_id": "customer-12345",
"redirect_url": "https://your-app.example/return",
"metadata": {
"bank_id": "mbank",
"account_variant": "retail",
"amount": "10.00",
"title": "Activation fee 12345",
"recipient_account_number": "PL61109010140000071219812874",
"recipient_name_address": "Your Company Ltd Warsaw",
"expected_iban": "PL27114020040000300201355387"
}
}'

Payment parameters

metadata fieldRequiredDescription
bank_idyesFrom the list of banks
amountyesAmount in PLN, format d.dd
titleyesTransfer title, 5-140 characters
recipient_account_numberyesRecipient account
recipient_name_addressyesRecipient name and address
account_variantnoretail (default) or company
expected_ibannoThe account the customer must pay from - see below
Non-ASCII characters in the title and recipient name

These values are passed to the provider in HTTP headers, which cannot carry non-ASCII characters. The hub encodes them for you - send plain text. Just mind the 5-140 character limit on the title, and that only letters, digits, space and _ - . : / , + are allowed.

Restricting the sender account

expected_iban states which account the customer should pay from. It works in two stages:

  1. Before the payment - some banks will restrict the choice to that account. Which ones, you can tell from the supports_sender_account flag in the bank list.
  2. After the payment - we always compare the sender account against expected_iban and return the verdict.

Treat stage 1 as a convenience for the customer, not as a control mechanism - the control is stage 2.

Account owner confirmation (account_ownership)

curl -X POST https://hub.dpay.pl/api/v1/verifications \
-H "Authorization: Bearer deid_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"purpose": "account_ownership",
"external_id": "customer-12345",
"redirect_url": "https://your-app.example/return",
"metadata": {
"bank_id": "mbank",
"account_variant": "retail",
"expected_iban": "PL27114020040000300201355387"
}
}'

The customer signs in to their bank and consents to a one-off read of the account data. The consent is single-use - we never reuse it.

Results

Fetch the result once the session finishes.

GET/api/v1/verifications/{id}/resultSession result: payment state or account owner data.Full contract in the API Reference

activation_payment

verified_data fieldDescription
payment_statusinitiated, pending, done, rejected, cancelled
sender_account_last_fourLast four characters of the sender account
iban_matchVerdict of the comparison against expected_iban
amount, bank_idAmount and bank

account_ownership

verified_data fieldDescription
name_addressAccount holder name and address exactly as the bank returned them
full_name, first_name, last_name, addressSplit best-effort; name_address is the source value
account_number_last4Last four characters of the account number
iban_matchVerdict of the comparison against expected_iban
consent_expires_atConsent expiry
null is not false

iban_match has three possible values. true means match, false means a real mismatch, and null means "not determined" - you did not pass expected_iban, or the bank did not return the account number. Treating null as false produces false rejections.

The full account number never leaves the hub. You receive the comparison verdict and the last four characters - enough for the customer to recognise their account.

Matching the account holder name

name_address is a single joined string: name and address together, in a format that depends on the bank. The same company appears written in many ways, and a street name can look like a surname. If you compare it against your own data, do not rely on a literal comparison.

Session purpose - consequences

Both purposes behave the same way:

  • Session TTL: 60 minutes - signing in to a bank and authorising a transfer take longer than scanning a document
  • Available modes: single only - one payment and one sign-in
  • The provider field: you do not send it

Timing and flow

  • Time: from a few seconds to a few hours
  • Payment confirmation: arrives with very different delays, depending on the bank

Treat any non-terminal state as "still waiting", not as a failure. Set up webhooks and keep status polling as a safety net for undelivered notifications.