Prompt

How do I set up a payment routing API for failover when an acquirer times out or declines?

Technology / Payments Infrastructure2 observationsLast seen Oct 19, 2026

Latest observation

Oct 19, 2026OpenAI APIWeb search: off

To set up a payment routing API with failover, you want a system that can:

  1. Send the payment to a primary acquirer
  2. Detect timeout/soft decline conditions
  3. Retry or reroute to a backup acquirer
  4. Avoid duplicate charges
  5. Keep a full audit trail

Here’s a practical blueprint.


1) Define your routing rules

Decide which failure cases should trigger failover:

Typical failover triggers

  • Timeouts: no response within your SLA
  • Soft declines: issuer/acquirer response suggests retry may succeed
  • Network errors: 5xx, connection reset, DNS failure
  • Technical rejects: acquirer unavailable, processing error

Usually do not fail over for:

  • Hard declines like:
    • insufficient funds
    • stolen card
    • expired card
    • invalid CVV
    • do-not-honor depending on your policy
  • These are cardholder/issuer decisions and usually won’t improve by switching acquirers.

A good rule is to maintain a decline classification map:

  • retryable
  • non_retryable
  • unknown

2) Use an idempotency key everywhere

This is critical. If the first acquirer actually processed the payment but your API timed out, a retry could double-charge the customer.

Requirements

  • Client sends idempotency_key
  • Your routing API stores:
    • request payload hash
    • status
    • chosen acquirer
    • acquirer reference IDs
    • final payment outcome

Behavior

If the same idempotency key is received again:

  • return the original result
  • do not re-authorize unless explicitly safe and intended

3) Create a routing decision engine

Your API should separate:

  • routing logic
  • acquirer adapters
  • payment state tracking

Example flow

  1. API receives payment request
  2. Routing engine selects primary acquirer
  3. Send request to acquirer A
  4. If response is:
    • success → return success
    • soft decline / timeout / technical failure → route to acquirer B
    • hard decline → return decline immediately

Routing logic inputs

  • card BIN / issuer country
  • payment amount / currency
  • merchant account
  • acquirer uptime / health
  • cost / authorization rate
  • MOTO / ecommerce / 3DS requirements
  • previous transaction history

4) Build acquirer adapters

Each acquirer should have a normalized interface.

Example internal interface

{
  "authorize": {
    "amount": 1000,
    "currency": "USD",
    "card_token": "tok_123",
    "merchant_id": "m_001",
    "idempotency_key": "abc-123"
  }
}

Each adapter translates this into the acquirer’s format and maps the response back to your standard model.

Normalize response fields

  • status: approved / declined / error / pending
  • reason_code
  • reason_category
  • acquirer_reference
  • network_reference
  • retryable: true/false

5) Implement failover carefully

Recommended retry policy

  • Timeouts: retry once on backup acquirer
  • Technical errors: retry once on backup
  • Soft declines: reroute to backup acquirer
  • Hard declines: no failover

Important

Do not blindly retry the same acquirer multiple times for card payments unless you have a reason. This can:

  • increase latency
  • increase duplicate risk
  • hurt approval rates

Example decision tree

Primary acquirer attempt
  ├─ Approved → done
  ├─ Hard decline → stop
  ├─ Soft decline → try backup
  └─ Timeout/error → try backup

6) Track payment state as a state machine

A payment should move through explicit states.

Example states

  • created
  • routing_selected
  • sent_to_acquirer_a
  • timeout_acquirer_a
  • sent_to_acquirer_b
  • approved
  • declined
  • manual_review
  • error

This makes it easier to:

  • debug issues
  • reconcile transactions
  • avoid double capture
  • expose status to clients

7) Prevent duplicate authorization/capture

This is one of the biggest risks.

Best practices

  • Use idempotency keys
  • Store acquirer authorization IDs
  • Before failover, determine whether the first attempt may still be “in flight”
  • If an acquirer supports it, use:
    • authorization reversal/void if a duplicate happens
    • transaction inquiry to verify final status

For async or delayed responses

Sometimes a timeout does not mean failure. The acquirer may later approve the transaction.

