Drop-in SDK: TensorRail.js
Card acceptance switches on when a card acquirer is enabled for your account — ask your account manager; no rail enabled today takes cards. Build on this page once they confirm one.
The card shape does not change when it does. The only card shape the API accepts is an opaque
token minted by the processor's own PCI-compliant secure fields, and a raw card number is refused
in every mode, sandbox included, with ERR_2029 — by design, no card number reaches TensorRail.
Meanwhile the rails you already have take payments through Payment links, Hosted checkout or the Direct API. Your dashboard shows the methods enabled on your account.
The Drop-in SDK bundle is served from https://js.tensorrail.com/v1/TensorRail.js and
talks to the TensorRail API at api.tensorrail.com. See the
SDK Overview for details. If you'd rather not write frontend
payment code at all, the Hosted Checkout
is the fastest path.
TensorRail.js renders a secure, pre-styled payment form on your checkout page. Customers enter card data directly into TensorRail's hosted secure fields, so it never touches your servers.
All API calls use the TensorRail API at api.tensorrail.com with the api-key: header. See Authentication. To look up payment history, see the Reporting API.
When to use the Drop-in SDK
- You want a pre-built, styled payment form embedded in your page.
- You want to keep card data off your servers.
- You want to support multiple payment methods with one integration.
How it works
Your backend creates the payment with the secret key; the browser mounts the element with the scoped client_secret; the customer pays inside the iframe; confirmPayment finalizes; the webhook delivers the authoritative result.
End-to-end: the client_secret flow
- Backend calls
POST /paymentswith your secret key (rail_<type>_test_…/rail_<type>_live_…), an integeramountin minor units (e.g.5000= $50.00 USD), andcurrency. Passreturn_urlfor redirect flows and anIdempotency-Key(a UUID) on every create. - The API returns a
payment_id(e.g.pay_…), astatus, and aclient_secretscoped to that one payment. - Frontend loads TensorRail.js with your publishable key, then passes only the
client_secretintoelements({ clientSecret }), never the secret key. - The customer pays in the iframe.
confirmPaymentfinalizes and redirects toreturn_url. Always verify the result server-side (the Reporting API or a webhook). - The webhook delivers the final status to your backend; verify the
TensorRail-Signaturesignature (HMAC-SHA512) before fulfilling the order.
Step-by-step
1. Create a payment (backend)
Call POST /payments with your secret key. This returns the client_secret your frontend needs.
| Param | Required | Notes |
|---|---|---|
amount | Yes | Integer, minor units (cents). 5000 = $50.00. Never send decimals. |
currency | Yes | ISO 4217 code, e.g. USD. |
return_url | Recommended | Where the customer lands after confirmPayment. |
Idempotency-Key (header) | Recommended | A UUID; makes retries safe. See Idempotency. |
curl -X POST https://api.tensorrail.com/payments \
-H "api-key: rail_full_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"amount": 5000,
"currency": "USD",
"return_url": "https://yoursite.com/payment/complete"
}'
Responses also return amount in minor units.
Response (includes client_secret):
{
"payment_id": "pay_N5cPeGw6uS2QIMjnsjVF",
"client_secret": "<client_secret>",
"status": "requires_payment_method"
}
Pass client_secret to your frontend. Never expose your secret API key to the frontend.
2. Include TensorRail.js (frontend)
Load the SDK from the TensorRail CDN:
<script src="https://js.tensorrail.com/v1/TensorRail.js"></script>
There is one bundle and one host. For test mode, pass a rail_open_test_
publishable key: the key selects the mode, and the same
js.tensorrail.com/v1/TensorRail.js serves both test and live. The runtime's own
assets and the payment iframe are fetched from js.tensorrail.com, so your
Content-Security-Policy must allow that host.
Using a bundler (React, Vue, Vite, webpack)? Install the npm loader instead of a script tag:
npm install @tensorrail/sdk, thenconst tensorrail = await loadTensorRail("rail_open_test_…"). It injects the same CDN runtime and returns the initialized instance. See the SDK Overview.
3. Initialize and mount the payment form
Your publishable key lives in the Dashboard under Developers → API Keys, prefixed rail_open_<mode>_ (e.g. rail_open_test_…). Use a test-mode publishable key (rail_open_test_…) for testing and a live key (rail_open_live_…) for production traffic.
<div id="payment-form"></div>
<!-- UI label can show dollars; the payment was created with amount: 5000 (minor units) -->
<button id="pay-button">Pay $50.00</button>
<script>
// Backend API base URL for the SDK to call:
// - Sandbox development: https://api.tensorrail.com (with rail_full_test_ keys)
// - Production: https://api.tensorrail.com (with rail_full_live_ keys)
const tensorrail = window.TensorRail("your_publishable_key", {
customBackendUrl: "https://api.tensorrail.com",
});
const elements = tensorrail.elements({ clientSecret: clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-form");
// Handle submission
document.getElementById("pay-button").addEventListener("click", async () => {
const { error, paymentIntent } = await tensorrail.confirmPayment({
elements,
confirmParams: {
return_url: "https://yoursite.com/payment/complete",
},
});
if (error) {
console.error(error.message);
}
// Customer is redirected to return_url on success
});
</script>
Set customBackendUrl to https://api.tensorrail.com while testing (rail_<type>_test_… keys). For live traffic, use https://api.tensorrail.com and your live publishable key.
4. Handle the return URL
After payment, the customer lands on your return_url with query parameters:
https://yoursite.com/payment/complete?payment_id=pay_N5cPeGw6uS2QIMjnsjVF&status=succeeded
Never trust the redirect alone. Verify status server-side before fulfilling: process the webhook or look up payment_id via the Reporting API.
Wallets (Apple Pay / Google Pay)
Digital-wallet payment sheets are rendered in the browser. This is a publishable-key (client-side) flow, not a server-to-server call. TensorRail.js requests the wallet session tokens for you when you mount the payment element, so in most integrations you never call the endpoint directly.
When you need to drive a custom wallet button yourself, request the session
tokens from the browser with the payment's client_secret and your
publishable key (rail_open_*):
POST /payments/session_tokens (client-side, publishable key)
| Field | Required | Description |
|---|---|---|
payment_id | Yes | The payment to create wallet session tokens for. |
client_secret | Yes (browser) | The payment's client_secret, when called from the browser with a publishable key. |
wallets | No | Restrict to specific wallets (e.g. ["apple_pay"]). Defaults to all enabled wallets. |
// In the browser, with the payment's client_secret and your publishable key.
const res = await fetch("https://api.tensorrail.com/payments/session_tokens", {
method: "POST",
headers: {
"api-key": "rail_open_test_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
payment_id: "pay_N5cPeGw6uS2QIMjnsjVF",
client_secret: clientSecret,
wallets: ["apple_pay", "google_pay"],
}),
});
const { session_token } = await res.json();
// Feed session_token into the Apple Pay / Google Pay sheet.
The response carries a per-wallet session_token array your wallet button uses
to render its sheet. Because this runs client-side with a publishable key, it
never handles a secret key; keep secret keys on your backend.
Errors
confirmPayment returns { error } on a problem; your backend create call returns a { "error": { "code": "ERR_NNNN" } } body. Common cases:
| Code | Meaning | Action |
|---|---|---|
ERR_2001 / ERR_2002 | Missing or malformed field on create | Fix the request payload before retrying. |
ERR_2004 | Unsupported currency — raised on settlement, not on payment create | Settle in a currency your account holds a balance in. |
ERR_3018 | Duplicate resource: a payment_id was reused for a different payment | Use a fresh payment_id per distinct payment. |
ERR_4094 | Payment declined | Show error.message; do not fulfill the order. |
ERR_5099 | Internal error | Retry with backoff; reconcile via webhook. |
See Error catalog for the full ERR_* reference.
Edge cases
| Situation | What happens | What you should do |
|---|---|---|
| Card declined | confirmPayment may return { error } or the payment moves to failed | Show the message from error.message or unified error fields; do not fulfill the order. |
3DS / authentication (requires_customer_action) | Status indicates customer action; redirect may be required | Follow next_action / redirect flow; after return, confirm status via webhook or the Reporting API. |
| Network or SDK error | fetch or SDK fails before a definitive status | Retry idempotently only when safe; show a generic error; reconcile later via webhooks. |
| Webhook arrives before redirect | Final status may be known server-side first | Always treat the webhook (verified with TensorRail-Signature, HMAC-SHA512) as the source of truth for fulfillment. |
Never rely on client-side success alone. Note: a webhook payload's top-level keys are merchant_id (a string), event_id, event_type, content, and timestamp; the payment object is nested at content.object. See Webhooks for the full shape.
Customization
Theme TensorRail.js to match your brand:
const elements = tensorrail.elements({
clientSecret: clientSecret,
appearance: {
theme: "flat",
variables: {
colorPrimary: "#4F7CFF",
fontFamily: "Inter, sans-serif",
borderRadius: "8px",
},
},
});
Supported payment methods
The Drop-in SDK renders the methods enabled for your account automatically, matched to the
customer's market and the payment's currency, so you do not hard-code a method list and do not
re-integrate to add one. The SDK asks the platform which methods apply to this payment at
render time, so you never enumerate them yourself. The set enabled on your account is visible in
your dashboard, and your account manager can extend it — see
Corridors and coverage. (The unauthenticated
GET /v1/capabilities catalogue lists what the
platform supports overall; it is not per-account.)
Security
- Card data goes straight into TensorRail's hosted secure fields; it never reaches your servers.
- The
client_secretis safe on the frontend: it can only confirm the specific payment it was created for. - Always verify status server-side (API or webhook) before fulfilling orders.
Complete example
Save as checkout.html and serve over HTTPS (or a local static server). Replace your_publishable_key with your Dashboard publishable key (rail_open_test_… in test mode) and client_secret with the value your backend got from POST /payments. Your server must call TensorRail with the secret key; never put it in HTML.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>TensorRail Checkout</title>
<script src="https://js.tensorrail.com/v1/TensorRail.js"></script>
</head>
<body>
<div id="payment-form"></div>
<button id="pay-btn" type="button">Pay $50.00</button>
<script>
// 1) Backend: POST https://api.tensorrail.com/payments
// with header "api-key: rail_..." + Idempotency-Key → { client_secret, payment_id, ... }
const clientSecret = "REPLACE_WITH_client_secret_FROM_YOUR_BACKEND";
const tensorrail = window.TensorRail("your_publishable_key", {
customBackendUrl: "https://api.tensorrail.com",
});
const elements = tensorrail.elements({ clientSecret: clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-form");
document.getElementById("pay-btn").addEventListener("click", async () => {
const { error } = await tensorrail.confirmPayment({
elements,
confirmParams: { return_url: "https://yoursite.com/success" },
});
if (error) console.error("Payment failed:", error.message);
});
</script>
</body>
</html>
Related
- Server-to-Server: full backend control
- 3D Secure: handle
requires_customer_actionchallenges - Webhooks: verified status events (
TensorRail-Signature) - Error catalog: the full
ERR_*reference - Idempotency: safe retries on create