Orchestration and routing
Routing is account configuration that TensorRail sets up and manages for you; ask your account manager to change yours. What applies to every payment is the one error contract described in Error intelligence and the idempotency contract below.
TensorRail takes a single payment and routes it to a rail enabled for your account, under the routing set up for your account: which of the eligible rails carries a payment, by its market, method and currency. All of it happens behind the one payment object: you send one payment, and you get one outcome back.
What you get
- Rule-based routing. A payment is routed to an eligible rail for its market, method and currency, in the priority set up for your account.
- One outcome surface. You see the final result on the payment object and the webhook. You do not need to pick a route, and you never learn which one was used.
- Declines you can act on. A decline comes back as a stable code from the unified error catalog, and the Error catalog says for each code whether a new attempt can succeed.
You do not build, tune, or maintain the routing logic.
Declines and new attempts
Integrating rails one by one means learning each rail's dialect of failure before you can decide what to do next. On TensorRail that decision starts from one code:
- Declines are classified, not treated alike. A terminal decline (for example an invalid instrument) will fail again however often it is attempted; for other codes the catalog says a new attempt can succeed.
failedis terminal for the payment. When the customer tries again, that is a new payment with a newIdempotency-Key(below). A failed payment is never re-confirmed.
What you observe from the outside: the payment resolves to succeeded or failed, with the
same statuses and codes whatever rail carried it.
Safe client retries with Idempotency-Key
You make your own create calls safe to retry over a
flaky network by sending an Idempotency-Key header on POST /payments. Idempotency is
enforced on the payment-create path:
- The same key with the same body returns the original payment rather than creating a new one.
- The same key with a different body also returns the original payment. There is no
body-mismatch rejection on this path: the differing body is silently ignored, and you get the
first payment back with the first payment's amount. That makes a retry safe and a
key-generation bug invisible, so treat one-key-per-logical-attempt as your discipline rather
than something the API will catch for you. (The Reporting API's
POST /v1/refundsdoes compare bodies and rejects a mismatch withERR_3002; see Refunds via the Reporting API.)
Concretely, this timeout-retry sequence is safe:
# First attempt times out at your HTTP client, outcome unknown.
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" }'
# Retry with the SAME key and SAME body: returns the original payment,
# same payment_id, no second collection.
Rules of thumb:
- Generate one fresh UUID per logical payment attempt and reuse it across network retries of that attempt.
- When the customer deliberately tries again after a failed payment, that is a new logical attempt: create a new payment with a new key. Never reuse the old key for a different attempt.
- You can also supply your own
payment_idon create. Creation is unique on it, but it behaves the other way round: a second create with the samepayment_idis rejected withERR_3018(duplicate) rather than replayed, so onERR_3018retrieve the existing payment. It is the right tool when you already have a natural unique order reference; theIdempotency-Keyis the right tool for a network retry.
Refunds take an Idempotency-Key too, on POST /v1/refunds, where it is required and a reused key
with a different body is refused; see Refunds.
For the request shapes, see Accept a payment.
What you never have to build
Teams that run multi-market payments without an orchestration layer end up owning all of this themselves. On TensorRail it is platform behavior:
| Without orchestration | On TensorRail |
|---|---|
| Per-rail integrations and their maintenance | One create/confirm/webhook path |
| Decline taxonomies per rail | One unified error catalog (Error intelligence) |
| Retry logic, with double-charge risk | Enforced idempotency on payment and refund creation |
| Per-rail reconciliation | One ledger-backed balance (Money integrity) |
How this fits the platform
Orchestration is the layer that sits under your payments. It routes each one across the rails enabled for your account and returns a single unified result, so the same integration reaches more markets without you building any of it. Routing is account configuration that TensorRail manages, not merchant-configurable API surface; you get its results, not its knobs. See The platform.
TensorRail, Limassol, Cyprus.