PaidBy Payment Widget
Overview
Transaction creation and hosted payment widget integration
Use the PaidBy API to create an Open Banking transaction and receive a hosted payment URL. A successful request returns an authorization object containing a redirect_url. Load this URL directly in the hosted PaidBy payment widget.
⚠️ Do not construct the widget URL manually. Use the complete URL returned in redirect_url.
Create a transaction
POST /v1/authorization/
Creates a PaidBy Open Banking transaction. A successful request returns an authorization object containing a redirect_url. The merchant frontend loads this URL in the hosted PaidBy payment widget.
API endpoints
|
Environment |
Method |
Transaction endpoint |
|
stage |
POST |
|
|
production |
POST |
Use the stage environment for development and testing. Use the production environment for approved live transactions.
These URLs are transaction-creation endpoints, not widget URLs. The complete widget URL is returned in the redirect_url response field.
Request headers
|
Header |
Type |
Requirement |
Example |
Description |
|
From |
String |
Mandatory |
|
API client name assigned to the PaidBy integration. |
|
Authorization |
String |
Mandatory |
|
Bearer token assigned to the PaidBy integration. The word Bearer must be included. |
|
Content-Type |
String |
Mandatory |
|
Indicates that the request body is JSON. |
Use the exact From and Authorization values assigned to the merchant integration. Do not expose these values in frontend code.
Request example
cURL
curl "https://stage-gateway.isignth.is/v1/authorization/" \
--request POST \
--header "From: YOUR_API_CLIENT_NAME" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"workflow": "Test_Workflow",
"merchant": {
"id": "Test_Merchant",
"transaction_webhook_url": "https://merchant.example.com/webhooks/paidby",
"return_url": "https://merchant.example.com/payment/result"
},
"client": {
"first_name": "Shana",
"last_name": "Barrows",
"email": "shana.barrows@mail.com"
},
"transaction": {
"id": "Test_ID",
"amount": "100",
"currency": "EUR",
"reference": "Test_Ref"
},
"account_holder": {
"name": "Jane Smith",
"type": "business",
"iban": "DE89370400440532013000",
"bic": "COBADEFFXXX"
},
"account": {
"identifier": "Test_ID"
}
}'
For production, use: https://api.isxpay.com/gateway/service/v1/authorization/
JSON body
{
"workflow": "Test_Workflow",
"merchant": {
"id": "Test_Merchant",
"transaction_webhook_url": "https://merchant.example.com/webhooks/paidby",
"return_url": "https://merchant.example.com/payment/result"
},
"client": {
"first_name": "Shana",
"last_name": "Barrows",
"email": "shana.barrows@mail.com"
},
"transaction": {
"id": "Test_ID",
"amount": "100",
"currency": "EUR",
"reference": "Test_Ref"
},
"account_holder": {
"name": "Jane Smith",
"type": "business",
"iban": "DE89370400440532013000",
"bic": "COBADEFFXXX"
},
"account": {
"identifier": "Test_ID"
}
}
For GBP transactions, set transaction.currency to GBP and omit account_holder. The account_holder object is used only for applicable EUR flows.
Request parameters
|
Parameter |
Type |
Requirement |
Example |
Description |
|
|
String |
Mandatory |
|
Workflow name assigned to the merchant integration. |
|
|
Object |
Mandatory |
- |
Merchant identification and callback configuration. |
|
|
String |
Mandatory |
|
Merchant identifier assigned to the integration. |
|
|
String |
Mandatory |
|
HTTPS endpoint that receives transaction notifications. |
|
|
String |
Mandatory |
|
URL to which the customer is returned after the hosted flow. |
|
|
Object |
Mandatory |
- |
Customer information. |
|
|
String |
Mandatory |
|
Customer first name. |
|
|
String |
Mandatory |
|
Customer last name. |
|
|
String |
Mandatory |
|
Customer email address. |
|
|
String |
Optional |
- |
Customer gender. |
|
|
String |
Optional |
- |
Customer IP address. |
|
|
String |
Optional |
- |
Customer mobile number. |
|
|
String |
Optional |
- |
Customer title. |
|
|
String |
Optional |
- |
Customer middle name. |
|
|
String |
Optional |
- |
Customer date of birth. |
|
|
String |
Optional |
- |
Customer country of citizenship. |
|
|
String |
Optional |
- |
Customer country of birth. |
|
|
String |
Optional |
- |
Residential street number. |
|
|
String |
Optional |
- |
Residential street. |
|
|
String |
Optional |
- |
Secondary address information. |
|
|
String |
Optional |
- |
Residential city. |
|
|
String |
Optional |
- |
Residential state or subdivision. |
|
|
String |
Optional |
- |
Residential postal code. |
|
|
String |
Optional |
- |
Residential country. |
|
|
Object |
Mandatory |
- |
Payment transaction information. |
|
|
String |
Mandatory |
|
Unique merchant transaction identifier. |
|
|
String |
Mandatory |
|
Amount in the lowest currency unit. |
|
|
String |
Mandatory |
|
EUR or GBP, according to the merchant contract. |
|
|
String |
Mandatory |
|
Unique merchant transaction reference. |
|
|
Object |
Mandatory |
- |
Merchant-side customer account information. |
|
|
String |
Mandatory |
|
Unique customer identifier assigned by the merchant. |
|
|
Object |
Conditional |
- |
Bank-account details for applicable EUR transactions. |
|
|
String |
Optional |
|
Name on the bank account. |
|
|
String |
Optional |
|
Type of bank account. |
|
|
String |
Conditional |
|
Required when account_holder is included. |
|
|
String |
Conditional |
|
Bank identifier code. |
Amount formatting
The transaction.amount must be supplied in the lowest currency unit.
|
Payment amount |
API value |
|
EUR 1.00 |
100 |
|
EUR 10.00 |
1000 |
|
GBP 25.50 |
2550 |
Unique reference fields
|
Field |
Requirement |
|
|
Must be unique. |
|
|
Must be unique. |
|
|
Must be unique and paired with the customer for future transactions. |
Conditional account-holder fields
|
Rule |
Description |
|
EUR |
account_holder may be supplied for applicable EUR flows. |
|
GBP |
Omit account_holder. |
|
IBAN |
When account_holder is included, account_holder.iban is mandatory. |
Response example
When the transaction is created successfully, PaidBy returns an authorization object similar to:
JSON
{
"id": "e6988b4a-ed1c-4104-9caf-af6bb8769756",
"uid": "e6988b4a-ed1c-4104-9caf-af6bb8769756",
"secret": "159173ee-6309-4806-b499-d2598a2ed21e",
"context_uid": "e6988b4a-ed1c-4104-9caf-af6bb8769756",
"mode": "registration",
"original_message": {
"merchant_id": "Test_Merchant",
"transaction_id": "Test_ID",
"reference": "Test_Ref"
},
"transactions": "[]",
"state": "PENDING",
"compound_state": "PENDING.VALIDATED_TRANSACTION",
"redirect_url": "https://paidby-stage.isxtech.com/landing/e6988b4a-ed1c-4104-9caf-af6bb8769756"
}
The redirect_url field contains the complete hosted PaidBy widget URL. Load this value directly in the payment iframe.
Response attributes
|
Attribute |
Type |
Example |
Description |
|
|
String |
|
Unique authorization response identifier. |
|
|
String |
|
Unique authorization identifier. |
|
|
String |
|
Transaction secret used to validate notifications. |
|
|
String |
|
Context identifier associated with the transaction. |
|
|
String |
|
Authorization flow mode. |
|
|
Object |
- |
Merchant and transaction information associated with the original request. |
|
|
String |
|
Merchant identifier from the request. |
|
|
String |
|
Transaction identifier from transaction.id. |
|
|
String |
|
Transaction reference from transaction.reference. |
|
|
String or collection |
|
Transaction data returned by the API. |
|
|
String |
|
Current transaction state. |
|
|
String |
|
Detailed transaction state. |
|
|
String |
|
Complete hosted PaidBy widget URL. |
Response handling
- Use
redirect_urlexactly as returned. - Do not construct the widget URL from
id,uid, or another response field. - Do not append values to
redirect_url. - Do not treat the initial
PENDINGstate as confirmation of payment. - Handle final payment status through the separate PaidBy Notifications documentation.
Open the payment widget
The merchant frontend requests a transaction from the merchant backend. The merchant backend calls PaidBy and returns the information required to open the widget.
Recommended merchant backend response
JSON
{
"id": "e6988b4a-ed1c-4104-9caf-af6bb8769756",
"state": "PENDING",
"compound_state": "PENDING.VALIDATED_TRANSACTION",
"redirect_url": "https://paidby-stage.isxtech.com/landing/e6988b4a-ed1c-4104-9caf-af6bb8769756"
}
Backend integration
The following examples demonstrate the same transaction-creation flow in Node.js, PHP, and Java.
Node.js
import express from "express";
const app = express();
app.use(express.json());
const endpoints = {
stage: "https://stage-gateway.isignth.is/v1/authorization/",
production: "https://api.isxpay.com/gateway/service/v1/authorization/"
};
app.post("/api/create-paidby-transaction", async (req, res) => {
const payload = {
workflow: process.env.PAIDBY_WORKFLOW,
merchant: {
id: process.env.PAIDBY_MERCHANT_ID,
transaction_webhook_url: process.env.PAIDBY_WEBHOOK_URL,
return_url: process.env.PAIDBY_RETURN_URL
},
client: req.body.client,
transaction: req.body.transaction,
account: req.body.account
};
if (req.body.transaction.currency === "EUR" && req.body.account_holder) {
payload.account_holder = req.body.account_holder;
}
const endpoint = process.env.PAIDBY_ENVIRONMENT === "production"
? endpoints.production : endpoints.stage;
const response = await fetch(endpoint, {
method: "POST",
headers: {
From: process.env.PAIDBY_API_CLIENT_NAME,
Authorization: `Bearer ${process.env.PAIDBY_API_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
const data = await response.json();
if (!response.ok || !data.redirect_url) {
return res.status(502).json({ error: "Unable to create PaidBy transaction" });
}
res.json({
id: data.id,
state: data.state,
compound_state: data.compound_state,
redirect_url: data.redirect_url
});
});
PHP
<?php
$endpoint = getenv('PAIDBY_ENVIRONMENT') === 'production'
? 'https://api.isxpay.com/gateway/service/v1/authorization/'
: 'https://stage-gateway.isignth.is/v1/authorization/';
$payload = [
'workflow' => getenv('PAIDBY_WORKFLOW'),
'merchant' => [
'id' => getenv('PAIDBY_MERCHANT_ID'),
'transaction_webhook_url' => getenv('PAIDBY_WEBHOOK_URL'),
'return_url' => getenv('PAIDBY_RETURN_URL')
],
'client' => $request['client'],
'transaction' => $request['transaction'],
'account' => $request['account']
];
if ($request['transaction']['currency'] === 'EUR' && isset($request['account_holder'])) {
$payload['account_holder'] = $request['account_holder'];
}
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'From: ' . getenv('PAIDBY_API_CLIENT_NAME'),
'Authorization: Bearer ' . getenv('PAIDBY_API_TOKEN'),
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($payload)
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($status < 200 || $status >= 300 || empty($data['redirect_url'])) {
http_response_code(502);
echo json_encode(['error' => 'Unable to create PaidBy transaction']);
exit;
}
echo json_encode([
'id' => $data['id'],
'state' => $data['state'],
'compound_state' => $data['compound_state'],
'redirect_url' => $data['redirect_url']
]);
Java
HttpClient client = HttpClient.newHttpClient();
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("workflow", System.getenv("PAIDBY_WORKFLOW"));
payload.put("merchant", Map.of(
"id", System.getenv("PAIDBY_MERCHANT_ID"),
"transaction_webhook_url", System.getenv("PAIDBY_WEBHOOK_URL"),
"return_url", System.getenv("PAIDBY_RETURN_URL")
));
payload.put("client", requestBody.get("client"));
payload.put("transaction", requestBody.get("transaction"));
payload.put("account", requestBody.get("account"));
Map<String, Object> transaction = (Map<String, Object>) requestBody.get("transaction");
if ("EUR".equals(transaction.get("currency")) && requestBody.containsKey("account_holder")) {
payload.put("account_holder", requestBody.get("account_holder"));
}
String endpoint = "production".equals(System.getenv("PAIDBY_ENVIRONMENT"))
? "https://api.isxpay.com/gateway/service/v1/authorization/"
: "https://stage-gateway.isignth.is/v1/authorization/";
HttpRequest request = HttpRequest.newBuilder(URI.create(endpoint))
.header("From", System.getenv("PAIDBY_API_CLIENT_NAME"))
.header("Authorization", "Bearer " + System.getenv("PAIDBY_API_TOKEN"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(payload)))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> data = mapper.readValue(response.body(), new TypeReference<>() {});
if (response.statusCode() < 200 || response.statusCode() >= 300 || data.get("redirect_url") == null) {
throw new IllegalStateException("Unable to create PaidBy transaction");
}
Frontend integration
HTML
<button id="paidby-pay-button" type="button">
Proceed to pay
</button>
<p id="paidby-error" role="alert"></p>
<dialog id="paidby-widget-dialog" aria-labelledby="paidby-widget-title">
<header class="paidby-dialog-header">
<h2 id="paidby-widget-title">Complete your payment</h2>
<button id="paidby-close-button" type="button" aria-label="Close payment dialog">×</button>
</header>
<iframe
id="paidby-widget-frame"
title="PaidBy Payment Widget"
loading="lazy"
scrolling="yes"
allow="payment"
allow="camera; microphone"
src="about:blank">
</iframe>
</dialog>
The iframe remains set to about:blank until transaction creation succeeds.
CSS
dialog#paidby-widget-dialog {
width: min(1020px, calc(100% - 32px));
max-width: 1020px;
max-height: calc(100dvh - 44px);
padding: 0;
border: 0;
border-radius: 10px;
overflow: hidden;
background: transparent;
box-shadow: 08px 32px rgba(0, 0, 0, 0.25);
}
dialog#paidby-widget-dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
}
.paidby-dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #fff;
}
#paidby-widget-frame {
display: block;
width: 100%;
height: 600px;
min-height: 520px;
border: 0;
background: #fff;
}
@media (max-width: 768px) {
dialog#paidby-widget-dialog {
width: 100vw;
height: 100dvh;
max-width: 100vw;
max-height: 100dvh;
margin: 0;
border-radius: 0;
}
#paidby-widget-frame {
height: calc(100dvh - 68px);
min-height: 0;
}
}
JavaScript
const payButton = document.getElementById("paidby-pay-button");
const dialog = document.getElementById("paidby-widget-dialog");
const iframe = document.getElementById("paidby-widget-frame");
const closeButton = document.getElementById("paidby-close-button");
const errorMessage = document.getElementById("paidby-error");
let lastFocusedElement = null;
async function openPaidByWidget(paymentDetails) {
payButton.disabled = true;
errorMessage.textContent = "";
try {
const response = await fetch("/api/create-paidby-transaction", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(paymentDetails)
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || "Unable to start payment");
if (!data.redirect_url) throw new Error("The response did not contain redirect_url");
lastFocusedElement = document.activeElement;
iframe.src = data.redirect_url;
dialog.showModal();
closeButton.focus();
} catch (error) {
errorMessage.textContent = error.message || "Unable to start payment";
} finally {
payButton.disabled = false;
}
}
closeButton.addEventListener("click", () => dialog.close());
dialog.addEventListener("click", event => {
if (event.target === dialog) dialog.close();
});
dialog.addEventListener("close", () => {
iframe.src = "about:blank";
if (lastFocusedElement) lastFocusedElement.focus();
});
Error handling
The merchant integration should handle:
- Network failures.
- Missing or invalid
Fromvalues. - Missing or invalid
Authorizationvalues orBearerprefix. - Invalid workflow or merchant IDs.
- Missing mandatory request objects or fields.
- Duplicate transaction identifiers or references.
- Invalid amount formatting.
- Unsupported currency.
- Missing
account.identifier. - Missing
account_holder.ibanwhenaccount_holderis supplied. - PaidBy API timeouts.
- Non-JSON API responses.
- Missing
redirect_url. - Expired widget sessions.
- Transaction cancellation, decline, or risk rejection.
HTTP response codes
PaidBy HTTP response codes are documented in the API Glossary. Refer to API Glossary - HTTP Codes.
Check the HTTP response status before processing the response body. Successful responses should also be checked for the expected fields, including redirect_url.
Payment notifications
Payment status notifications are documented separately from the transaction-creation API. Refer to PaidBy API Notifications.
The notifications documentation covers:
- Notification payloads.
- Authentication or verification.
- Retry behaviour.
- Merchant acknowledgement.
- Final payment-status handling.
This guide covers only:
- Creating the PaidBy transaction.
- Receiving the
redirect_url.
- Opening the hosted PaidBy widget.
The initial state and compound_state returned when creating a transaction must not be treated as the final payment result.