Skip to main content

Payment Links

Generate shareable payment links for flexible payment collection scenarios.

Payment links provide a URL where customers can complete their payment. This guide covers creating and managing payment links.

Overview​

A payment link:

  • Associates with a payment order
  • Generates a checkout URL for customers
  • Has configurable lifetime (expiration)
  • Supports language and payment method customization
Amount Format

All amounts use minor currency units (e.g., cents for EUR), as integers:

  • Request: integer (e.g., 2500 for €25.00)
  • Response: integer (e.g., 2500 for €25.00)
Splits require a non-expiring, full-amount link

If the underlying order was created with splits, the payment link must not expire. Set lifetime: 0 — otherwise the request is rejected with validation_split_expiration_not_allowed. The link amount must also equal the order's full amount; a mismatch is rejected with validation_split_amount_mismatch (partial-amount links are only allowed for non-split orders).

Endpoints​

MethodEndpointDescription
POST/checkout-payment-link/integration/v1/payment-linksCreate a payment link
GET/checkout-payment-link/integration/v1/payment-links/{id}Get a payment link
GET/checkout-payment-link/integration/v1/payment-linksList payment links
PUT/checkout-payment-link/integration/v1/payment-links/{id}Update a payment link
PUT/checkout-payment-link/integration/v1/payment-links/{id}/cancelCancel a payment link
info

You can read a link with GET /payment-links/{id} and list links with GET /payment-links. Links can also be updated (PUT /payment-links/{id}) and canceled (PUT /payment-links/{id}/cancel). Webhooks remain available for state changes (see Webhooks).

Request​

curl -X POST https://api.paysera.com/checkout-payment-link/integration/v1/payment-links \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"name": "Order #12345",
"lifetime": 3600,
"experience": {
"language": "en",
"payment_flow": "paysera_checkout"
},
"purchase": {
"amount": 2500
},
"payment_details": {
"key": "swedbank",
"purpose": "Order #12345 - Leather Wallet",
"country_code": "LT"
},
"payer_information": {
"name": "John Doe",
"email": "john.doe@paysera.net"
},
"metadata": {
"referrer": "https://paysera.net"
}
}'

Request Parameters​

ParameterTypeRequiredDescription
order_idUUIDYesThe payment order ID
namestringYesDisplay name for the payment link (max 255 chars)
lifetimeintegerNoLink validity in seconds (default: 259200 = 3 days, 0 = never expire, max: 86313600). Must be 0 if the order has splits.
experienceobjectYesPayment experience configuration
experience.languagestringYesUI language code (ISO 639-1, e.g., "en", "lt")
experience.payment_flowstringNoPayment flow: "paysera_checkout" (default) or "direct_payment"
purchaseobjectYesPurchase information
purchase.amountintegerYesAmount in minor units (e.g., 2500 for €25.00)
payment_detailsobjectNoPre-selected payment method configuration
payment_details.keystringNoPayment method key (e.g., "swedbank")
payment_details.purposestringNoPayment purpose description (max 255 chars). Used verbatim as the payer-facing payment purpose, truncated to 140 chars — see Payment purpose
payment_details.country_codestringNoISO 3166-1 alpha-2 country code
payer_informationobjectNoCustomer information
payer_information.namestringNoCustomer name (max 150 chars)
payer_information.emailstringNoCustomer email
metadataobjectNoAdditional key-value metadata

Response​

{
"link_id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"payment_URL": "https://api.paysera.com/checkout-payment-link/payment-collection/v1/payment-links/abc123def456GhiJkl_mNOpQrStUvWxYz0123456",
"experience": {
"language": "en",
"payment_flow": "paysera_checkout"
},
"purchase": {
"amount": 2500
},
"payment_details": {
"key": "swedbank",
"purpose": "Order #12345 - Leather Wallet",
"country_code": "LT"
},
"payer_information": {
"name": "John Doe",
"email": "john.doe@paysera.net"
},
"expired_at": 1736436870,
"created_at": 1736433270
}

Response Fields​

FieldTypeDescription
link_idUUIDUnique payment link identifier
order_idUUIDAssociated order identifier
payment_URLstringPayment URL for customer redirect
experienceobjectPayment experience configuration
experience.languagestringUI language code
experience.payment_flowstringPayment flow type
purchaseobjectPurchase information
purchase.amountLongAmount in minor units
payment_detailsobjectPayment details (if provided)
payment_details.keystringPayment method key
payment_details.purposestringThe purpose you sent, echoed back — null if you omitted it. See Payment purpose
payment_details.country_codestringCountry code
payer_informationobjectPayer information (if provided)
payer_information.namestringPayer name
payer_information.emailstringPayer email
expired_atLongExpiration Unix timestamp (seconds), null if never expires
created_atLongCreation Unix timestamp (seconds)

Payment purpose​

The payment purpose is the narrative the payer sees on the payment page and on their bank statement. The resolved narrative — yours or the generated default — is returned as purpose on the payment object and in payment webhooks. The payment link object echoes back only what you sent in payment_details.purpose, so that field stays null when you omit it, even though the payer still sees the generated default.