You need:

  • a callback/webhook or polling reconciliation job
  • a rule for late responses:
    • if backup already approved, you may need to void one authorization
    • if not, reconcile with the first acquirer’s final status

8) Add circuit breakers and health checks

If an acquirer is unhealthy, stop sending traffic to it for a while.

Circuit breaker logic

  • Monitor error rate, timeout rate, latency
  • If thresholds exceeded:
    • open circuit
    • route new traffic to backups
  • After cooldown:
    • half-open with small test volume
    • close if healthy

Also monitor

  • auth approval rate
  • timeout rate by card BIN/country
  • soft decline rate
  • webhook delivery failures

9) Make routing configurable

Avoid hardcoding routing rules.

Configuration examples

  • route by BIN range
  • route by currency
  • route by merchant
  • route by amount threshold
  • route by acquirer health score
  • percentage-based canary routing

Sample rule set

rules:
  - if: currency == "USD" and card_country == "US"
    prefer: acquirer_a
    fallback: acquirer_b

  - if: acquirer_a.timeout_rate > 5%
    disable: acquirer_a

  - if: amount > 50000
    require: acquirer_c

10) Expose a clean API

Example endpoint

POST /payments/authorize

Request

{
  "amount": 1000,
  "currency": "USD",
  "card_token": "tok_123",
  "merchant_id": "m_001",
  "idempotency_key": "pay-789",
  "metadata": {
    "order_id": "o-555"
  }
}

Response

{
  "payment_id": "pmt_123",
  "status": "approved",
  "acquirer": "acquirer_b",
  "acquirer_reference": "auth_9988",
  "fallback_used": true
}

If declined:

{
  "payment_id": "pmt_123",
  "status": "declined",
  "decline_category": "hard_decline",
  "reason_code": "05",
  "acquirer": "acquirer_a"
}

11) Reconciliation and reporting

You need a background process to:

  • compare internal payment records with acquirer settlements
  • catch late approvals
  • identify duplicates
  • reconcile reversals/voids
  • produce audit logs

This is essential if failover is used.


12) Security and compliance

  • Never store raw PAN unless PCI compliant
  • Use tokenization
  • Encrypt sensitive data in transit and at rest
  • Restrict acquirer credentials per environment
  • Log safely: avoid card data, CVV, secrets
  • Use signed webhooks and request authentication

13) Suggested architecture

Components

  • API gateway
  • Routing engine
  • Payment state store
  • Acquirer adapter services
  • Health monitor
  • Reconciliation worker
  • Webhook listener
  • Audit log store

Flow

Client → Payment API → Routing Engine → Acquirer A
                                  ↘ on failover → Acquirer B
                                 ↘ state store + audit logs

14) Implementation tips

  • Make timeouts short but realistic
  • Use asynchronous job processing for reconciliation
  • Record exact timestamps for each attempt
  • Store acquirer-specific request/response payloads securely for debugging
  • Make failover decisions deterministic and testable
  • Test with sandbox acquirers and injected failures

15) Simple pseudocode

def authorize_payment(request):
    if already_processed(request.idempotency_key):
        return previous_result(request.idempotency_key)

    route = choose_primary_acquirer(request)
    result = send_to_acquirer(route.primary, request)

    if result.approved:
        store_result(request.idempotency_key, result)
        return result

    if result.hard_decline:
        store_result(request.idempotency_key, result)
        return result

    if result.timeout or result.soft_decline or result.technical_error:
        backup = choose_backup_acquirer(request, exclude=[route.primary])
        backup_result = send_to_acquirer(backup, request)

        final = resolve_results(result, backup_result)
        store_result(request.idempotency_key, final)
        return final

16) Key policy decisions you should make up front

  1. Which decline codes are retryable?
  2. How long to wait before considering a timeout?
  3. How many failover attempts are allowed?
  4. Should failover be synchronous or asynchronous?
  5. What happens if both acquirers approve?
  6. How do you reverse one of two approvals?
  7. Which routes are preferred by region/BIN/currency?
  8. What is your reconciliation SLA?

If you want, I can also provide:

  • a reference API design
  • a database schema for payment routing
  • a state machine diagram
  • or sample code in Node.js, Python, or Java.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (2 observations for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.