Provider timeout, rejection and unsupported route: three different outcomes
An identity onboarding step calls a verification provider and does not get a clean answer. Maybe the call timed out. Maybe the provider returned a rejection. Maybe the route you asked for is not supported at all. A common shortcut treats all three as "failed" and either rejects the user or lets them through. This article argues that your application, not the provider and not an orchestration layer, should own a decision table that keeps these outcomes apart. It shows how to check that table against the synthetic fixture and what the fixture can and cannot prove. Nothing here switches providers automatically, and the fixture grants no real permission.
curl https://gridzen.ai/console/api/quickstart/trial \
-H 'Content-Type: application/json' \
-d '{"task": "identity_onboarding", "scenario": "success"}'The problem and the decision you own
Picture a seller-onboarding service that needs an identity result before enabling a payout feature. Three calls go wrong in three different ways. The first never completes because the provider is unavailable. The second completes and the provider says Declined. The third asks for a capability the route does not offer, such as sending an OTP through a phone check. If one handler maps all three to reject, honest users get blocked whenever the provider is down. If it maps them to allow, an outage quietly becomes approval. GridZen's guide states the principle directly: an unavailable provider is an operational failure, not a low-risk signal. Its current Twilio adapter returns a 503 through the API when the provider is unavailable, and the alpha does not silently convert that failure to allow. The decision you own is what your workflow does next for each outcome. A review queue, a hold or an explicitly approved backup must be built by the calling workflow.
A proposed caller-owned decision table
This is a proposed application design, not implemented GridZen behavior. Give each row an outcome class, its evidence, the HTTP status you recorded and the next action. Technical failure: the check could not complete, for example a timeout, malformed response, missing credentials or a 503. It is not evidence about the person. Next action: hold the workflow, apply a bounded retry only where the upstream operation and your workflow allow it, then route to review. Negative evidence: the provider completed and returned an assertion such as Declined or a mismatch. Next action: follow your policy for declines, keep the record explainable and do not retry hoping for a different answer. Insufficient evidence: the policy lacks a required signal, for example a result that is unknown. Next action: review or an existing step you already run. Unsupported route: the capability was never requested from a provider. Next action: use a separate provider you have authorized for that task. Record HTTP status separately from the decision outcome. A 503 has no successful decision response and must never be logged as decline or allow. The table also contains no row that sends the request to a different provider by itself. A backup is acceptable only if someone explicitly approved it for that task and data.
Worked hypothetical scenario with fixture outputs
Hypothetically, a team maps its table onto the downloadable four-branch phone example plus the identity fixture. This is the local example. It is not the anonymous three-scenario HTTP trial, which is a separate thing. The phone branches show the classes side by side. Every branch returns mode synthetic_fixture, otp_sent false and live_line_type_available false. The normal branch returns state simulated, valid true, line_type mobile and next continue_customer_policy. The unknown branch returns state unknown, valid null, line_type null and next review_or_existing_otp. That is insufficient evidence. The provider_failure branch returns state unknown, valid null, line_type null and next bounded_retry_then_review. That is technical failure, and its next step differs from the unknown branch even though state matches. So your table must key on the cause, not only the state field. The otp branch returns state unsupported, valid null and next customer_otp_provider. That is an unsupported route. No message is sent. On the identity side, a synthetic Declined correction after approval returns status declined, permission_granted false and grant_count 1. That is negative evidence, recorded as a decline. It is not an outage.
Failure handling in the identity fixture
The identity fixture shows how an incomplete check should not drift into a result. If no decision arrives before the deadline, recover marks the session expired locally. With deadline 60 the output is status expired, permission_granted false, grant_count 0, next review_or_authorized_provider_refresh and provider_polled false. Recover does not poll a provider. It only expires pending, review or unknown sessions that have passed their deadline. An unrecognized status becomes unknown, not declined. Errors are also distinct. An event for an unknown session raises BINDING_MISMATCH, and reading an unknown session raises SESSION_NOT_FOUND. Fixture events accept only synthetic callbacks with a fixture source. A non-fixture mode or source raises PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED, because there is no real signature verifier. Duplicate events leave grant_count at 1. Any permission_granted value is a local fixture record with live_permission_changed false. It is not live business permission.
Limits: implemented versus proposed
Implemented in the fixture are the four phone branches, the SQLite session states, local expiry, deduplication and the error names above. Implemented in the alpha API, per the guide, is a 503 when the Twilio adapter's provider is unavailable. The decision table, the review queue, the retry budget and any approved backup are your design. GridZen's alpha does not move funds or guarantee idempotency for a downstream payment action. The current free phone route does not request line type, ownership, reachability or OTP delivery. For hosted identity onboarding, a new person passing the full hosted journey remains untested, and Approved is not unconditional permission. Live route availability needs separate confirmation.
Acceptance checklist
Use this checklist before you call the table done. Each outcome class has its own row, and its next action is written down. HTTP status and decision outcome are stored in separate fields. A 503 or timeout is never logged as decline or allow. Retries are bounded and only allowed where the upstream operation permits them. No code path switches providers on its own, and any backup is explicitly approved. Tests cover timeout, malformed response, missing credentials, provider rejection and recovery, matching the fixture outputs above. Expired sessions show provider_polled false and lead to review. Synthetic grants are never wired to real permissions. The same cases are reviewed again whenever a new provider or policy is enabled. Once these pass locally, decide whether a saved sandbox request fits your task.
Run the checked synthetic example
Download and extract https://gridzen.ai/downloads/verification-delivery.zip, enter verification_delivery, then run with Python 3.10+. This local example makes no network calls. Use a fresh database for an independent identity run.
python3 delivery.py identity --db /tmp/gridzen-article-fixture.sqliteSources and scope
https://gridzen.ai/guides/provider-failures/ https://gridzen.ai/guides/current-capabilities/ Based on the checked source snapshot and local fixture; proposed application rules are design advice, not proven production behavior. Written with AI assistance and reviewed independently.
Separately try three sandbox outcomes Create an account to continue integrating
A download does not prove a successful run. Local examples and the online sandbox are tested separately.