Failover Behavior
This page explains what happens when TrustCaptcha — or the network path to it — is impaired or completely unavailable, and what you can do to keep your forms working through it.
We run a high-availability setup, so outages on our side are rare in practice. Two distinct failure modes can still happen, with very different mitigations:
| # | Scenario | What actually happens | Your responsibility | How rare? |
|---|---|---|---|---|
| 1 | Internal disruption | One of our internal services has a hiccup, but our public server is still reachable | Nothing. Gateway failover handles it transparently. | Uncommon |
| 2 | Total outage | Our entire service is unreachable — neither the widget bundle nor the API responds | Self-host the widget, opt in to widget-side failover, decide how your backend should react | Highly unlikely |
The rest of this page walks through each scenario in detail.
Scenario 1 — Internal disruption (gateway failover)
Section titled “Scenario 1 — Internal disruption (gateway failover)”This is the more likely of the two failure modes (though still uncommon): one of our internal services has a hiccup, but our public API gateway is still healthy. The gateway absorbs the problem on its own.
What you observe:
- Both the widget (when fetching its CAPTCHA config) and your backend (when fetching the verification result) continue to receive valid responses.
- The result you get back has
decisionTypeset toFAILOVERorFAILOVER_TOLERANCE, anddecisionActionset toALLOW. - The result also carries
gatewayFailoverActive: true, so you can tell apart “regular pass” from “passed during a tolerant fallback”.
What you have to do: by default, nothing. Failover is on so the typical integrator does not need to think about outages — the user passes the form, the result is ALLOW, the flow continues.
For high-security integrations (banking, identity-critical flows), check the boolean flag:
if (verificationResult.isGatewayFailoverActive()) { // Reject, route to manual review, or step up — this result was synthesized during an outage.}The flag is purely metadata. It is true whenever the gateway produced the result through any of its failover paths (including an accepted client-failover claim from Scenario 2), false otherwise.
Scenario 2 — Total outage
Section titled “Scenario 2 — Total outage”This is the worst case: our entire service is unreachable from both the user’s browser and your backend (DNS failure, our load balancer down, large-scale internet incident, …). Two things break at once:
- The widget bundle cannot be loaded from our CDN — without mitigation, the widget simply never renders.
- The widget runtime and your backend cannot reach our API — no token can be issued, and no result can be validated.
This scenario is exceptionally rare in practice, but if your forms have to keep working through it, three pieces have to be set up deliberately — none are on by default:
- Self-host the widget bundle.
- Enable widget-side failover.
- Decide how your backend should react.
The following sub-sections walk through each step in detail.
2.1 Self-host the widget bundle
Section titled “2.1 Self-host the widget bundle”Without this step, the bundle never loads from our CDN during an outage and there is nothing to fall back to — the widget simply doesn’t render. See Self-host the widget for the download links and a copy-and-paste setup.
2.2 Enable widget-side failover
Section titled “2.2 Enable widget-side failover”By default the widget surfaces a COMMUNICATION_FAILURE error when our API is unreachable and refuses to produce a verification token. To opt in to failover behavior, set the failover-enabled attribute on the <trustcaptcha-component> — the widget then issues a clearly-marked client-failover token instead, and the user can submit the form normally.
<trustcaptcha-component sitekey="<your_site_key>" failover-enabled></trustcaptcha-component>This is a deliberate availability/security trade-off. Only enable it on forms where blocking real users during an outage is worse than letting through a small amount of unverified traffic — and always combine it with a server-side decision policy (see 2.3 below). CMS / no-code plugins typically expose a corresponding toggle (default off) that maps to this attribute. See the widget overview for the complete property reference.
Format of the client-failover token. When the widget falls back, it generates a self-built verification token with the same outer shape as a regular one, plus a clientFailover: true flag:
{ "verificationId": "<random uuid v4>", "expiresAt": "<now+15min>", "clientFailover": true }(Base64-encoded as usual.) The widget treats this as a successful verification and emits captchaSolved. Whether to honor such a token is decided exclusively on the server side, not in the widget. When your backend validates it via one of our libraries, the request reaches our gateway with ?clientFailover=true. The gateway then decides:
- If the gateway has a recent outage on record (within its grace window, default 15 min): it returns a normal
200 OKresult withdecisionType=FAILOVER_TOLERANCEanddecisionAction=ALLOW. The library hands you the result like any other success, withgatewayFailoverActive: true. - If the gateway has no outage on record: it returns
412 PRECONDITION_FAILED. The library throwsClientReportedServerUnreachableException. The gateway never trusts a client claim alone — it only honors what its own observations support.
2.3 Backend exceptions
Section titled “2.3 Backend exceptions”In a true outage your library can throw one of two failover exceptions. Both extend FailoverException, so you can catch them generically; the trust level is very different though, so most integrations should treat them separately.
A — ServerUnreachableException
Section titled “A — ServerUnreachableException”Your backend could not reach our servers (connection error / timeout). This is a high-trust signal: a server-side observation, not something a malicious client can fake.
| Question | Recommendation |
|---|---|
| What should I do with the request? | Allow it, but log the incident. The user already attempted the CAPTCHA and shouldn’t be punished for our outage. |
| Anything to verify in advance? | Yes — make sure your egress firewall allows https://api.trustcomponent.com. If your backend is permanently blocked from reaching us, every request will throw this exception and your library will silently run in failover mode forever. Logging and alerting on this exception are non-negotiable. |
B — ClientReportedServerUnreachableException (HTTP 412)
Section titled “B — ClientReportedServerUnreachableException (HTTP 412)”The widget claimed it couldn’t reach our servers, but your backend can. This is a low-trust signal because it depends on what the client browser reported.
There are two realistic explanations:
- Legitimate — there really was an outage on our side, the user solved the CAPTCHA in that window, and our gateway has since recovered. The widget reported failover correctly; the backend simply reaches us a moment later, after recovery. This will always happen at the tail end of every outage.
- Suspicious — the client has a local network problem (firewall, proxy, captive portal) and produced a failover token even though our service was reachable to everyone else. Or the client is intentionally faking a failover.
| Question | Recommendation |
|---|---|
| What should I do with the request? | Default: reject or soft-challenge (e.g. ask the user to try again, or step up to email confirmation). |
| Can I be more lenient without weakening security? | Yes — if you maintain a short-lived internal record of “we just saw ServerUnreachableException X minutes ago”, you can allow ClientReportedServerUnreachableException for the same time window. That covers the legitimate tail-of-outage case without trusting unknown clients in normal operation. |
Code example
Section titled “Code example”The pattern of catching both failover exceptions separately looks like this:
try { VerificationResult result = trustCaptcha.getVerificationResult(token); // Handle the result as you normally would.} catch (ServerUnreachableException e) { // Example: allow + log.} catch (ClientReportedServerUnreachableException e) { // Example: reject or soft-challenge.}try { const result = await trustCaptcha.getVerificationResult(token); // Handle the result as you normally would.} catch (e) { if (e instanceof ServerUnreachableException) { // Example: allow + log. } else if (e instanceof ClientReportedServerUnreachableException) { // Example: reject or soft-challenge. }}try { $result = $trustCaptcha->getVerificationResult($token); // Handle the result as you normally would.} catch (ServerUnreachableException $e) { // Example: allow + log.} catch (ClientReportedServerUnreachableException $e) { // Example: reject or soft-challenge.}You can find detailed examples for every supported language on the respective Result Validation page (.NET, Go, JVM, Node.js, PHP, Python, Ruby, Rust).
Operational checklist
Section titled “Operational checklist”Failover is only useful if it doesn’t silently become the new normal. Treat the following as a mandatory checklist:
- Log every failover exception. Whether it’s
ServerUnreachable…orClientReportedServerUnreachable…, route it to your monitoring system. A sudden uptick is a signal — either we have an outage, or someone is probing your system. - Verify your egress firewall allows
https://api.trustcomponent.com. Without it, every request will throwServerUnreachableExceptionand you won’t notice without monitoring. - Self-host the widget on the forms that need failover. Bundle the widget with your frontend assets and keep at least one previous version available for rollback.
- Plugin / no-code users: the “Allow client-reported failover” toggle is off by default. Turn it on only as a deliberate decision, with a backend that handles
ClientReportedServerUnreachableExceptioncorrectly. - Custom REST integration: if you implement the result-retrieval call yourself, treat HTTP
412and connection errors with the same logic the libraries do — see the Result Validation overview for the full status-code table.