Provider health & failover
Every provider behind Finscale is continuously scored on errors, latency, and authorization rate. Healthy providers get traffic, degraded ones are down-weighted, disabled ones get none — and when an attempt fails retryably, failover moves the payment to the next provider inside the same API call. This page covers the provider object, the health states, and exactly when failover fires.
The three health states
Health is summarized in one field on every provider: status. Transitions are automatic, driven by the scoring signals below — no operator has to notice an outage before routing reacts.
status | Routing behavior | Typical cause |
|---|---|---|
| active | Full participation in the candidate list, ranked by auth-rate and cost as usual. | Normal operation. |
| degraded | Down-weighted — dropped toward the back of the candidate list. Still usable as a failover target, so capacity isn't wasted, but new payments prefer healthier providers. | Elevated error rate or latency, a spike in timeouts, an open incident at the provider or a scheme it fronts. |
| disabled | Receives no traffic at all — removed from every candidate list until reinstated. | Hard outage, or Finscale pulled it from rotation (maintenance, contract, compliance). |
Health scoring
Health is computed per provider — and where it matters, per method × currency segment, because a provider can be perfectly healthy for EUR cards while its iDEAL rail struggles. The inputs:
| Signal | Window | What it captures |
|---|---|---|
| Error rate | Minutes, rolling | Provider 5xx responses, connection failures, malformed replies — the loudest, fastest signal. |
| Timeout rate | Minutes, rolling | Attempts exceeding the per-attempt budget (10 s). Often the first symptom of an unfolding incident. |
| Latency | Minutes, rolling | Attempt round-trip times. A provider drifting slower gets down-ranked before it starts timing out. |
| Authorization rate | 7 days, rolling | Approval share per method × currency segment — surfaced on the object as auth_rate_7d. Slow-moving, but the signal that decides ranking between two healthy providers. |
| Open incidents | Live | Declared maintenance and incident windows at providers and schemes. |
Fast signals (errors, timeouts) can degrade a provider within minutes; recovery requires the same signals to stay clean, so a flapping provider doesn't oscillate back into the front of the list. The scoring feeds stage 4 of the routing decision — after method, currency, and your rules have filtered the candidates.
The provider object
Providers are read-only: Finscale manages the contracts, credentials, and health behind each opaque id. See your routing set and its live state with GET /v1/providers:
curl https://api.finscale.dev/v1/providers \
-H "Authorization: Bearer sk_test_51FinscaleDemo…"
{
"object": "list",
"data": [
{
"id": "prov_eu_acq_01",
"object": "provider",
"methods": ["card", "apple_pay", "google_pay", "ideal", "bancontact"],
"regions": ["EU", "UK"],
"currencies": ["EUR", "GBP"],
"status": "active",
"auth_rate_7d": 0.947
},
{
"id": "prov_eu_acq_02",
"object": "provider",
"methods": ["card", "ideal", "sepa_debit"],
"regions": ["EU"],
"currencies": ["EUR"],
"status": "degraded",
"auth_rate_7d": 0.913
}
],
"has_more": false
}
| Field | Type | Meaning |
|---|---|---|
id | string | Opaque provider id, always prefixed prov_. Stable — safe to pin routing rules to. |
methods | array | Method types this provider can process, from the catalog. |
regions | array | Regions served, e.g. EU, UK, US, BR, KE, SEA. |
currencies | array | Settlement currencies the provider supports. |
status | enum | active · degraded · disabled — the health states above. |
auth_rate_7d | number, 0–1 | Platform-wide authorization rate over the last 7 days. One input to smart routing's ranking — not a promise about your traffic mix. |
GET /v1/providers/{id} retrieves a single provider. There are no write endpoints — you steer traffic with routing rules, not by editing providers.
Failover policy
Health scoring is the slow loop; failover is the fast one. When an individual attempt fails retryably, Finscale moves to the next candidate immediately — inside the same POST /v1/payments call, invisible to your integration except in provider_attempts:
- Fires on soft failures only: timeouts, provider errors (
provider_unavailable,processing_error), and retryable declines where the issuer never gave a verdict. The full trigger table is on the smart routing page. - Never on final declines.
insufficient_fundsor a fraud block is the issuer's answer about the instrument — re-asking through a different acquirer gets the same no and burns your auth-rate reputation. - Bounded: up to 3 attempts per payment (primary + 2 failovers), each adding roughly one provider round-trip.
- Idempotent: one
Idempotency-Key, one payment, one charge — whichever provider lands it.
The two loops compound: a degraded provider is already at the back of the candidate list, so most payments never touch it — and the few that reach it as a last resort still fail over cleanly if it errors.
Create a test-mode card payment with "amount": 4999. The first provider returns a retryable decline, failover retries on the second, and the payment succeeds — with both attempts recorded in provider_attempts.
Webhook events
Every health transition emits provider.health.changed, with the provider object as the payload. request_id is null — Finscale generated the event internally, not in response to an API call:
{
"id": "evt_2Wd8pQ4rT6Ku",
"object": "event",
"type": "provider.health.changed",
"data": {
"object": {
"id": "prov_eu_acq_01",
"object": "provider",
"methods": ["card", "apple_pay", "google_pay", "ideal", "bancontact"],
"regions": ["EU", "UK"],
"currencies": ["EUR", "GBP"],
"status": "degraded",
"auth_rate_7d": 0.947
}
},
"request_id": null,
"livemode": false,
"created_at": "2026-07-16T09:24:31Z"
}
The event is informational: routing has already reacted by the time it is delivered. Subscribe to it for ops alerting, status pages, and to explain traffic-mix shifts in your reconciliation — not to trigger any action on your side. Delivery, signatures, and retry behavior are covered on the webhooks page.
Error scenarios
| Scenario | What you see |
|---|---|
| One provider fails, another approves | No error at all — the payment succeeds, and the failed attempt is visible only in provider_attempts. |
| Every eligible provider fails retryably | The payment fails with failure_code: provider_unavailable after the attempt budget is exhausted; payment.failed fires. Retry with the same Idempotency-Key is safe. |
The only provider for a method is disabled | New payments for that method fail fast with provider_unavailable — no attempt is made against a provider that is out of rotation. |
GET /v1/providers/{id} with an unknown id | 404 — invalid_request_error, code: resource_missing. |
provider_unavailable is a soft decline in the error model — it says something about the provider, not your customer. It is also rare by construction: it requires every eligible provider for the method to be failing at once.
Related
- Smart routing — the full routing decision and the failover trigger table.
- Routing rules — steering traffic yourself, and why health still matters when you do.
- Webhooks — delivery, signatures, and the full event catalog.
- Go-live checklist — subscribing to
provider.health.changedbefore launch.