Skip to main content

BLIK recurring payments (subscriptions)

BLIK recurring payments (called Recurring Payments by BLIK) let you charge a customer in subsequent periods without a BLIK code, which makes them suitable for subscriptions, plans and bills. The customer approves the recurring payment once in their banking app, and you then initiate subsequent charges from your server using its alias.

POST/api/v1_0/payments/registerComplete registration contract: parameters, checksum, and error codes.Full contract in the API Reference

How it works​

  1. Register a recurring payment - a transaction with a BLIK code and a recurring_registration object, with an amount of 0 (consent only) or an initial payment, for example for the first period. The customer sees the name and terms of the recurring payment in their banking app and, with a single confirmation, pays the initial payment and approves the recurring payment.
  2. Charges - subsequent payments with the recurring_alias field, sent by your server without a BLIK code.
  3. Result and retry - a successful charge is confirmed by an IPN. A charge declined for a temporary reason (for example insufficient funds) can be retried.
  4. Management - checking the status, cancelling, and notifications about changes (for example when the customer cancels the recurring payment in their banking app).
One recurring payments API

The recurring_registration and recurring_alias fields and the /payments/recurring/* endpoints are shared by payment methods. The currently supported method is BLIK (methods: ["blik"]), and the registration requires a BLIK code.

Recurring payment models​

You choose the model at registration. It determines who approves subsequent charges and which terms the customer accepts in their bank. dpay enables each model for your Payment Point.

ModelWho approves a chargeWhen to use it
AThe customer's bank, automatically - when the charge meets the terms accepted at registration (fixed amount, frequency, total limit, period)A fixed amount on a fixed schedule, for example a PLN 59.99 monthly plan
MThe customer - confirms every charge in the banking appVariable amounts or dates when the customer should approve each payment
ONobody - the bank charges the account without customer involvement if BLIK qualifies the charge as a merchant-initiated transaction (MIT)Continuous services with variable amounts, for example utility bills, usage fees, surcharges after a service

Model A - automatic​

  • At registration you provide the full set of terms: frequency (frequency), the amount of every charge (limit_amt), the total limit (tot_limit_amt), the first charge date (init_date) and the expiration date (expiration_date). The customer accepts them in the banking app.
  • The amount is fixed - every charge must equal limit_amt.
  • dpay rejects a charge with a different amount, one exceeding the total limit, or one sent before init_date - before it reaches BLIK.
  • The bank checks the frequency. It declines a charge that does not meet the registration terms with AUTOCONF_REQ_NOT_MET (with no_delay: false it may ask the customer to confirm instead).

Model M - customer confirmation​

  • The customer confirms every charge in the banking app, so you learn the result only after their decision - which can take up to 72 hours.
  • no_delay: true is not allowed in this model.
  • frequency, init_date and expiration_date are optional - they are sent to the bank with the invitation as information about the recurring payment.
  • The limit_amt, tot_limit_amt and is_limit_amt_fixed limits are optional and are not sent to the bank - dpay enforces them on every charge.

Model O - no customer involvement (MIT)​

  • At registration you provide the label, the alias, the terms link and optionally the expiration date. frequency and limits are prohibited in this model - their absence is how the bank recognizes model O.

  • A single charge can be at most PLN 2000 and must fall within an amount range active for your Payment Point:

    RangeAmounts
    1PLN 0 - 400.00
    2PLN 400.01 - 1000.00
    3PLN 1000.01 - 2000.00
  • dpay rejects a charge above PLN 2000 or in an inactive range (HTTP 400) before it reaches BLIK.

  • When BLIK does not qualify a charge as MIT (for example after a range has been temporarily disabled), the bank declines it with SEC_DECLINED. With no_delay: false the bank may ask the customer to confirm in the app instead.

Requirements for model O
  • Continuous services only: subscriptions, bills (electricity, gas, internet), usage fees, surcharges after a service, rides and parking. Model O is not intended for one-off purchases (online stores, food delivery).
  • Configuration at BLIK: dpay submits your Payment Point to BLIK together with the amount ranges and the materials from your request showing the registration flow (mockups, a recording or test access). BLIK reviews the flow and, after a positive review, configures the Payment Point - usually within about 3 business days.
  • Amount ranges are granted by dpay after a risk assessment. A model O registration requires at least one active range.
  • Amounts must not be split into smaller charges to fit a range.

Requirements​

  • An active Payment Point with BLIK enabled and BLIK recurring payments activated. Submit a request in the dpay panel: Payment points → service → Payment methods → BLIK - recurring payments. You choose the models (for model O also the amount ranges), describe the service, provide the terms and materials showing the consent flow, and accept the terms. We enable models A and M after reviewing the request, and model O additionally after the configuration at BLIK (see above). We will e-mail you our decision.
  • The terms of the recurring payment (terms of service or an agreement) at a stable URL - see terms link.
  • Secure server-side storage for the recurring payment alias, associated with the customer's account.
  • Access to the customer's IP address and User-Agent during registration, as required by applicable regulations.
  • A registration flow and customer communication that meet the obligations towards the customer and the BLIK requirements for the purchase flow (method name, list of banks, payment details before checkout).
Start with test mode

You can build and test the integration without a request - in test mode all models are available and the responses reproduce BLIK behavior.

Endpoint​

POST https://api-payments.dpay.pl/api/v1_0/payments/register
Content-Type: application/json

Step 1: Register a recurring payment​

Registration is a transaction with a BLIK code that carries the invitation to a recurring payment. Send it only after the customer has explicitly chosen a recurring payment on your website. The registration amount can be:

  • 0 - the customer is not charged and only approves the recurring payment,
  • more than 0 (initial payment) - for example for the first subscription period or service activation. On a single screen in the banking app, with a single PIN, the customer pays the initial payment and approves the recurring payment.
Initial payment

Combining the first payment with the consent is one step for the customer instead of two - BLIK recommends this flow because the customer will not close the app believing they have already enabled recurring payments. The initial payment is a regular BLIK payment confirmed with a PIN: the model O amount ranges and the PLN 2000 cap do not apply to it, and it does not count towards tot_limit_amt. You refund it like any other payment - see refunds.

If the customer's bank does not support BLIK recurring payments, BLIK declines the whole transaction (code ER_PAYID_UNHANDLED) - the customer does not pay the initial payment without an active recurring payment.

Request parameters​

FieldTypeRequiredDescription
transactionTypestringYes"transfers"
servicestringYesService name from the panel
valuestringYes0 - consent to the recurring payment only; more than 0 - an initial payment charged together with the consent
url_successstringYesURL after successful registration
url_failstringYesURL after failed registration
url_ipnstringNoURL for IPN notifications; without it no IPN is sent (you get the result by webhook)
checksumstringYesSHA-256 checksum
blik_codestringYes6-digit BLIK code
user_ipstringYesCustomer IP address
user_agentstringYesCustomer User-Agent header
recurring_registrationobjectYesRecurring payment terms (below)
alias_ipn_urlstringNoURL for change notifications (max. 500 characters); when omitted, notifications go to url_ipn
descriptionstringNoDescription of the registration transaction, passed to BLIK (first 35 characters). Without it, BLIK receives the recurring payment name (label)

recurring_registration object​

Which fields are required depends on the model. A prohibited field causes a validation error (HTTP 422).

FieldTypeAMODescription
labelstringYesYesYesName of the recurring payment shown to the customer in the banking app (max. 35 characters - the BLIK limit), for example "Premium plan". How to build the label: BLIK requirements
modelstringYesYesYesModel: "A", "M" or "O"
terms_urlstringYesYesYesLink to the recurring payment terms the customer accepts (max. 2048 characters) - see terms link
terms_versionstringNoNoNoVersion of the terms, for example "2026-09" (max. 64 characters)
aliasstringRecommendedRecommendedRecommendedYour recurring payment identifier (max. 128 characters), unique within the service - see the note below
methodsarrayNoNoNoMethods the customer pays the recurring payment with. Currently only ["blik"] (default)
frequencystringYesNoProhibitedFrequency: a number from 1 to 999 and a unit D (days), W (weeks), M (months) or Y (years), for example "1M", "2W", "30D"
limit_amtintegerYesNoProhibitedAmount in grosze (for example 5999 = PLN 59.99). Model A: the amount of every charge. Model M: the maximum charge amount (or the exact amount with is_limit_amt_fixed: true)
tot_limit_amtintegerYesNoProhibitedTotal limit of recurring charges in grosze (excluding the initial payment)
is_limit_amt_fixedbooleanNoNoProhibitedModel M: true - every charge must equal limit_amt, false - limit_amt is the maximum amount. In model A the amount is always fixed - omit the field or send true
init_datestringYesNo-Date of the first recurring charge (YYYY-MM-DD, today or later) - when the initial payment covers the first period, send the start of the next one. In model A a charge before this date is rejected. Ignored in model O
expiration_datestringYesNoNoExpiration date of the recurring payment (YYYY-MM-DD, after today, at most 10 years from today). Without a date it is valid until cancelled
Provide your own alias

Send your own identifier in recurring_registration.alias (for example "SUB-12345") and store it on your server, associated with the customer's account. You need it for charges, status checks and cancellation. If you omit it, dpay assigns an alias itself and returns it in the response in additionalInfo.recurring_registration.alias - but if the response does not reach you (for example after a timeout), you will not learn it. Registering a second active recurring payment with the same alias is rejected.

The terms_url field is required. It links to the document with the recurring payment terms the customer accepts at registration - terms of service, an agreement or a price list. dpay stores it together with terms_version and returns it in the recurring payment status. In a complaint, it is the evidence of the terms the customer agreed to.

  • The content behind the link must not change. When you change the terms, publish a new document at a new address (with a new terms_version) and use it for new registrations. Leave the previous document unchanged - customers registered earlier accepted its content.

  • It can be an agreement generated for a specific customer. A link to an individual document often contains an agreement identifier, checksums or a UUID. dpay accepts links of up to 2048 characters, for example:

    https://myshop.com/agreements/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08/2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae/0b5c3e2a-7f7d-4f1e-9d1c-3a2b1c0d9e8f
  • Availability - the document should open without logging in and stay available for at least 13 months after the last charge (the time the customer has to file a complaint).

Checksum generation​

The checksum is generated in the same way as for a standard payment:

sha256({service}|{SecretHash}|{value}|{url_success}|{url_fail}|{url_ipn})
Amount format in the checksum

The amount in the checksum is normalized to two decimal places - for a registration with value: 0, hash "0.00".

Example request​

cURL (model A, first month in the initial payment)​

The customer pays PLN 59.99 for October at registration, and subsequent PLN 59.99 charges follow monthly from 1 November.

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "59.99",
"url_success": "https://myshop.com/success",
"url_fail": "https://myshop.com/error",
"url_ipn": "https://myshop.com/api/ipn",
"checksum": "e3b0c44298fc1c149afb...",
"blik_code": "123456",
"user_ip": "192.168.1.100",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"description": "Premium plan",
"recurring_registration": {
"label": "Premium plan",
"alias": "SUB-1234567890",
"model": "A",
"frequency": "1M",
"limit_amt": 5999,
"tot_limit_amt": 71988,
"init_date": "2026-11-01",
"expiration_date": "2027-09-30",
"terms_url": "https://myshop.com/terms/2026-09",
"terms_version": "2026-09"
}
}'
<?php
$service = getenv('DPAY_SERVICE');
$secretHash = getenv('DPAY_SECRET_HASH');

$value = '0.00';
$urlSuccess = 'https://myshop.com/success';
$urlFail = 'https://myshop.com/error';
$urlIpn = 'https://myshop.com/api/ipn';

$checksum = hash('sha256',
$service . '|' . $secretHash . '|' . $value . '|' .
$urlSuccess . '|' . $urlFail . '|' . $urlIpn
);

// Agreement generated for the customer - the content at this address must not change
$agreementUrl = 'https://myshop.com/agreements/' . $agreement->uuid . '/' . $agreement->checksum;

$payload = json_encode([
'transactionType' => 'transfers',
'service' => $service,
'value' => 0,
'url_success' => $urlSuccess,
'url_fail' => $urlFail,
'url_ipn' => $urlIpn,
'checksum' => $checksum,
'blik_code' => $_POST['blik_code'],
'user_ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'description' => 'Energy bill',
'recurring_registration' => [
'label' => 'Energy - contract 2026/123',
'alias' => 'ENERGY-' . $customerId,
'model' => 'O',
'terms_url' => $agreementUrl,
'terms_version' => '2026/123',
],
]);

$ch = curl_init('https://api-payments.dpay.pl/api/v1_0/payments/register');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);

In model M the registration object can contain only the required fields, or additionally information for the customer and limits enforced by dpay:

{
"label": "Account top-ups",
"alias": "TOPUP-1234567890",
"model": "M",
"frequency": "1M",
"limit_amt": 20000,
"is_limit_amt_fixed": false,
"terms_url": "https://myshop.com/terms/2026-09"
}

API response​

{
"error": false,
"msg": "Internal processing",
"status": true,
"transactionId": "abc-def-123-456",
"additionalInfo": {
"recurring_registration": {
"alias": "SUB-1234567890",
"methods": ["blik"]
}
}
}

The status field is a boolean (true/false). Store transactionId (the identifier of the registration transaction) and additionalInfo.recurring_registration.alias - the alias you send in the recurring_alias field with charges. After this response, the customer approves the recurring payment in the banking app.

If the registration is declined immediately, the response is {"error": true, "msg": "Transaction canceled", "status": false, ...} and the decline code is in additionalInfo.error.

Registration result​

  • Approval - you receive a change notification with change_type: "ALIAS_REGISTER", and the registration transaction moves to paid (you receive a standard IPN for it with the initial payment amount or 0). From now on you can charge the customer.
  • Rejection or no approval - the recurring payment is not activated, the initial payment is not charged, and the registration transaction moves to expired (no IPN). You can check the current state with the status endpoint.

Step 2: Create a recurring charge​

Your server initiates subsequent payments without a BLIK code and without customer involvement (in model M the customer confirms them in the banking app).

Request parameters​

FieldTypeRequiredDescription
transactionTypestringYes"transfers"
servicestringYesService name from the panel
valuestringYesCharge amount in PLN (must be greater than 0)
url_successstringYesURL after successful payment
url_failstringYesURL after failed payment
url_ipnstringNoURL for IPN notifications; without it no IPN is sent (you get the result by webhook)
checksumstringYesSHA-256 checksum with the charge amount and the alias at the end (see below)
recurring_aliasstringYesRecurring payment alias from the registration (your recurring_registration.alias, also returned in additionalInfo.recurring_registration.alias; max. 128 characters)
no_delaybooleanNoDefaults to true - the bank responds immediately without waiting for the customer. false - the bank may hold the charge for up to 72 hours and ask the customer to confirm it in the app; requires prior approval by dpay. In model M omit this field - true is rejected
descriptionstringNoCharge description, for example "Premium plan 10/2026". The bank shows it to the customer in the account history - BLIK accepts the first 35 characters. Without it, the customer sees the recurring payment name (label)
user_ipstringNoIP address, optional for server-to-server charges
user_agentstringNoUser-Agent, optional for server-to-server charges
Mutually exclusive fields

Do not send blik_code, blik_alias, register_blik_alias, or recurring_registration with a charge. The recurring_alias field cannot be combined with them. The recurring_registration and recurring_alias fields work only with transactionType: "transfers".

Charge checksum​

sha256({service}|{SecretHash}|{value}|{url_success}|{url_fail}|{url_ipn}|{recurring_alias})

The alias at the end binds the charge to a specific customer: a checksum without the alias or with another alias is rejected (Invalid checksum). Without url_ipn, keep the empty segment: ...|{url_fail}||{recurring_alias}.

Validation before the charge reaches BLIK​

Before a charge reaches BLIK, dpay checks:

  • the recurring payment - it must be registered for your service and active (dpay checks its current status, for example whether the customer has cancelled it in the banking app),
  • model A - the amount equals limit_amt, successful charges plus the current one do not exceed tot_limit_amt, and the charge is not earlier than init_date,
  • model M - the limits provided at registration (if any),
  • model O - the amount is at most PLN 2000 and within an active range.

A violation returns HTTP 400 with a description in message (see error handling). Nothing is sent to BLIK and the customer is not charged.

Example request​

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "59.99",
"url_success": "https://myshop.com/success",
"url_fail": "https://myshop.com/error",
"url_ipn": "https://myshop.com/api/ipn",
"checksum": "e3b0c44298fc1c149afb...",
"recurring_alias": "SUB-1234567890",
"description": "Premium plan 11/2026"
}'

API response​

{
"error": false,
"msg": "Internal processing",
"status": true,
"transactionId": "abc-def-123-456"
}

The charge has been passed to the bank. Store transactionId - you need it to check the status and to retry the charge if necessary.

If the charge is declined immediately, the response is {"error": true, "msg": "Transaction canceled", "status": false, ...} and the decline code is in additionalInfo.error (with a description in additionalInfo.error_description).

Charge result​

ResultTransaction statusNotification
Charge succeededpaidIPN transfer to url_ipn - as for any payment
Charge declinedexpiredNo IPN

With no_delay: true the result is usually known within a few seconds. In model M and with no_delay: false the bank may wait for the customer for up to 72 hours.

Declined transactions do not generate an IPN, so check the status of charges whose IPN has not arrived in the expected time - by querying the transaction status. After an expired status you can try to retry the charge - the response tells you whether the decline code allows it.

Decline codes​

The most common decline codes (in additionalInfo.error of the registration or charge response, or in the retry endpoint response):

CodeMeaningWhat to do
INSUFFICIENT_FUNDSInsufficient funds in the customer's accountRetry the charge or try a new one later
LIMIT_EXCEEDEDTransaction limit exceeded at the customer's bankRetry the charge or inform the customer
SYSTEM_ERROR, GENERAL_ERROR, ISS_OUTOFSERVICETemporary technical problem at the bank or BLIKRetry the charge
USER_DECLINEDThe customer rejected the charge in the banking appDo not retry - contact the customer
TIMEOUTThe customer did not confirm the charge in timeDo not retry - create a new charge
AUTOCONF_REQ_NOT_METModel A: the charge does not match the registration terms (amount, date, frequency)Check the amount and the charge schedule
SEC_DECLINEDModel O: BLIK did not qualify the charge as MITContact dpay support; the customer can pay with a BLIK code
ALIAS_DECLINEDThe recurring payment was declined or cancelledOffer the customer a new registration
ALIAS_NOT_FOUNDThe recurring payment does not exist at BLIKOffer the customer a new registration
RECURRING_NOT_ENABLEDThe recurring payment model is not enabled for your Payment PointContact dpay support
AMOUNT_LIMIT_EXCEEDEDThe amount exceeds the maximum charge amountCheck the amount
ER_PAYID_UNHANDLEDRegistration: the customer's bank does not support BLIK recurring payments, the whole transaction is declinedOffer the customer another payment method
WRONG_TICKET_BLOCKEDRegistration: BLIK code payments temporarily stopped after a series of wrong codes (issued by dpay)Ask the customer to try again later - see protection against Wrong Ticket attacks
INTERNAL_ERRORUnknown result (communication error)Check the transaction status before trying again

Retry a declined charge​

BLIK allows retrying a declined charge with the same transaction identifier, which rules out charging the customer twice. Use the dedicated endpoint for this - do not resend the same charge through payments/register, because that creates a new transaction.

Retry rules:

  • only a charge declined with INSUFFICIENT_FUNDS, LIMIT_EXCEEDED, SYSTEM_ERROR, GENERAL_ERROR or ISS_OUTOFSERVICE can be retried,
  • at most 3 retries within 5 minutes of creating the original charge,
  • after that, create a new charge (step 2),
  • dpay checks the recurring payment and the amount in the same way as for the original charge.
POST/api/v1_0/payments/recurring/retryComplete contract for retrying a declined recurring charge.Full contract in the API Reference

Endpoint​

POST https://api-payments.dpay.pl/api/v1_0/payments/recurring/retry
Content-Type: application/json

Parameters​

FieldTypeRequiredDescription
servicestringYesService name from the panel
transaction_idstringYestransactionId from the charge response (max. 64 characters)
checksumstringYesSHA-256 checksum

Checksum generation​

sha256({service}|{SecretHash}|{transaction_id})

Example request​

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/recurring/retry \
-H "Content-Type: application/json" \
-d '{
"service": "abc123",
"transaction_id": "abc-def-123-456",
"checksum": "9c1b4f2e7a..."
}'

Response - retry sent​

{
"status": "success",
"data": {
"transactionId": "abc-def-123-456",
"retry": {
"status": "pending",
"count": 1
}
}
}
FieldDescription
retry.statuspending - the retry reached the bank and the result arrives as for a charge (an IPN on success, expired on decline). failed - BLIK rejected the retry immediately and the transaction stays expired
retry.countRetry number (1-3)
retry.error, retry.error_descriptionDecline code and description - only with failed

Response - retry not allowed​

When a retry is not allowed, the API returns HTTP 400 and nothing is sent to BLIK:

{
"status": "failed",
"message": "Recurring charge cannot be retried (DECLINE_NOT_RETRYABLE).",
"errors": {
"retry": "DECLINE_NOT_RETRYABLE",
"decline_reason": "USER_DECLINED"
}
}
Code in errors.retryMeaningWhat to do
PAYMENT_NOT_DECLINEDThe charge was not declined - it is in progress or succeededWait for the result
DECLINE_NOT_RETRYABLEThe decline code (in errors.decline_reason) does not allow a retryFollow the decline code
RETRY_LIMIT_REACHEDThe retry limit has been usedCreate a new charge
RETRY_WINDOW_EXPIRED5 minutes have passed since the original chargeCreate a new charge
ALIAS_NOT_AVAILABLEThe recurring payment is no longer activeOffer the customer a new registration
RECURRING_NOT_ENABLEDThe recurring payment model is not enabled for your Payment PointContact dpay support
PAYMENT_NOT_FOUNDThe charge was not found in the BLIK systemCheck transaction_id
Service unavailableTemporary loss of connection to the BLIK systemCall the retry endpoint again shortly

Other errors use a key matching the field: errors.transaction_id (Transaction not found, Not a recurring charge, Transaction already paid, Transaction not retryable), errors.recurring_alias (Alias not found, Alias not active), and amount validation messages as for a charge.

Check the recurring payment status​

Retrieve the current status of a recurring payment, together with the registration terms, from the status endpoint:

POST https://api-payments.dpay.pl/api/v1_0/payments/recurring/status
Content-Type: application/json
POST/api/v1_0/payments/recurring/statusComplete contract: recurring payment status and registration terms.Full contract in the API Reference

Parameters​

FieldTypeRequiredDescription
servicestringYesService name from the panel
aliasstringYesRecurring payment alias (max. 128 characters)
checksumstringYesSHA-256 checksum

Checksum generation​

sha256({service}|{SecretHash}|{alias})

Example request​

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/recurring/status \
-H "Content-Type: application/json" \
-d '{
"service": "abc123",
"alias": "SUB-1234567890",
"checksum": "f5a3b2c1d0..."
}'

Response​

{
"status": "success",
"data": {
"alias": "SUB-1234567890",
"method": "blik",
"status": "ACTIVE",
"expiration_date": "2027-09-30",
"registration": {
"transaction_id": "abc-def-123-456",
"label": "Premium plan",
"model": "A",
"frequency": "1M",
"limit_amt": 5999,
"tot_limit_amt": 71988,
"is_limit_amt_fixed": true,
"init_date": "2026-11-01",
"terms_url": "https://myshop.com/terms/2026-09",
"terms_version": "2026-09",
"registered_at": "2026-09-26T12:00:00+02:00"
}
}
}
FieldTypeDescription
aliasstringRecurring payment alias
methodstringPayment method - "blik"
statusstring|nullCurrent status: "ACTIVE" (can be charged), "INACTIVE" (registration not approved yet), "UNREGISTERED" (cancelled by you or the customer), "EXPIRED" (the expiration date has passed), "DECLINED" (registration declined)
expiration_datestring|nullExpiration date
registrationobjectRegistration terms: transaction_id of the registration transaction, label, model, frequency, limits, init_date, terms_url, terms_version and the registration date (registered_at). Fields not provided at registration are null

When no recurring payment with this alias was registered for your service, the API returns HTTP 400 with errors.alias: Alias not found.

Cancel a recurring payment​

Cancel the recurring payment when the customer cancels the service on your website, or when you replace it with a new one (for example when the plan changes or it is about to expire - its validity cannot be extended). In that case, register the new recurring payment first and cancel the old one after the new one becomes active. Charges with a cancelled alias are rejected.

POST/api/v1_0/payments/recurring/cancelComplete contract for cancelling a recurring payment.Full contract in the API Reference

Endpoint​

POST https://api-payments.dpay.pl/api/v1_0/payments/recurring/cancel
Content-Type: application/json

Parameters​

FieldTypeRequiredDescription
servicestringYesService name from the panel
aliasstringYesRecurring payment alias (max. 128 characters)
reasonstringNoReason for cancelling (max. 255 characters)
checksumstringYesSHA-256 checksum

Checksum generation​

sha256({service}|{SecretHash}|{alias}|cancel)

The constant cancel at the end distinguishes cancellation from a status query - a status checksum cannot cancel the recurring payment.

Example request​

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/recurring/cancel \
-H "Content-Type: application/json" \
-d '{
"service": "abc123",
"alias": "SUB-1234567890",
"reason": "Plan cancelled",
"checksum": "f5a3b2c1d0..."
}'

Response​

{
"status": "success",
"data": {
"alias": "SUB-1234567890",
"status": "UNREGISTERED"
}
}

Errors are returned with HTTP 400. errors.cancel contains Alias not found (no active recurring payment with this alias), a BLIK error code, or Service unavailable (try again shortly).

Recurring payment change notifications​

On every status change of a recurring payment, dpay.pl sends an alias_update notification to alias_ipn_url (or url_ipn when it was not provided). The notification format, signature verification and retry schedule are the same as for BLIK OneClick - see alias notifications. For a recurring payment, alias_type is "PAYID".

change_typeMeaningWhat to do
ALIAS_REGISTERThe customer approved the recurring payment - it is activeMark it as active and schedule charges
ALIAS_UPDATERecurring payment data changedRefresh the status with the status endpoint
ALIAS_UNREGISTERThe recurring payment was cancelled - by you or by the customer in the banking appStop charging; if the customer has not cancelled the service, offer a new recurring payment
ALIAS_EXPIREDThe expiration date has passedStop charging and offer a new recurring payment
ALIAS_DECLINEDThe registration was declinedDo not charge the customer
Identifying the recurring payment in a notification

Correlate the notification by the id field - it is the transactionId of the registration transaction. The alias_key field contains the identifier in the BLIK system, which is not equal to your alias (recurring_registration.alias).

Obligations towards the customer​

BLIK recurring payments are subject to BLIK complaint rules - the customer can dispute a charge at their bank up to 13 months after authorization. The most common grounds are missing consent, unclear terms, a charge after cancellation, and missing notice before a payment. Therefore:

  • Consent - send the registration only after an explicit action by the customer on your website. Show the terms and the price clearly, separately from other content.
  • Terms - in terms_url, send the document the customer actually accepted, and do not change its content - see terms link.
  • Purchase flow - the name "BLIK Recurring Payments", the list of supporting banks and the payment details before the code - see BLIK requirements.
  • Label - label should clearly identify the service. The customer sees it in the banking app in the list of their recurring payments - see how to build the label.
  • Notice - inform the customer at least 14 days before the first paid period, 30 days before a renewal, and 14 days before a price change.
  • Charges - charge only within the terms agreed with the customer. Use the retry endpoint to retry declines.
  • Cancellation - make cancellation easy on your website. After a cancellation, do not charge the customer and cancel the recurring payment.
  • Evidence - keep, for at least 13 months after the last charge: proof of consent (date, IP address, version of the terms), correspondence with the customer, and the cancellation history. dpay will ask for them in a complaint.

Complaints and fraud reports​

A customer can dispute a charge with their bank (a complaint), and a bank can report a payment as fraudulent. The case appears in the panel under Disputes, and you receive an e-mail with the response deadline:

CaseYour deadlinedpay's deadline towards BLIK
Complaint5 calendar days15 days - no response means the complaint is upheld
Fraud report1 business daythe next business day
  • dpay fills in the consent record from the registration (date, terms, the customer's IP address and browser), the charge history and the status of the recurring payment. You provide the consent screen and the notices sent to the customer before charging - without them, defending against a lack-of-consent or lack-of-notice claim is very difficult.
  • An upheld complaint means a correction: the amount goes back to the customer's bank and is deducted from your balance. For fraud, if the funds are still in your balance, we return them to the bank with a good-faith correction.
  • After a correction, dpay can request arbitration within 30 days. A won arbitration returns the correction to your balance.

Fees (net, deducted from the balance):

ItemAmount
Handling of each complaintPLN 50
Upheld complaintPLN 100
Lost arbitrationPLN 1000

VAT is added to these amounts. The handling fee applies to every complaint, whatever the outcome. The fees for an upheld complaint and a lost arbitration (they include the BLIK fees) apply only when you lose - there are none when you win.

Testing in test mode​

In test mode, you can test the whole recurring payments integration - registration, charges, declines, retries, status, cancellation and notifications - with no real money. dpay does not connect to BLIK then: the responses come from our simulator, which reproduces the BLIK behavior verified in tests on the BLIK test environment and in the certification.

  • Turn on the service's Test mode in the panel - see test environment.
  • No request is needed: in test mode all models (A, M and O), all model O amount ranges and no_delay: false are available. Field validation, checksums, the PLN 2,000 limit and the model A limits work as in production.
  • Requests, responses, IPNs and webhooks have the same format as in production. Webhooks from test mode have livemode: false.
  • A recurring payment registered in test mode works only in test mode. After you turn test mode off, a charge of its alias returns Alias not found - register your customers again in production.
  • The times in the tables are approximate - results arrive asynchronously, as from the bank.

Registration​

The registration result depends on the code in blik_code:

BLIK codeResult
777200Success - the transaction is paid after about 5 s, and after another 5 s or so the recurring payment is active
777201Success after a longer wait for the customer (about 10 s)
777400The customer declined the payment in the banking app (USER_DECLINED)
777401The customer did not confirm in time (TIMEOUT)
777402Insufficient funds for the initial fee (INSUFFICIENT_FUNDS)
777500System error (SYSTEM_ERROR)
777000The customer's bank does not support recurring payments (ER_PAYID_UNHANDLED) - use it to test the error screen required by BLIK
Any other codeSuccess, as for 777200
  • Success - the registration transaction moves to paid (IPN transfer, webhook payment.succeeded), and then the recurring payment moves to ACTIVE: you receive an alias_update notification with change_type: "ALIAS_REGISTER" and a recurring_payment.activated webhook. In test mode, the alias_key field has the form DPAY.PAYID.0.....
  • Decline - an immediate response with the code in additionalInfo.error, the transaction is expired, the recurring payment is DECLINED and you receive a recurring_payment.declined webhook.

Charges​

A charge responds immediately with Internal processing, and the result depends on the amount - just like the BLIK test environment, which picks the bank response by amount:

AmountResultRetry
PLN 60.73Declined with INSUFFICIENT_FUNDSAllowed, succeeds
PLN 60.74Declined with LIMIT_EXCEEDEDAllowed, succeeds
PLN 60.78Declined with SYSTEM_ERRORAllowed, succeeds
PLN 60.79Declined with INSUFFICIENT_FUNDS on every attemptAllowed - the fourth retry returns RETRY_LIMIT_REACHED
PLN 60.75Declined with TIMEOUTNot allowed (DECLINE_NOT_RETRYABLE)
PLN 60.76Declined with SEC_DECLINED - the charge was not qualified as MITNot allowed
PLN 60.77Declined with USER_DECLINED - the customer declined the charge in the appNot allowed
PLN 60.65Success after about 60 s - as after the customer's confirmation (model M, no_delay: false)-
PLN 2.37Success, and about 5 s later the customer removes the recurring payment in the banking app-
Any other amountSuccess after about 5 s-
  • Success - status paid, IPN transfer and a payment.succeeded webhook.
  • Decline - status expired, no IPN, a payment.failed webhook with the decline code (for example failure.code: "insufficient_funds", failure.provider_code: "INSUFFICIENT_FUNDS").
  • PLN 2.37 - after the successful charge, the recurring payment moves to UNREGISTERED: you receive alias_update with change_type: "ALIAS_UNREGISTER" and a recurring_payment.canceled webhook with canceled_by: "customer". The next charge returns Alias not active.
  • The amount must pass the model validation. In model A, a charge must equal limit_amt - to test a decline, register the payment with limit_amt: 6073.

Retries, status, cancellation and expiration​

  • Retry - the same rules and codes as in production: only the listed declines, at most 3 retries within 5 minutes of the charge. The retry result arrives after about 5 s.
  • Status - INACTIVE until activation, then ACTIVE, UNREGISTERED, EXPIRED or DECLINED.
  • Cancellation - status UNREGISTERED immediately and a recurring_payment.canceled webhook with canceled_by: "merchant". As in BLIK, no alias_update follows a cancellation by you.
  • Expiration - a recurring payment with expiration_date expires after that date. You get the EXPIRED status, alias_update with change_type: "ALIAS_EXPIRED" and a recurring_payment.expired webhook on the first charge or status request after that date.
  • Refunds - refund successful test charges like any payment in test mode.

Rate limits​

EndpointLimit
POST /payments/register120 requests/min
POST /payments/recurring/status60 requests/min
POST /payments/recurring/retry30 requests/min
POST /payments/recurring/cancel30 requests/min

Error handling​

Field validation errors (for example recurring_registration.frequency in model O, a missing recurring_registration.init_date in model A, or a missing recurring_registration.terms_url) are returned with HTTP 422 and a list of fields in errors. Business validation errors are returned with HTTP 400 in the format {"status": "failed", "message": "...", "errors": {...}}. Common messages:

MessageCause
Recurring payments are not enabled for this service.Recurring payments are not enabled for the service
BLIK Recurring model M is not enabled for this service.The selected model is not active for the service
BLIK Recurring model O requires at least one active amount range for this service. Contact dpay support.Model O without an active amount range
An active recurring payment with this alias already exists.An active recurring payment with this alias already exists (errors.alias: Alias already active)
Recurring charge requires value > 0.Charge with an amount of 0
No recurring payment registered for this alias.No recurring payment with this alias was registered for the service (errors.recurring_alias: Alias not found)
Recurring payment is not active (status: ...).The recurring payment expired, was cancelled, or the registration was not approved (errors.recurring_alias: Alias not active)
BLIK Recurring model M does not allow no_delay=true: ...no_delay: true for a model M recurring payment
Setting no_delay=false requires merchant approval (...). Contact support.no_delay: false used without dpay approval
BLIK Recurring: payment amount must be exactly ... PLN (fixed limit).Amount different from the fixed registration amount (model A, or model M with is_limit_amt_fixed: true)
BLIK Recurring: payment amount ... PLN exceeds single payment limit ... PLN.Model M: single charge limit exceeded
BLIK Recurring: total spent ... PLN + requested ... PLN exceeds total limit ... PLN.Total limit tot_limit_amt exceeded
BLIK Recurring model A: the first payment is scheduled for ....Model A: charge before init_date
BLIK Recurring model O: payment amount ... PLN exceeds the maximum of 2000.00 PLN.Model O: amount above PLN 2000
BLIK Recurring model O: payment amount ... PLN is in the ... range, which is not active for this service. Contact dpay support.Model O: amount in an inactive range

Declines by the bank and the BLIK system are described in decline codes.

tip

If the recurring payment has expired or the customer has cancelled it in the banking app, offer a new registration (step 1) the next time you interact with the customer, for example when the subscription renews.