Yutai Connect
Yutai Connect lets a holder link a bank card to your app and unlock the benefits their purchases reach. Yutai hosts the whole flow on its own origin: your page opens a session, the holder chooses their bank and consents, and Yutai tells you the outcome. Your app never sees a bank credential, an account number or a balance.
The flow
Four steps, and the middle two never leave Yutai:
- Your server creates a Connect session with its secret key and gets a hosted address back.
- Your page opens that address with the SDK button or a redirect.
- The holder picks their bank, signs in there and consents to share what the benefit needs, all on Yutai's own page.
- Yutai returns the session's standing to your page, and your server reads the session again before it grants anything.
The session address carries a one-time token that stands for 15 minutes. Your secret key stays on your server: the browser holds the address, never the key.
// On your server, never in the browser.
const session = await api.createConnectSession({
externalRef: "your-own-user-id", // never an email address or a phone number
returnUrl: "https://wallet.example/yutai/return",
view: "consent", // or "purchases" for the matched purchases view
});
// The hosted address carries the one-time token; hand it to the page.
const sessionUrl = session.connectUrl;The session
A session is one attempt to link one person's card. Your server creates it with its secret key; these are all the fields the create call takes.
externalRef- Your own opaque id for the person the session is for: 1 to 128 letters, digits or . _ : -, never an email address or a phone number.
returnUrl- Where the hosted page sends the person back: an https address, or your app's own scheme when your profile lists it; at most 2048 characters, with no credentials.
context- The brand the person came from: exactly one of an ISIN or a company id, shown on the consent screen only.
locale- The language the hosted page speaks: a language tag such as en-GB.
mode- How the result comes back: one of redirect, popup or app. It only changes how the result returns, never the flow.
view- What the hosted page shows: one of consent or purchases.
wallets- At most 20 wallets, each wallet once. Each address fits its own family, one of EVM, SOLANA, TRON or TON.
The session's standing tells you where it stands. Only a connected session grants anything, and the read on your server is the one authority.
pending- The session is waiting: the hosted page has not been opened yet. Nothing was granted. This is not final: it can still move on.
opened- The hosted page is open and the person has not finished there yet. Nothing was granted. This is not final: it can still move on.
connected- The session is connected. Yutai holds the link, and your own server reads it before it grants anything. This is final.
cancelled- The person closed the hosted page without linking a card. Nothing was granted. This is final.
failed- The session did not link a card. Nothing was granted. This is final.
expired- The session ran out of time before anyone opened it. Nothing was granted. This is final.
A failed session carries exactly one reason from the list below. The bank's or provider's own message never rides a session, so your branch is a closed list.
consent_declined- The person chose not to give their consent. Offer the link again when the person is ready.
bank_unavailable- The chosen bank could not be reached. Ask the person to try again later.
country_not_supported- Yutai cannot link banks in the person's country yet. Show nothing about the bank: Yutai does not reach it in that country yet.
Read the standing and the failure reason on your server before you grant anything:
// On your server, never in the browser: the read is the one authority.
const { status, failureReason } = await api.readConnectSession(id);
if (status === "connected") {
// Grant only now, for this session's externalRef.
} else if (status === "failed") {
// failureReason is one of consent_declined, bank_unavailable, country_not_supported.
}The API reference carries each call's own page, with its fields, answers and sample: createConnectSession and getConnectSession.
The script
Yutai serves the SDK's script at the address below: the script loads once, then the element is written with its facts as attributes.
The script is served today.
<script src="https://app.yutai.com/sdk/v1/yutai-sdk-web.min.js"></script>
<yutai-connect-button session-url="https://app.yutai.com/connect/cxs_01J9ZQ4V8W7X6Y5Z4A3B2C1D0E?t=example-token"></yutai-connect-button>The sample
The SDK draws the button with the Yutai seal and the words below. A distributor may change the words; the seal never changes.
The attributes
Both elements take the same three attributes. The button opens the consent screen; the manage element opens the manage screen.
session-url- Required. The session's hosted address your server's create answer carried, on https://app.yutai.com, with the path /connect/ and the session's own cxs_ id. An address on any other origin, or one whose id is not a session id, is refused before a window opens.
label- Optional. The button's words. The default is "Link a card to unlock more" on the button and "Manage or disconnect" on the manage element; the Yutai seal beside them never changes.
view- Optional. "consent" or "purchases". The button defaults to consent, the manage element to purchases, and a value the element does not take is refused in plain words.
A page with its own button calls the same door, Yutai.connect, and gets the answer as a promise:
import { Yutai } from "@yutai/sdk-web";
document.querySelector("#link-card").addEventListener("click", async () => {
const { session, status, sentence } = await Yutai.connect({ sessionUrl });
// status is the session's standing: connected, cancelled, failed or expired.
if (status === "failed" && sentence) {
// Only a failed session carries a sentence: show it to the person.
}
console.log(session, status);
});A React page draws both presses through the package's own wrappers, which hear the same result:
import { ConnectButton, ConnectManage } from "@yutai/sdk-web/react";
<ConnectButton
sessionUrl={sessionUrl}
onResult={({ session, status, sentence }) => {
if (status === "failed" && sentence) {
// Only a failed session carries a sentence: show it to the person.
}
// Read the session again on your server before you grant anything.
}}
/>
<ConnectManage sessionUrl={sessionUrl} />Styling parts
A part is a named piece inside the elements that a page's own ::part rule reaches. The list is closed: a name the elements do not carry matches nothing.
connect- The press both elements draw: its surface, its words and the Yutai seal inside it. The button and the manage element carry the same part name, so one rule restyles both.
yutai-connect-button::part(connect) { background: navy; color: white; }The tokens
Set the tokens you want on the element itself, the way the member template does: a value the element carries beats its own binding.
--accent- The primary action colour.
--accent-foreground- The text colour that sits on the primary action colour.
--focus- The colour focus rings wear.
--link- The colour a link wears.
--radius- The corners the element rounds with, 0 to 0.75rem.
--yutai-font-sans- The font stack the element's text renders in.
--background- The page background around the element, from the approved neutral list.
The result
A popup's hosted page posts one message back to the page that opened it: its type is yutai.connect.result, and its origin is exactly https://app.yutai.com. The SDK accepts only that origin and only the session the button opened, so another page's message cannot claim a link was made. The elements dispatch one yutai-connect event with the same fields.
session- The session's own id, the one the address carried. It is never a customer identifier.
status- The session's standing when the hosted page ended: connected, cancelled, failed or expired.
sentence- Only a failed session carries it: the hosted page's own plain sentence saying why the link did not happen. A connected, cancelled or expired result carries none, and your server's read stays the one authority.
Treat the message as a signal to read the session again on your server, never as the grant itself. The read call is the one authority: readConnectSession names the standing, and a session that is not connected grants nothing.
window.addEventListener("message", (event) => {
if (event.origin !== "https://app.yutai.com") return;
const { type, session, status, sentence } = event.data;
if (type !== "yutai.connect.result") return;
if (status === "failed" && sentence) {
// Only a failed session carries a sentence: show it to the person.
}
// Read the session again on your server before you grant anything.
});Manage and disconnect
A holder without a Yutai account still exercises their choices. Opening a session for an already linked external reference shows the manage screen: which bank is linked, when consent ends, and the two presses to renew the consent or disconnect. A settings page writes the manage element so the person reaches that screen in one press.
<yutai-connect-manage
session-url="https://app.yutai.com/connect/cxs_01J9ZQ4V8W7X6Y5Z4A3B2C1D0E?t=example-token"
label="Manage my links"
></yutai-connect-manage>A React page writes the same press as the ConnectManage component, documented on the React page. The SDK only opens the screen; the hosted page owns every action on it. The Renew press shows the same consent card again and records a fresh consent, and the Disconnect press ends the link.
Outcomes and notices
Connect answers outcomes, never data. The outcomes read, GET /v1/connect/outcomes, takes your own external reference and answers whether the link stands, the day consent ends, and for each company the person holds whether they are also its customer, from which month, and the benefits that opens. The API reference documents it and lists it as callable today. The session calls and the outcomes read need a secret key carrying connect.write or connect.read.
The Webhooks page names the three Connect notices a subscription can ask for: a link made, a link ended and an owner-customer change. Yutai does not send them yet.
Unlink a customer
The unlink call, DELETE /v1/connect/links/{external_ref}, takes the same reference your create took and ends that person's link for the calling distributor, answering 204 with no body; nothing else changes. It ends the link without the person visiting the hosted flow.
An unknown reference, a reference that names no link, and a reference another distributor linked all answer the same 404, so no reference can be probed.
The API reference carries the call's own page, with its answers, its sample and its state.
Coverage and data
Yutai reads a holder's card spending through the bank connections it supports. The session's context names the company, security or ISA the offer is for, so the hosted page can tell the holder why it asks.
Yutai's open banking network reaches 12,000+ banks and financial institutions across 50+ countries; availability varies by country and bank.
What comes back to you is an outcome tied to your own external reference: whether the link connected, and the benefits the holder now reaches. Bank names, account numbers, balances and transaction lines stay with Yutai and the bank. Do not ask a holder to type a bank credential into your page: the hosted flow exists so that never happens.
Phones and redirects
On a computer the SDK opens a popup and waits for the result message. On a phone's browser a popup is a second tab the person cannot find, so the SDK moves the whole page to the hosted flow instead. The return address the session was created with brings the person back, and your return page reads the session's status on the server.
In a native app, open the session address in the phone's system browser: SFSafariViewController on iOS and Android Custom Tabs on Android, with the app mode and your app's own return scheme. Never open it in a web view: a bank may refuse to sign in there, and the person cannot see the address they are trusting.
The example wallet's Connect section runs the flow end to end against the simulated bank, and Errors and limits explains every refusal.