The purpose is set at creation only. The management endpoints use a different, flat representation of the link and do not carry it at all: neither Get a Payment Link nor List Payment Links returns it, and Update a Payment Link cannot change it. To change the purpose, cancel the link and create a new one.

If you send payment_details.purpose, it is used verbatim as the payer-facing purpose, truncated to 140 characters (banks do not accept longer narratives, even though the field itself accepts up to 255). Write it in the language your payer should read — it is never translated.

If you omit it, Paysera generates a default narrative from the order and project:

Payment for order #<purchase.reference> at <business name> (<project website>)

The generated text is translated into experience.language; languages without a translation fall back to English. The (<project website>) suffix is omitted when the project has no website configured.

Merchant statement is separate

The text on your own settlement statement is always the generated English narrative with your legal or registered name, regardless of payment_details.purpose and experience.language, so your reconciliation text stays stable.

StatusCodeDescription
ActiveactiveLink is valid and can be used for payment
CompletedcompletedPayment completed successfully via this link
ExpiredexpiredLink has expired (can still complete if payment in-flight)
CanceledcanceledLink was invalidated
No Failed Status

Payment links do not have a failed status. If a payment attempt fails, the link remains active (unless expired or canceled), allowing the customer to retry.

See Payment Statuses Reference for complete status documentation.

Code Examples​

<?php

function createPaymentLink(string $accessToken, string $orderId, array $linkData): array
{
$ch = curl_init('https://api.paysera.com/checkout-payment-link/integration/v1/payment-links');

$payload = [
'order_id' => $orderId,
'name' => $linkData['name'],
'lifetime' => $linkData['lifetime'] ?? 3600,
'experience' => [
'language' => $linkData['language'] ?? 'en',
'payment_flow' => 'paysera_checkout',
],
'purchase' => [
'amount' => $linkData['amount'], // In minor units (cents)
],
];

// Add optional payer information
if (!empty($linkData['payer_email'])) {
$payload['payer_information'] = [
'name' => $linkData['payer_name'] ?? null,
'email' => $linkData['payer_email'],
];
}

// Add pre-selected payment method
if (!empty($linkData['payment_method'])) {
$payload['payment_details'] = [
'key' => $linkData['payment_method'],
'country_code' => $linkData['country_code'] ?? null,
];
}

curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $accessToken,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode !== 201) {
throw new Exception('Failed to create payment link: ' . $response);
}

return json_decode($response, true);
}

// Usage - amount is in minor units (cents)
$link = createPaymentLink($accessToken, $orderId, [
'name' => 'Order #12345',
'amount' => 2500, // €25.00 in cents
'language' => 'en',
'lifetime' => 3600,
'payer_email' => 'customer@paysera.net',
]);

echo "Payment URL: " . $link['payment_URL'];

Pre-selecting Payment Method​

To skip the payment method selection screen, use direct_payment flow with the preferred payment method:

curl -X POST https://api.paysera.com/checkout-payment-link/integration/v1/payment-links \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"order_id": "order-uuid",
"name": "Direct bank payment",
"lifetime": 3600,
"experience": {
"language": "en",
"payment_flow": "direct_payment"
},
"purchase": {
"amount": 2500
},
"payment_details": {
"key": "swedbank",
"country_code": "LT"
}
}'

Getting Available Payment Methods​

To get available payment methods for pre-selection:

curl -X GET "https://api.paysera.com/checkout-project/integration/v1/methods?amount=2500&currency=EUR" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Filtering by amount and currency is optional and only applied when both are provided. There is no country query parameter — supported countries are returned per method in available_countries.

Response includes available methods with their keys:

{
"items": [
{
"key": "swedbank",
"title": "Swedbank",
"description": "Pay via Swedbank online banking.",
"type": "banklink",
"flow": "redirect",
"available_countries": ["LT", "EE"],
"available_currencies": {
"EUR": { "main": true }
}
},
{
"key": "seb",
"title": "SEB",
"type": "banklink",
"flow": "redirect",
"available_countries": ["LT"],
"available_currencies": {
"EUR": { "main": true }
}
}
]
}

See Payment Methods Reference for the full field list.

Error Responses​

Invalid Order​

{
"error": "not_found",
"error_description": "Order not found"
}

Validation Error​

{
"error": "invalid_properties",
"error_description": "Validation failed",
"error_properties": {
"purchase.amount": ["Amount should be greater than 0"],
"experience.language": ["Language is required"]
}
}

Creating a link whose name is already used by another link on the same order returns 409 Conflict:

{
"error": "validation_duplicate_name",
"error_description": "A payment link with this name already exists"
}

Authentication Failure​

{
"error": "unauthorized",
"error_description": "Authentication is required to access this resource"
}
GET /checkout-payment-link/integration/v1/payment-links/{id}

Retrieves a single payment link. Expired links are recognised on read and reflected in status.

curl https://api.paysera.com/checkout-payment-link/integration/v1/payment-links/{id} \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response​

