Skip to main content

Payment links

A payment link is a shareable hosted-checkout URL: the same TensorRail-hosted payment page as hosted checkout, but sent to the customer instead of redirected to. You send the link by email, chat, invoice, or social, and the customer opens it and pays. The outcome lands on the same webhooks, the same balance, and the same reporting as every other payment.

This is one of three integration paths. It needs the least engineering of the three: you can create a link with no code from the dashboard, or with a single API call.

When to use it

  • Invoicing. Attach a pay-now link to an invoice or quote.
  • No website, or no dev time yet. Collect before you have a checkout built.
  • Sales-led or social. Send a link over chat, email, or a social channel.
  • One-off collections. Deposits, custom orders, ad-hoc charges.

If you want to redirect a customer mid-checkout on your own site, use hosted checkout. If you want the payment experience inside your own UI, use the direct API.

From the dashboard: Payment Links → enter amount, currency, and an optional description → Create. You get a URL to copy and share. This needs no engineering at all, and the resulting payment flows into the same reporting and webhooks as any API-created payment.

One call prepares the collection, and you build the shareable URL from its response. It produces the same kind of pay.tensorrail.com link as the dashboard's Payment Links page — the hosted page described in hosted checkout.

The link's environment follows your API key. Prepare with a rail_*_test_* key and the link runs against the sandbox (test money, test methods); prepare with a rail_*_live_* key and the link collects real money. A live prepare on an account that has not completed live activation is refused with 409 — you cannot create a live-looking link that silently routes to the sandbox.

Step 1: Prepare the collection

POST /v1/payment-links/prepare creates the underlying payment for the link and returns the identifiers you need. It lives on the /v1 surface, so authenticate with your secret key (rail_full_test_… / rail_full_live_…) as a bearer token — see Authentication.

Use the secret key, not the publishable key

Both key types begin rail_, which makes them easy to confuse. Prepare needs the payments:write scope, and a publishable key (rail_open_…) never carries it — passing one fails with ERR_1002 invalid_api_key, and passing it without Bearer fails with ERR_1011 unauthorized. The publishable key is what this endpoint returns to you, for the browser step that follows.

Body: amount_minor (required), currency (required), description (optional), and return_url (optional — where the payer lands after completing payment; defaults to the hosted page's completion screen).

curl -X POST https://api.tensorrail.com/v1/payment-links/prepare \
-H "Authorization: Bearer rail_full_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": 50000,
"currency": "INR",
"description": "Invoice INV-3391"
}'

The response carries the prepared collection:

{
"payment_id": "pay_N5cPeGw6uS2QIMjnsjVF",
"client_secret": "pay_N5cPeGw6uS2QIMjnsjVF_secret_k9Yt…",
"publishable_key": "rail_open_test_xxx",
"amount_minor": 50000,
"currency": "INR",
"url": "https://pay.tensorrail.com/?amount=50000&client_secret=pay_N5cPeGw6uS2QIMjnsjVF_secret_k9Yt…&currency=INR&mode=link&payment_id=pay_N5cPeGw6uS2QIMjnsjVF&pk=rail_open_test_xxx"
}

Step 2: Send the URL

Use the url from the response. It is the finished shareable link — send it to the customer as-is. It opens the same hosted page as hosted checkout.

Each link maps to exactly one payment_id, so a link is single-use for one collection: once that payment succeeds, it is done.

Do not assemble the URL by hand

amount and currency in the link are what the hosted page displays. The server builds url from the amount and currency the engine actually created, so the payer always sees the figure they will be charged. Typing the URL yourself reintroduces the ways it can go wrong: a missing client_secret (the page then has no payment to load), a mistyped pk (method enumeration fails), or a display amount that does not match the payment.

If you must construct it yourself — an older integration, or a link rebuilt from stored identifiers — the shape is:

https://pay.tensorrail.com/?mode=link&payment_id=PAYMENT_ID&client_secret=CLIENT_SECRET&pk=PUBLISHABLE_KEY&amount=50000&currency=INR

All of mode, payment_id, client_secret and pk are required. A URL that says mode=link but omits payment_id or client_secret is rejected with Invalid checkout link rather than rendering a partial page.

GET /v1/payment-links on the account and reporting API returns the links your account has saved, for reconciliation and display. To make a prepared link appear in that list, persist it with POST /v1/payment-links (amount_minor, currency, optional description, and the built url); links created from the dashboard are saved there automatically.

Sharing and expiry

RuleDetail
A link is just a URLSend it however you reach the customer — email, chat, invoice PDF, a message. It opens the same hosted page as hosted checkout, so every enabled method is presented and new methods appear automatically.
One link, one paymentEach link maps to one payment_id. Do not reuse a link across customers or collections — create a fresh link per invoice.
Valid for 24 hoursA payment that is never completed expires after that window. See expires_on on the payment object and the payment_expired webhook.
Expired means finishedTreat it like a failed attempt. If the customer still wants to pay, create a new payment and a new link rather than reviving the old one.

Reconciling the resulting payment

A payment created by a link is an ordinary payment, so you reconcile it exactly like any other:

  • Join by payment_id. Store the payment_id from the prepare response against your invoice or order. It is on the payment object and on every webhook, so links you send by hand still land in your automated fulfilment and reporting.
  • Confirm by webhook. Treat the invoice as paid only on the payment_succeeded webhook (or a retrieve returning succeeded), never because the customer opened the link. Local rails complete asynchronously.
  • Reconcile in reporting. The payment appears in your transaction history and exports the same as any collection, and posts against the same ledger under your balance. See Money integrity.

Complete worked example: invoice a customer

const SECRET = process.env.TENSORRAIL_SECRET_KEY;

async function sendInvoiceLink(invoice) {
// 1. Prepare the collection behind the link.
const r = await fetch("https://api.tensorrail.com/v1/payment-links/prepare", {
method: "POST",
headers: {
Authorization: `Bearer ${SECRET}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount_minor: invoice.amount_minor,
currency: invoice.currency,
description: `Invoice ${invoice.number}`,
}),
});
const prepared = await r.json();

// 2. Build the shareable hosted-page URL.
const url = new URL("https://pay.tensorrail.com/");
url.searchParams.set("mode", "link");
url.searchParams.set("payment_id", prepared.payment_id);
url.searchParams.set("client_secret", prepared.client_secret);
url.searchParams.set("pk", prepared.publishable_key);
url.searchParams.set("amount", String(prepared.amount_minor));
url.searchParams.set("currency", prepared.currency);

// 3. Store the link against the invoice (join by payment_id), then email it.
await invoices.attachPayment(invoice.number, prepared.payment_id, url.toString());
await email.send(invoice.customer_email, {
subject: `Invoice ${invoice.number}`,
body: `Pay securely here: ${url.toString()}`,
});
}

// 4. Mark the invoice paid from the webhook, never from a page open.
async function onWebhook(event) {
if (event.content.object.status === "succeeded") {
const invoice = await invoices.findByPaymentId(event.content.object.payment_id);
if (invoice) invoices.markPaid(invoice.number);
}
}

Next steps

You can send a payment link. To close the loop:

  • Handle webhooks: mark the invoice paid from the authoritative event, not a page open.
  • Hosted checkout: the same page redirected to mid-checkout when you want an inline flow.
  • Testing and sandbox: exercise a link end to end on test keys before going live.

TensorRail, Limassol, Cyprus.