Link Web SDK
Add Spare Link to a website with React or plain JavaScript. Payments, mandates and account information for the UAE.
The Link Web SDK (@spare-technologies/spare-link-web)
shows Spare Link on your website. Your backend creates a payment request or an
account request. The SDK shows bank selection and consent to the payer, then sends
the payer to their bank in the same tab. Your backend confirms the result. The SDK
makes no API calls and holds no credentials.
Link Web SDK
npm install @spare-technologies/spare-link-webAvailable now
This SDK is published on npm (1.0.0). Pin an exact version in package.json, and
read the release notes before you upgrade. It serves the UAE only.
How Spare Link Works on a Website
- Your backend creates the request and passes its
referenceto your page. - Your page opens Spare Link with the
reference. - The payer selects a bank and gives consent in Spare Link.
- The SDK shows a loader and sends the whole tab to the bank. The bank never loads inside a frame.
- The payer's bank returns the payer to your return URL.
- Your backend reads the result from Spare.
The browser gets no onSuccess callback. Your page unloads when the payer goes to the
bank, so your backend reads the result.
Ways to Show Spare Link
| Way | The payer sees | How |
|---|---|---|
| Modal | A dialog or a side drawer over your page, open until the bank | open() |
| Inline | Spare Link inside your page, at the width and height you choose | session.mountIframe(element) |
| Redirect | The whole tab goes to the Spare page, then to the bank | session.redirectToHosted() |
Modal and inline show Spare Link inside your page, so that page must be one of the API
key's trusted origins. Where your page sets a Content Security Policy, allow the Spare API
host in frame-src.
Prerequisites
- An API key for your backend, with your website under Trusted origins. Read Register Your Website.
- A backend that calls the Spare API with
Authorization: Bearerandx-tenant: UAE. Read Integration flow. - Signed payment requests:
POST /payment-requestsneeds anx-signatureheader. Read Request signing. - Return pages on https for
successRedirectUrlandfailureRedirectUrl. - React 18 or 19, only if you use the React entry (
@spare-technologies/spare-link-web/react).
Register Your Website
Spare shows Spare Link inside a page only when that page is a trusted origin of the API key that created the request. This stops another website from framing your payment page. Each API key holds its own list.
- In the Spare dashboard, go to Developer, then API keys.
- Open the API key your backend uses to create requests, or create one.
- Under Trusted origins, add each website that shows Spare Link. For example, add your production site and your staging site.
- Save the key.
An origin is a scheme, a host, and an optional port, with no path. A key holds up to 10.
| Valid | Not valid |
|---|---|
https://shop.example.com | https://shop.example.com/checkout (has a path) |
https://shop.example.com:8443 | http://shop.example.com (not https) |
https://staging.example.com | https://*.example.com (wildcards are not allowed) |
https://xn--mnchen-3ya.de | https://mΓΌnchen.de (write a name with accents in its punycode form) |
https://shop.example.com:443 (leave the default port out) | |
https://localhost or https://192.168.1.5 (the host must be a public name) |
Two failures follow from a missing origin:
- A payment request created with
channel: 'sdk_web'fails when its API key has no trusted origins, even if you only redirect. The error message names the setting to add. - Where the page that shows Spare Link is not on the list, the browser refuses the frame
and the SDK reports
BRIDGE_TIMEOUT. This applies to payments and account information.
Add the origin of a new website before you show Spare Link on it. Requests already created with that key use the updated list immediately.
Create the Request on Your Backend
Your backend creates the request with the Spare API or the TypeScript SDK, using the API key that holds your website under Trusted origins. The Link Web SDK needs these fields on the request:
| Field | Request | Value |
|---|---|---|
channel | Payment request only | sdk_web |
successRedirectUrl | Payment and account request | An https page on your website |
failureRedirectUrl | Payment and account request | An https page on your website |
An account request has no channel field.
Payments
Create the payment with POST /payment-requests.
Make Your First Payment shows the full
request, and Setup Your First Mandate shows a
mandate. The response returns internalReference. Pass it to your page as the reference.
The SDK supports every payment type. Only type and instructions change between them.
type | The payer approves | Guide |
|---|---|---|
SingleInstantPayment | One payment now, domestic or international | Single payment |
FixedDefinedSchedule | Fixed amounts on set dates | Defined schedule |
VariableDefinedSchedule | Set dates, each with a maximum amount | Defined schedule |
FixedPeriodicSchedule | The same amount every period | Recurring |
VariablePeriodicSchedule | Every period, up to a maximum amount | Recurring |
FixedOnDemand | A fixed amount, taken when you ask | On demand |
VariableOnDemand | Any amount up to a limit, taken when you ask | On demand |
The payer approves a mandate once. Your backend then takes each payment with
POST /mandate/schedule. Read Manage your mandates.
An international payment is a SingleInstantPayment with isInternationalPayment: true.
Read International payments.
Account Information
Create the account request with POST /data/account-requests. In the UAE, the request
also requires referenceCustomerId and customerEmail. Read
Account Requests for the
full request.
The response returns an id and a requestLink. The reference is the last segment of
the requestLink path. Keep the id: your backend reads the result with it.
Show Spare Link on Your Website
Open Spare Link with the reference from your backend and the environment you are
testing in. This example saves the order before the tab goes to the bank and logs a
cancel or an error.
import { useSpareLink } from "@spare-technologies/spare-link-web/react";
export function PayButton({ reference }: { reference: string }) {
const { open, ready } = useSpareLink({
reference, // from your backend
environment: "sandbox", // or "production"
onOpenUrl: () => sessionStorage.setItem("pendingOrder", "order-1042"),
onExit: (reason) => console.log("closed", reason), // "cancel" or "user_closed"
onError: (error) => console.error(error.code, error.message),
});
return (
<button disabled={!ready} onClick={() => open()}>
Pay with your bank
</button>
);
}For account information, add product: "data". The React entry also has a ready-made
button:
import { SpareLinkButton } from "@spare-technologies/spare-link-web/react";
<SpareLinkButton reference={reference} environment="sandbox">
Pay with your bank
</SpareLinkButton>;import { SpareLink } from "@spare-technologies/spare-link-web";
const link = SpareLink.payment.create({
reference, // from your backend
environment: "sandbox", // or "production"
onOpenUrl: () => sessionStorage.setItem("pendingOrder", "order-1042"),
onExit: (reason) => console.log("closed", reason),
onError: (error) => console.error(error.code, error.message),
});
payButton.onclick = () => link.open();For account information, use SpareLink.data.create(...) with the same options.
Customize the Modal
The options of open() change how the modal looks. They never change what the payer
approves. This example opens a drawer on the right with your logo:
open({
position: "right",
width: "32rem",
logoUrl: "https://shop.example.com/logo.png",
});| Option | Default | Description |
|---|---|---|
title | Per product | The heading above Spare Link. |
logoUrl | - | An https image for the header. The SDK ignores any other URL. |
position | "center" | "center" is a dialog. "right" is a full-height drawer. |
width | "30rem" | Any CSS length, such as 30rem, 90vw or min(30rem, 100%). Use rem so the width follows the payer's text size. The modal is never wider than the screen. |
height | "45rem" | Any CSS length. A drawer is full height unless you set one. |
fullscreenOnMobile | true | Fills the screen when the viewport is 30rem wide or less. |
zIndex | 2147483000 | The stacking order, for pages with their own layers. |
dismissible | true | Closes on Escape and on a click outside. The close button always works. |
onDismiss | - | Runs when the payer closes the modal before the bank. |
Size an Inline Frame
Pass the size to mountIframe. Without a size, the frame fills its element.
session.mountIframe(element, { width: "26rem", height: "40rem" });| Option | Default | Description |
|---|---|---|
width | 100% | Any CSS length, such as 26rem. The frame is never wider than its element. |
height | 100% | Any CSS length. Set it when your element has no height of its own, or the frame has none either. |
Confirm the Result
When the payer returns, your return page loads with ?status=success or
?status=failure. Treat it as a hint only, because anyone can open that URL. Your backend
must confirm the status with Spare before you fulfil anything.
| Request | Read with | Success | Failure |
|---|---|---|---|
| Single payment | GET /payment-requests/{reference} | Processed | Rejected, Errored |
| Mandate (any schedule type) | GET /payment-requests/{reference} | Scheduled | Rejected, Errored |
| Account information | GET /data/account-requests/{id} | Approved | Rejected, Expired |
Webhooks reach your backend sooner than polling. Read Webhooks.
Read the Account Data
An approved account request gives a consent. The account data is available after a sync:
- Call
POST /data/sync?consentId={consentId}with{ "syncAccounts": true, "syncBalances": true, "syncTransactions": true }. The response returns ajobId. - Call
GET /data/sync/{jobId}untilaccount,balanceandtransactionareSucceeded. - Call
GET /data/accounts?consentId={consentId}.
Environments
The environment option picks the Spare host, so you never copy a URL.
environment | The reference must come from | Real money |
|---|---|---|
"sandbox" | The Spare sandbox | No |
"production" | Spare production | Yes |
The reference must come from the same environment as the option. In the sandbox, the
console says so once and the modal header shows a Sandbox badge. A baseUrl on a
Spare domain must be one of Spare's own hosts. The SDK refuses any other Spare host.
Go to the Bank
When the payer chooses a bank, Spare Link asks the SDK to open it. The SDK covers Spare Link with a loader and sends this tab to the bank. The loader stays until the bank page loads, so the payer never sees a blank page.
- To change the loader text, set
handoffText. - To show your own loader, set
handoffLoader: falseand useonHandoff. - To keep the tab on your page, set
openUrlStrategy: "none". The SDK then only passes the URL toonOpenUrl.
On a Phone
On a phone, the payer's bank may open its own app instead of its website. The SDK sends the tab to the bank link in both cases. Where the payer returns to your tab without finishing, the SDK removes the loader. Where the payer finishes, the bank returns them to your return URL. Test this flow on a real phone with each bank before you go live.
Terms and Privacy Links
The consent page links to Spare's Terms of Service and Privacy Policy. When the payer taps
one, the SDK opens it in a new tab. This tab, the frame and the modal stay on the consent
page, so the payer can come back and pay or cancel. The behaviour is the same for every
openUrlStrategy, and it never sends the payer to the bank.
The SDK reports the tap through onEvent. Your page does not need to handle it:
onEvent: (name, data) => {
if (name === "open_document") console.log(data.url, data.opened);
},| Field | Description |
|---|---|
url | The document address. It is always https. |
opened | false when the browser blocked the new tab. |
What Spare Link Collects
Open Finance rules ask Spare to give the payer's bank context about each payment, so the bank can judge its risk. Spare Link collects this context in the payer's browser, inside Spare's frame. Your page does not collect it, cannot read it, and cannot supply it.
- Device and browser facts. Device type, operating system, screen, connection type, browser details and how the payer scrolled. These need no prompt.
- Location. Spare Link asks the payer once, on the consent screen. Where the payer
declines, the payment goes ahead without it. The SDK gives the frame
allow="payment; geolocation", so the browser's prompt names your website.
Two things fall to you:
- Do not block location on your page. A
Permissions-Policyheader withgeolocation=()stops the prompt. Leave it out, or allow Spare's hosts, for examplegeolocation=(self "https://api.tryspare.ae" "https://api.sandbox.tryspare.ae"). - State it in your privacy policy. Spare Link collects location and device details during payment on your page.
Options
useSpareLink, SpareLink.payment.create and SpareLink.data.create take the same options.
| Option | Default | Description |
|---|---|---|
reference | Required | From your backend. |
environment | Required | "sandbox" or "production". Read Environments. |
tenant | "UAE" | The Spare market. |
product | "payment" | "data" for account information. React only. JavaScript uses SpareLink.data. |
modal | - | Default modal options. React only. |
handoffLoader | true | Shows a loader from the moment the payer leaves until the bank loads. Read Go to the Bank. |
handoffText | Taking you to your bank⦠| The text of that loader. |
openUrlStrategy | "top" | "top" sends this tab to the bank. "none" only passes the URL to onOpenUrl. |
allowedMessageOrigins | The Spare API origin | Exact origins only. The SDK rejects *, paths and plain http. |
readyTimeoutMs | 60000 | Reports BRIDGE_TIMEOUT when Spare Link does not start in time. |
debug | false | Logs the lifecycle to the browser console. |
baseUrl | - | Advanced. Replaces environment and tenant. https only, or http on localhost. |
useSpareLink returns open(options?), close(), ready, error and session. ready is
true once Spare Link can open. error is set when an option is not valid.
Callbacks
| Callback | When it runs |
|---|---|
onReady | Spare Link is ready. |
onOpenUrl(url) | Spare Link sends the payer to the bank. Save your order reference here. |
onHandoff(url) | This tab is about to go to the bank. It runs once, after onOpenUrl, and only when the SDK navigates. |
onExit(reason) | The payer cancelled ("cancel") or closed the modal ("user_closed"). |
onError(error) | Something failed. Read Error Codes. |
onEvent(name, data) | Every event, by name: ready, open_url, open_document, handoff, exit, error. |
Error Codes
onError receives an error with a code and a message.
| Code | Meaning | What to do |
|---|---|---|
INVALID_CONFIG | An option is not valid, for example no reference or environment, or a width with no unit. | Read the message. |
BRIDGE_TIMEOUT | Spare Link did not start in time. | Check that the reference comes from the same environment and that your website is under Trusted origins. For a payment, check that it has channel: 'sdk_web'. |
IFRAME_LOAD_FAILED | The frame did not load. | Check the network and your Content Security Policy. |
HOSTED_ERROR | Spare Link reported an error. The message is the Spare code. | For request_expired, create a new request. Read When a Request Is Already Over. |
DESTROYED | A session was used after destroy(). | Create a new session. |
When a Request Is Already Over
Where the payer opens a request that has already ended, the frame shows the reason and your page receives one of these:
| Spare code | What it means | What your page gets |
|---|---|---|
request_expired | The request ran past its expiry. | onError with HOSTED_ERROR |
request_processed | The payment is already complete. | onError with HOSTED_ERROR |
request_errored, something_went_wrong | The request failed on Spare's side. | onError with HOSTED_ERROR |
request_rejected | The request was cancelled. | onExit("cancel") |
The frame shows no return link to your website, so decide the next step in onError or
onExit, then confirm the result on your backend. A reference that Spare does not know
has no request to show, so the SDK reports BRIDGE_TIMEOUT.