Skip to main content

Orchestration and routing

Enabled per account

Everything on this page is a capability we enable for an account, not behaviour that is on by default. Whether routing and decline recovery are switched on for your account is account configuration: ask your account manager where your account stands, and do not build against the behaviour described here until it is enabled for you. What IS ambient, on every payment, is the one error contract described in Error intelligence.

When it is enabled, TensorRail takes a single payment and places it across the rails available for your account: choosing an approval-optimized route, applying rule-based fallback, and recovering a recoverable decline by re-attempting on an alternative. All of it happens behind the one payment object — you send one payment, and TensorRail works out how best to get it approved.

What you get

  • Approval-optimized routing. A payment is routed to an eligible path for its market, method, currency, and account configuration.
  • Cross-processor decline recovery. When an attempt returns a recoverable decline, the platform can automatically re-attempt the same payment on an alternative eligible route, without you re-submitting, up to your account's configured policy, before returning a terminal outcome.
  • One outcome surface. You see the final result on the payment object and the webhook. You never pick a route and never learn which one was used.

You do not build, tune, or maintain any of this. The value shows up as a higher realized approval rate and resilience against transient failures.

Why this lifts approval rates

A single-route setup converts every decline into lost revenue, even when the decline is transient: a rail timing out, a route momentarily degraded, an attempt that a different eligible path would have approved. Orchestration changes the economics of exactly those cases:

  • Declines are classified, not treated alike. A terminal decline (for example an invalid instrument) is returned to you immediately; a recoverable one is eligible for another attempt.
  • Recovery happens inside the same payment. There is one payment_id, one amount, one eventual outcome, one webhook. Recovery attempts can never double-collect, because they are attempts of the same payment, not new payments.
  • The policy is per account. How aggressively recovery runs is account configuration, not code you write, and it improves without you shipping anything.

What you observe from the outside: the payment stays processing slightly longer, then resolves to succeeded or failed. A payment that would have died on a single route comes back approved, and your integration cannot tell the difference, which is the point.

Safe client retries with Idempotency-Key

Separately from platform-side recovery, 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/refunds does compare bodies and rejects a mismatch with ERR_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_id on create. Creation is unique on it, but it behaves the other way round: a second create with the same payment_id is rejected with ERR_3018 (duplicate) rather than replayed, so on ERR_3018 retrieve the existing payment. It is the right tool when you already have a natural unique order reference; the Idempotency-Key is the right tool for a network retry.

The same Idempotency-Key contract applies to POST /refunds; 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 orchestrationOn TensorRail
Per-rail integrations and their maintenanceOne create/confirm/webhook path
Decline taxonomies per railOne unified error catalog (Error intelligence)
Retry and failover logic, with double-charge riskIn-payment recovery plus enforced idempotency
Per-rail reconciliationOne ledger-backed balance (Money integrity)

How this fits the platform

Orchestration is the layer that sits under your payments once it is enabled for your account. It routes each one across the rails available to you, recovers declines where it safely can, and returns a single unified result, so the same integration reaches more markets and more approvals without you building any of it. The routing decision logic itself is internal to the platform and not merchant-configurable surface area; you get its results, not its knobs. See The platform.


TensorRail, Limassol, Cyprus.