Payment and account confirmation
Two session purposes that answer one question: does this bank account really belong to this person or company?
| Session purpose | What it does | What it proves |
|---|---|---|
activation_payment | Initiates a transfer from the customer's account (PIS) | The customer can access the account and ordered a transfer from it |
account_ownership | Signs 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.
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_variant | Label for the customer | When |
|---|---|---|
retail | Personal banking | Ordinary sign-in; in many banks company accounts too |
company | Business banking | A 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 field | Required | Description |
|---|---|---|
bank_id | yes | From the list of banks |
amount | yes | Amount in PLN, format d.dd |
title | yes | Transfer title, 5-140 characters |
recipient_account_number | yes | Recipient account |
recipient_name_address | yes | Recipient name and address |
account_variant | no | retail (default) or company |
expected_iban | no | The account the customer must pay from - see below |
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:
- Before the payment - some banks will restrict the choice to that account. Which ones, you can tell from the
supports_sender_accountflag in the bank list. - After the payment - we always compare the sender account against
expected_ibanand 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 field | Description |
|---|---|
payment_status | initiated, pending, done, rejected, cancelled |
sender_account_last_four | Last four characters of the sender account |
iban_match | Verdict of the comparison against expected_iban |
amount, bank_id | Amount and bank |
account_ownership
verified_data field | Description |
|---|---|
name_address | Account holder name and address exactly as the bank returned them |
full_name, first_name, last_name, address | Split best-effort; name_address is the source value |
account_number_last4 | Last four characters of the account number |
iban_match | Verdict of the comparison against expected_iban |
consent_expires_at | Consent expiry |
null is not falseiban_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:
singleonly - one payment and one sign-in - The
providerfield: 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.