How it works: the payment lifecycle
Every payment on TensorRail moves through the same four stages, whatever the market or method:
create then route then authorize/collect then record, with webhooks throughout. The
merchant-visible object is always a payment with an amount, a currency, a status,
and a client_secret, identical across hosted checkout, payment links, and the direct API.
1. Create
You call POST /payments with an amount (in the smallest currency unit) and a currency
(which must be a corridor enabled for your account):
curl -X POST https://api.tensorrail.com/payments \
-H "api-key: rail_full_test_xxx" \
-H "Idempotency-Key: 5f0c9e2a-1b7d-4d3e-9a1c-6b2f8e4a1c33" \
-H "Content-Type: application/json" \
-d '{ "amount": 50000, "currency": "INR", "return_url": "https://yourshop.com/done" }'
{
"payment_id": "pay_N5cPeGw6uS2QIMjnsjVF",
"status": "requires_payment_method",
"amount": 50000,
"currency": "INR",
"client_secret": "pay_N5cPeGw6uS2QIMjnsjVF_secret_…"
}
Two identifiers come back and each has a distinct job: payment_id is the server-side handle
you store against your order; client_secret is the browser-safe credential that lets the
hosted page (or a client-side confirm with your publishable key) act on exactly this one
payment and nothing else.
Creation is idempotent, and idempotency is enforced: retrying this call with the
same Idempotency-Key returns the original payment instead of creating a duplicate
collection. See Orchestration and routing.
2. Route and present
You either hand off to the hosted page, which renders every enabled method, or render the
enabled methods yourself and confirm the customer's choice. Behind the single surface,
TensorRail selects and drives the appropriate rail. You never pick a processor and never see
which one was used; the platform chooses an approval-optimized route for the payment's market,
method, currency, and account configuration.
3. Authorize and collect
The customer completes on their local rail. Many rails require a customer action: an approval
in a bank or wallet app, a redirect, or a reference to pay to. This is surfaced as a generic
next_action on the payment object that you simply forward the customer to, while the
payment sits at status: "requires_customer_action". Once the rail has the payment, status
moves to processing, and the terminal outcome (succeeded or failed) arrives by webhook.
The same handling works as new methods are added, because your code never branches on the
method.
return_url is not proof of paymentLocal rails complete asynchronously after the customer leaves checkout. The authoritative
signal is the success webhook (or a retrieve returning status: "succeeded"), never the
customer landing back on your return_url. A customer can close the tab after paying, or
return without paying; your fulfilment must key off the webhook.
4. Record
Cleared value is recorded to your account balance for the currency it was collected in. Under that balance sits a double-entry ledger with automated reconciliation, so the number is provable rather than merely displayed: every fee is its own posting, and every posting names the payment it belongs to. See Money integrity for how the balance is backed and reconciled, and Fees and currency for where fees and conversion appear.
Status vocabulary
Branch your logic on the payment status, never on message text. These are the values you
meet on the common paths:
| Status | Meaning | Your move |
|---|---|---|
requires_payment_method | Created; no method submitted yet | Present methods; confirm |
requires_confirmation | Method attached; needs confirmation | Call confirm |
requires_customer_action | Waiting on the customer (approval, redirect, reference) | Forward the customer to next_action |
processing | Submitted to the rail; outcome pending | Wait for the webhook |
requires_capture | Authorized; awaiting capture (manual capture flow) | Capture on fulfilment |
succeeded | Collected | Fulfil (on the webhook) |
failed | Did not complete | Read error_code / error_message; offer retry |
cancelled | Voided before completion | Nothing further |
The table above is the set you meet on the common paths, not the complete enum. A collection the
customer never completes in time ends at expired — on live traffic that is the most frequent
terminal outcome of all, so handle it first. Partial-capture flows add partially_captured,
partially_captured_and_capturable, partially_authorized_and_requires_capture and
partially_captured_and_processing; a payment awaiting an action on our side reports
requires_merchant_action; a void after capture reports cancelled_post_capture; and a
payment whose amount or currency does not match what the processor reports is conflicted —
do not fulfil that one.
Which of these a given integration meets depends on the payment method and on what is enabled
for your account, so give your switch a default that treats an unknown status as "not yet
terminal, wait for the webhook" rather than as a failure. That way a method you enable later
cannot turn into a wrongly-failed order.
Two of these are terminal (succeeded, cancelled); failed is terminal for that payment,
but the customer can try again with a new payment. A payment that is never completed can
also expire (you receive a payment_expired event); treat it like a failure for fulfilment
purposes.
The same trace, end to end
A typical asynchronous local-rail collection looks like this on the wire:
POST /paymentsreturnsstatus: "requires_payment_method".POST /payments/{payment_id}/confirmwith the customer's method returnsstatus: "requires_customer_action"plus anext_action.- The customer approves on their rail; a
payment_processingwebhook may arrive. - The
payment_succeededwebhook arrives with the full payment object incontent;GET /payments/{payment_id}now returnsstatus: "succeeded". - The cleared value posts to your balance and appears in your exports.
Every method family (mobile money, bank transfer, virtual account, local instant rails, crypto) is a variation of this one trace; only what the customer does at step 3 differs.
Webhooks throughout
Outcome events (payment_processing, payment_succeeded, payment_failed, and the rest of
the payment lifecycle) are delivered to your endpoint at each state change, signed, with
retries on failure. Webhooks are the reliable way to learn outcomes because rails complete
asynchronously. See Handle webhooks.
TensorRail, Limassol, Cyprus.