> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.paymentkit.com/guides/payment-orchestration/routing-payments/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server. # Routing payments # Before you begin Make sure you have: * At least **two connected payment processors**. * Processor IDs handy (find them in **Orchestration > Payment processors** or via `GET /api/{account_id}/payments/processors`). > **Info** > > If you only have one processor connected, PaymentKit will only use your default processor. **At this time, payment routes can only be defined via the API.** --- # How payment routes work PaymentKit will try each processor in the order of your defined route until one succeeds. If a processor is unavailable or doesn’t support the currency, PaymentKit skips to the next processor automatically. ```mermaid flowchart TD A[Customer submits payment] --> B[Processor 1: Stripe] B -->|Success| C[Payment complete] B -->|Failed| D[Processor 2: Authorize.net] D -->|Success| C D -->|Failed| E[Processor 3: Airwallex] E -->|Success| C E -->|Failed| F[Payment failed] ``` Routes are resolved in precedence order: a route passed on the checkout session takes priority, then the account’s default route. If no route is configured, PaymentKit uses your default processor. ## Create a route ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/processor-routes \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "US Optimized", "description": "Primary route for US customers", "processors": [ { "processor_id": "proc_abc123" }, { "processor_id": "proc_def456" }, { "processor_id": "proc_ghi789" } ] }' ``` `name` and a non-empty `processors` list are required; `description` is optional. Response: ```json { "id": "proute_xyz789", "name": "US Optimized", "description": "Primary route for US customers", "processors": [ { "processor_id": "proc_abc123" }, { "processor_id": "proc_def456" }, { "processor_id": "proc_ghi789" } ], "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ``` ## Use a route in checkout Pass a single `processor_route_id` when creating a checkout session. The route itself contains an ordered list of processors — PaymentKit tries each one sequentially until a payment succeeds. You don’t pass multiple processor IDs directly; instead, define the processor list inside the route and reference the route by its ID. > **Info** > > **One route ID, multiple processors.** The `processor_route_id` field accepts a single route ID (e.g., `proute_xyz789`), not an array of processor IDs. To control which processors are used and in what order, configure them within the route via `POST /api/{account_id}/processor-routes`. For cascade pricing, each tier (`cascade_line_items[]`) can specify its own `processor_route_id` to use a different fallback chain. Pass the `processor_route_id` when creating a checkout session: ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/checkout-sessions \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cus_abc123", "line_items": [ { "price_id": "price_xyz", "quantity": 1 } ], "processor_route_id": "proute_xyz789", "success_url": "https://example.com/success", "return_url": "https://example.com/return" }' ``` ## Set a default route (optional) Apply a route to all checkout sessions without needing to specify it each time. Set `default_processor_route_id` to `null` to clear the default. ```bash curl -X PATCH https://app.paymentkit.com/api/accounts/{account_id}/default-processor-route \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "default_processor_route_id": "proute_xyz789" }' ``` ## Token fallback retries (CIT) When a customer pays with a network token and the charge is declined, PaymentKit can automatically retry the payment once using the card number instead of the token, on the same processor. This section covers that retry, how to tell it apart from a duplicate charge, and the other reasons a payment might fall back from a network token to a card number without a visible retry. This section covers checkout (CIT) payments. ### The retry: `soft_decline_token_error` If a network-token charge is declined for a reason unrelated to the card itself, for example a processor-side issue reading the token, PaymentKit retries immediately with the underlying card number. The card itself is usually fine; only the token attempt failed. * One retry, with the card number, on the **same processor** as the original attempt. * One retry per processor, per payment. * Declines that indicate a problem with the card itself never trigger this retry: lost or stolen card, pick-up card, expired card, incorrect CVC, and unsupported card. Retrying these with the same card would not change the outcome. This is the only fallback reason that produces **two attempts** on a payment, one `network_token` attempt followed by one `pan` attempt. Seeing both is expected, not a duplicate charge. > **Note** > > This retry is currently supported on a subset of processors. Coverage may expand over time; reach out to support if you need to confirm whether it's active for a specific processor on your route. #### Example: a fallback pair ```json { "external_id": "pa_001", "credential_source": "network_token", "fallback_reason": null, "cryptogram_generated_at": "2026-08-18T15:33:58.100000Z" } ``` ```json { "external_id": "pa_002", "credential_source": "pan", "fallback_reason": "soft_decline_token_error", "cryptogram_generated_at": null } ``` The first attempt is the original network-token charge. The second is the fallback retry, identified by `fallback_reason: soft_decline_token_error`. Only one of the two can succeed and result in a captured payment. If you use [processor cascading](#how-payment-routes-work), a payment can show this same two-attempt pair on each processor the route tries, not just once overall. A cascaded payment that falls back on every processor it attempts is still one customer charge, not one charge per attempt shown. ### Other fallback reasons (single attempt, no retry) The remaining `fallback_reason` values are resolved before any charge is attempted. In these cases the network token is never charged at all, so the payment shows a single `pan` attempt, not a pair: | `fallback_reason` | Meaning | | ------------------------ | -------------------------------------------- | | `no_token` | No network token was available to attempt | | `token_inactive` | The network token exists but isn't active | | `timeout_fallback` | Generating the token's cryptogram timed out | | `processor_unsupported` | The processor doesn't support network tokens | | `status_check_failed` | Couldn't confirm the token's status | | `cryptogram_unavailable` | No cryptogram was available for the token | | `sandbox_unsupported` | Network tokens aren't supported in sandbox | If you see one of these on a payment's single `pan` attempt, PaymentKit charged the card number directly. There is no preceding network-token attempt to reconcile against. ### Fields on a processor attempt | Field | Values | Description | | ------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | `credential_source` | `network_token`, `pan`, `null` | Which credential the attempt charged with. `null` on non-card payment methods (wallets, bank debits, etc.) | | `fallback_reason` | one of the 8 values above, or `null` | Set when the attempt is a fallback; `null` on a normal network-token or non-fallback PAN attempt | | `cryptogram_generated_at` | RFC 3339 timestamp, or `null` | When the network-token cryptogram was generated. Only set on `network_token` attempts | Two invariants hold on every attempt: * `fallback_reason` is only ever non-null when `credential_source` is `pan`. * `cryptogram_generated_at` is only ever non-null when `credential_source` is `network_token`. ### Where to find this Both attempts are visible on the payment's detail page in the dashboard, and via the processor attempts API: * `GET /api/{account_id}/payments/processor_attempts/{external_id}` — a single attempt * `GET /api/{account_id}/payments/processor_attempts/` — list, filterable by `credential_source` and `fallback_reason` * `GET /api/{account_id}/payments/processor_attempts/by_intent/{payment_intent_external_id}` — all attempts for a payment intent ## Manage routes List all routes: ```bash curl https://app.paymentkit.com/api/{account_id}/processor-routes \ -H "Authorization: Bearer sk_live_..." ``` Update a route: ```bash curl -X PATCH https://app.paymentkit.com/api/{account_id}/processor-routes/proute_xyz789 \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "US Optimized v2", "processors": [ { "processor_id": "proc_def456" }, { "processor_id": "proc_abc123" } ] }' ``` Delete a route: ```bash curl -X DELETE https://app.paymentkit.com/api/{account_id}/processor-routes/proute_xyz789 \ -H "Authorization: Bearer sk_live_..." ``` > **Warning** > > A route cannot be deleted while it is still referenced by a **checkout session**, used by a **cascade pricing tier**, or set as the **account default**. Deletion attempts on a referenced route return `409 Conflict`. ## View route audit log Track changes to a processor route over time. The audit log records who made changes, what changed, and when. ```bash curl https://app.paymentkit.com/api/{account_id}/processor-routes/proute_xyz789/audit-log \ -H "Authorization: Bearer sk_live_..." ``` Response: ```json { "items": [ { "id": "ral_abc123", "route_id": "proute_xyz789", "action": "updated", "operator_identity": "casey@example.com", "details": { "changes": { "processors": { "old_value": [{ "processor_id": "proc_abc123" }], "new_value": [ { "processor_id": "proc_def456" }, { "processor_id": "proc_abc123" } ] } } }, "reason": null, "created_at": "2026-02-01T15:30:00Z" }, { "id": "ral_def456", "route_id": "proute_xyz789", "action": "created", "operator_identity": "api_key_tok_abc123", "details": { "name": "US Optimized", "description": null, "processors": [{ "processor_id": "proc_abc123" }] }, "reason": null, "created_at": "2026-01-15T10:30:00Z" } ], "total": 2, "has_more": false } ``` **Audit log fields:** | Field | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `action` | `created`, `updated`, or `deleted` | | `operator_identity` | Who made the change — a user's email (e.g. `casey@example.com`) or an API-key subject (`api_key_`) | | `details` | For `updated`, a `changes` map of `{ field: { old_value, new_value } }`; for `created`/`deleted`, the full route config (`name`, `description`, `processors`) | | `reason` | Optional reason for the change (`null` when none was provided) | | `created_at` | When the change occurred | > **Note** > > Audit logs are retained even after a route is deleted. Query by route external ID to see historical changes. --- # MIT cascade settings Control how recurring/subscription payments (Merchant Initiated Transactions) retry across processors when the last successful processor is unavailable. By default, MITs use only the subscription's last successful processor. Enable **cascade** to try fallback processors in order when the primary fails. ## Get MIT settings ```bash curl https://app.paymentkit.com/api/accounts/{account_id}/mit-settings \ -H "Authorization: Bearer sk_live_..." ``` Response: ```json { "cascading_enabled": true, "fallback_processor_1_id": "proc_abc123", "fallback_processor_2_id": "proc_def456", "update_default_pm_on_fallback": false, "max_fallback_payment_methods": 1 } ``` ## Update MIT settings ```bash curl -X PATCH https://app.paymentkit.com/api/accounts/{account_id}/mit-settings \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "cascading_enabled": true, "fallback_processor_1_id": "proc_abc123", "fallback_processor_2_id": "proc_def456" }' ``` **Settings fields:** | Field | Type | Description | | ------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `cascading_enabled` | boolean | Enable fallback to other processors when the last successful one is unavailable (default: `false`) | | `fallback_processor_1_id` | string | First fallback processor ID | | `fallback_processor_2_id` | string | Second fallback processor ID | | `update_default_pm_on_fallback` | boolean | When `true`, a successful fallback card re-pins the subscription and invoice default payment method to that card (default: `false`) | | `max_fallback_payment_methods` | integer | Maximum number of fallback cards to try after the primary default card, `0`–`10` (`0` = primary only, default: `1`) | **How MIT cascade works:** 1. PaymentKit attempts payment with the subscription's last successful processor 2. If that fails and `cascading_enabled` is `true`, it tries `fallback_processor_1_id` 3. If that also fails, it tries `fallback_processor_2_id` 4. After any success, that processor becomes the new "last successful" for future MITs > **Tip** > > Use MIT cascading for high-value recurring payments where maximizing collection success outweighs the cost of multiple processor attempts. > Define **processor routes**—ordered lists of payment processors—to control how payments are attempted. When a customer pays, PaymentKit tries each processor in order until one succeeds.