Skip to main content

BLIK Level 0

BLIK Level 0 is a direct integration in which the customer enters a six-digit BLIK code on your website without being redirected to an external payment gateway. This provides a smoother user experience.

How BLIK Level 0 works

Unlike the standard Simple Gateway integration, the customer is not redirected. The entire process takes place on your website.

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

Requirements

  • An active Payment Point with BLIK enabled
  • A form on your website for entering the BLIK code
  • Access to the customer's IP address and User-Agent, as required by applicable regulations

Endpoint

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

The payment registration endpoint accepts up to 120 requests per minute.

Request parameters

The request is the same as for standard payment registration, with additional BLIK-specific fields:

FieldTypeRequiredDescription
transactionTypestringYes"transfers"
servicestringYesService name from the panel
valuestringYesAmount in PLN
url_successstringYesURL used after a successful payment
url_failstringYesURL used after a failed payment
url_ipnstringYesURL for IPN notifications
checksumstringYesSHA-256 checksum
blik_codestringYesSix-digit BLIK code
user_ipstringYesCustomer's IP address
user_agentstringYesCustomer's User-Agent header

Checksum generation

Generate the checksum exactly as for a standard payment:

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

Example request

cURL

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "29.99",
"url_success": "https://myshop.example/success",
"url_fail": "https://myshop.example/error",
"url_ipn": "https://myshop.example/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"
}'

PHP

<?php
$service = getenv('DPAY_SERVICE');
$secretHash = getenv('DPAY_SECRET_HASH');

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

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

$payload = json_encode([
'transactionType' => 'transfers',
'service' => $service,
'value' => $value,
'url_success' => $urlSuccess,
'url_fail' => $urlFail,
'url_ipn' => $urlIpn,
'checksum' => $checksum,
'blik_code' => $_POST['blik_code'], // Code submitted through the form
'user_ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
]);

$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);

JavaScript (Node.js / Express)

const crypto = require('crypto');
const axios = require('axios');

async function processBlikPayment(req, res) {
const service = process.env.DPAY_SERVICE;
const secretHash = process.env.DPAY_SECRET_HASH;

const value = '29.99';
const urlSuccess = 'https://myshop.example/success';
const urlFail = 'https://myshop.example/error';
const urlIpn = 'https://myshop.example/api/ipn';

const checksum = crypto
.createHash('sha256')
.update(`${service}|${secretHash}|${value}|${urlSuccess}|${urlFail}|${urlIpn}`)
.digest('hex');

const response = await axios.post(
'https://api-payments.dpay.pl/api/v1_0/payments/register',
{
transactionType: 'transfers',
service,
value,
url_success: urlSuccess,
url_fail: urlFail,
url_ipn: urlIpn,
checksum,
blik_code: req.body.blik_code,
user_ip: req.ip,
user_agent: req.headers['user-agent'],
}
);

return response.data;
}

API response

Success: payment awaiting confirmation

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

The status field is a Boolean (true or false). After receiving this response, the customer must confirm the transaction in their banking app. You receive the payment result through IPN.

Check transaction status

In addition to waiting for an IPN notification, you can poll the dpay API for the current transaction status. This is useful when you want to update the user interface in real time.

Endpoint

POST https://panel.dpay.pl/api/v1/pbl/details
Content-Type: application/json

Parameters

FieldTypeDescription
servicestringService name from the panel
transaction_idstringTransaction ID returned during registration
checksumstringSHA-256 checksum

Checksum generation

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

Example response

The response contains the top-level status: "success" field and a transaction object. The most important fields are shown below; see Transaction Details in the API Reference for the complete response:

{
"status": "success",
"transaction": {
"id": "abc-def-123-456",
"status": "paid",
"value": "29.99",
"creation_date": "2026-03-15 12:00:00"
}
}

Possible statuses

StatusDescription
createdTransaction created and awaiting confirmation
processingTransaction is being processed
paidPayment completed successfully
capturedFunds captured; final status for a pre-authorized payment
expiredTransaction canceled or expired
tip

We recommend polling every 2–3 seconds for no more than 2 minutes. The BLIK code expires after that time.

The complete endpoint documentation is available in Transaction Details in the API Reference.

Error: payment declined

{
"error": true,
"msg": "Transaction canceled",
"status": false,
"transactionId": "42191111-A7AE-392E-8C09-7965C1DC6B0B",
"additionalInfo": {
"error": "USER_DECLINED",
"error_description": "User declined the transaction"
}
}

Error handling

The error code is returned in additionalInfo.error, with an optional description in additionalInfo.error_description.

In test mode (sandbox), the following simulation codes are used:

Error codeDescriptionAction
DECLINETransaction declinedAsk the customer to retry
EXPIRED_CARDPayment instrument expiredInform the customer
INSUFFICIENT_FUNDSInsufficient fundsInform the customer
USER_DECLINEDCustomer declined the transaction in their banking appAsk the customer to retry
TIMEOUTTimed out while waiting for confirmationAsk for a new code
SYSTEM_ERRORSystem errorRetry later

In production, additionalInfo.error contains the raw BLIK decline code passed through without modification, or INTERNAL_ERROR for an internal error. Treat the list as open-ended and handle unknown codes as a generic payment decline.

BLIK form example (frontend)

<form id="blik-form">
<label for="blik-code">BLIK code</label>
<input
type="text"
id="blik-code"
name="blik_code"
maxlength="6"
pattern="[0-9]{6}"
inputmode="numeric"
placeholder="______"
required
/>
<button type="submit">Pay</button>
</form>

<div id="blik-status" style="display: none;">
<p>Confirm the payment in your banking app...</p>
</div>

<script>
document.getElementById('blik-form').addEventListener('submit', async (e) => {
e.preventDefault();

const blikCode = document.getElementById('blik-code').value;

// Show the waiting message
document.getElementById('blik-form').style.display = 'none';
document.getElementById('blik-status').style.display = 'block';

const response = await fetch('/api/pay/blik', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ blik_code: blikCode }),
});

const result = await response.json();

if (result.error) {
alert('Error: ' + result.msg);
document.getElementById('blik-form').style.display = 'block';
document.getElementById('blik-status').style.display = 'none';
}
// Wait for the IPN; the payment status changes after confirmation
});
</script>

Testing in sandbox mode

In test mode, you can use special BLIK codes that simulate different scenarios without contacting the BLIK provider:

BLIK codeScenario
777200Payment succeeds
777201Payment succeeds after a longer wait
777400Customer declines in their banking app
777401Timeout with no confirmation
777402Insufficient funds
777500System error

The complete list of test data and details is available in Test environment.

BLIK code validation

A BLIK code always consists of exactly six digits. Validate it on both the client and server:

// Server-side validation
if (!preg_match('/^\d{6}$/', $blikCode)) {
http_response_code(400);
echo json_encode(['error' => 'The BLIK code must contain exactly 6 digits']);
exit;
}
BLIK code validity

A BLIK code is valid for 2 minutes after it is generated in the banking app. Make sure the form tells the customer to enter it promptly.