{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"project_id": "your-project-id",
"name": "Order #12345",
"amount": 2500,
"currency": "EUR",
"language": "en",
"country_code": "LT",
"link": { "url": "https://api.paysera.com/…/payment-links/abc123", "hash": "abc123" },
"preferred_payment_method_key": "swedbank",
"status": "active",
"is_personalized": true,
"expired_at": 1736436870,
"created_at": 1736433270,
"updated_at": 1736433270
}
GET /checkout-payment-link/integration/v1/payment-links

Returns a cursor-paginated list of payment links for your project.

curl "https://api.paysera.com/checkout-payment-link/integration/v1/payment-links?status=active&size=50" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Filter Parameters​

ParameterTypeDescription
order_idUUIDFilter by associated order
statusstringComma-separated statuses, e.g. active,expired

Pagination Parameters​

The order, payment link, payment and refund list endpoints share the same cursor-based pagination. Cursors are opaque strings — read them from _metadata.cursors and send them back unchanged; never build or parse them yourself.

ParameterTypeDefaultDescription
sizeinteger10Items per page (1–1000). Out-of-range values return 400 invalid_properties.
afterstring—Cursor for the next page. Take it from _metadata.cursors.after.
beforestring—Cursor for the previous page. Take it from _metadata.cursors.before. Mutually exclusive with after.
order_directionstringdescSort direction: asc or desc.
order_bystringcreation timeField to sort by. Allowed values differ per endpoint — see the endpoint's parameter table.
include_total_countbooleanfalseSet to true to populate _metadata.total. Off by default because counting is more expensive.

Every list response has the same envelope:

{
"items": [],
"_metadata": {
"has_next": true,
"has_previous": false,
"cursors": {
"after": "MDE5ZmRiMmUtOWE5NC03ZDVmLThmY2ItNGRhMTQ1MzFkZGQ0",
"before": "MDE5ZmRjMjgtNWQ5YS03NjQ5LWExOWItZDhjNDAzODQ2NTRi"
},
"total": null
}
}

total is null unless you pass include_total_count=true.

To walk the whole collection, keep sending after until has_next is false:

# first page
curl "https://api.paysera.com/merchant-order/integration/v1/orders?size=50" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# next page — reuse _metadata.cursors.after from the previous response
curl "https://api.paysera.com/merchant-order/integration/v1/orders?size=50&after=MDE5ZmRiMmUtOWE5NC03ZDVmLThmY2ItNGRhMTQ1MzFkZGQ0" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Unknown query parameters are ignored

The API does not reject parameters it doesn't know. Sending limit or cursor (names used by some other APIs) does not return an error — the values are silently dropped and you keep receiving the first page with the default size of 10. If pagination seems stuck, check the parameter names first.

order_by accepts created_at (default), updated_at, status, amount, currency and name.

Response​

{
"items": [
{
"id": "c8d9e0f1-2a3b-4c5d-6e7f-8a9b0c1d2e3f",
"link": { "url": "https://api.paysera.com/…/payment-links/abc123", "hash": "abc123" },
"project_id": "your-project-id",
"order_id": "a6f2b8e3-5e5f-47d9-b13f-87ed2db2938a",
"name": "Order #12345",
"amount": 2500,
"currency": "EUR",
"language": "en",
"country_code": "LT",
"receiver": { "name": null, "email": null },
"payer": { "name": "Jonas Jonaitis", "email": "jonas@example.com" },
"expired_at": 1736436870,
"preferred_payment_method_key": "swedbank",
"status": "active",
"is_personalized": true,
"created_at": 1736433270,
"updated_at": 1736433270,
"is_test": false
}
],
"_metadata": {
"has_next": true,
"has_previous": false,
"cursors": {
"after": "MDE5ZmRiMmUtYTY2Ny03MjFiLTkyZjMtNTUxODk4ZDg5OWM2",
"before": "MDE5ZmRjMjgtNjYwMy03NzJhLWI3NjQtNzM3NGViM2Q4MTUx"
},
"total": null
}
}
PUT /checkout-payment-link/integration/v1/payment-links/{id}

Updates a payment link. This is a full replacement — send all required fields; changes take effect immediately. The full updated payment-link object is returned.

Different field names from create

The update request uses a flat structure and names the lifetime field link_lifetime — unlike the create request, which nests purchase.amount / experience.language and names the field lifetime. This difference is intentional; use the field names shown below when updating. As with create, set link_lifetime: 0 if the order has splits.

Request Parameters​

ParameterTypeRequiredDescription
namestringYesDisplay name (max 255 chars)
amountintegerYesAmount in minor units (≥ 1)
currencystringYesISO 4217 currency
languagestringYesUI language (ISO 639-1)
payerobjectYes{ name, email }
receiverobjectNo{ name, email }
link_lifetimeintegerNoSeconds; default 259200 (3 days), 0 = never expires (max 86313600). Must be 0 if the order has splits.
preferred_payment_method_keystringNoPre-selected payment method
PUT /checkout-payment-link/integration/v1/payment-links/{id}/cancel

Cancels a payment link. The request body is empty; a 200 OK is returned.

Idempotent

Re-cancelling an already-canceled link succeeds. A 409 is returned only if the link has an active payment session in progress.