<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: murray wang</title>
    <description>The latest articles on DEV Community by murray wang (@ismurray).</description>
    <link>https://dev.to/ismurray</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4179690%2F62f9f256-2294-4f32-88fe-d227556ffcd1.jpg</url>
      <title>DEV Community: murray wang</title>
      <link>https://dev.to/ismurray</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ismurray"/>
    <language>en</language>
    <item>
      <title>An approval was corrected to Declined: how to test permission revocation</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Sat, 17 Oct 2026 03:36:01 +0000</pubDate>
      <link>https://dev.to/ismurray/an-approval-was-corrected-to-declined-how-to-test-permission-revocation-4jj</link>
      <guid>https://dev.to/ismurray/an-approval-was-corrected-to-declined-how-to-test-permission-revocation-4jj</guid>
      <description>&lt;p&gt;Your fixture session received Approved and granted a local permission. A later event says Declined. You need to decide whether that event revokes access, and how to tell a real correction from a replayed or stale callback. This article tests that decision in the synthetic fixture and separates what the fixture does from what your application must design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: an approval that no longer holds
&lt;/h2&gt;

&lt;p&gt;A seller passed verification, your handler recorded Approved, and the explicit policy allowed a local permission. Later a new Declined event arrives for the same session. If your code treats every terminal status as final, it ignores the correction and access stays open. If it treats every incoming event as fresh, a replayed callback can flip state back and forth. The goal is narrow. A new Declined correction after Approved must set permission_granted from true to false. grant_count must stay at 1, because the history of the grant does not disappear. Duplicate and stale events must change nothing. Whether a real provider ever sends such corrections, and what they mean, depends on that provider's actual contract. The fixture only lets you rehearse the shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the fixture actually implements
&lt;/h2&gt;

&lt;p&gt;The downloadable identity example stores sessions and processed event IDs in SQLite. Its event method accepts only mode synthetic_fixture and source fixture_callback, and only event IDs that start with fixture-. It has no production signature verifier. It checks tenant and subject binding first, then deduplicates on the session and event ID pair. One rule handles corrections. If the stored status is approved and the incoming status is Declined, the fixture records the event and sets status to declined and granted to 0. It leaves the grants counter untouched. Any other event against an approved, declined or expired session returns the current state unchanged. That event is not recorded either. This correction check runs before the deadline check, so a Declined correction after Approved is applied even when it arrives after the deadline. live_permission_changed is always false. No real permission exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothetical scenario and expected outputs
&lt;/h2&gt;

&lt;p&gt;This is a hypothetical walkthrough on fixture-session, bound to fixture-tenant and fixture-user, with deadline 60.&lt;/p&gt;

&lt;p&gt;Step 1: send event fixture-approved with status Approved and policy true. Expect status approved, deadline 60, permission_granted true, grant_count 1, live_permission_changed false.&lt;/p&gt;

&lt;p&gt;Step 2: replay fixture-approved. Expect the identical object. The duplicate is a no-op.&lt;/p&gt;

&lt;p&gt;Step 3: send a new event ID, for example fixture-correction, with status Declined. Expect status declined, deadline 60, permission_granted false, grant_count 1, live_permission_changed false.&lt;/p&gt;

&lt;p&gt;The tested expired output is a different path: a session that never received an approval. It has status expired, permission_granted false, grant_count 0, next review_or_authorized_provider_refresh and provider_polled false. Do not use it as evidence of revocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  New correction, duplicate, or stale pending event
&lt;/h2&gt;

&lt;p&gt;Three inputs can look alike and must stay distinct.&lt;/p&gt;

&lt;p&gt;A new correction carries an unseen event ID and status Declined against an approved session. It revokes the permission.&lt;/p&gt;

&lt;p&gt;A duplicate reuses a processed event ID. It returns stored state, even if its status field now differs.&lt;/p&gt;

&lt;p&gt;A stale pending event, such as In Progress or In Review arriving after Approved, must not downgrade or re-grant anything. The fixture returns current state and does not record it.&lt;/p&gt;

&lt;p&gt;Two more cases follow the same rule. A late Approved after a Declined correction is also ignored, so permission does not silently reopen. A second Declined after declined changes nothing.&lt;/p&gt;

&lt;p&gt;Test each case with distinct event IDs and assert both permission_granted and grant_count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed design: a caller-owned revocation receipt
&lt;/h2&gt;

&lt;p&gt;The following is a proposed design, not fixture behavior. The fixture flips one flag; real revocation touches listings, payouts or sessions in systems you own. A caller-owned revocation receipt could record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the session&lt;/li&gt;
&lt;li&gt;the correcting event ID&lt;/li&gt;
&lt;li&gt;the previous and new status&lt;/li&gt;
&lt;li&gt;the grant count at revocation&lt;/li&gt;
&lt;li&gt;a timestamp&lt;/li&gt;
&lt;li&gt;which downstream actions were requested and confirmed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write the receipt in the same transaction that clears the permission. Make downstream undo steps idempotent against that receipt. Before trusting a correction, re-fetch the authoritative decision through an authorized provider route. A synthetic Declined is not evidence of a real decline, and synthetic fixture permission is not live business permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure handling
&lt;/h2&gt;

&lt;p&gt;Several failures need defined behavior.&lt;/p&gt;

&lt;p&gt;If a correction fails the binding check, the fixture raises BINDING_MISMATCH. Treat this as an alert, not as a revocation.&lt;/p&gt;

&lt;p&gt;Non-synthetic sources raise PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED. Event IDs without the fixture- prefix raise SYNTHETIC_EVENT_REQUIRED. Sending an event for an unknown session raises BINDING_MISMATCH; reading an unknown session with read raises SESSION_NOT_FOUND.&lt;/p&gt;

&lt;p&gt;The recover method only expires pending, review or unknown sessions locally. It never polls a provider and never reverses an approval. Do not rely on it to catch a missed correction.&lt;/p&gt;

&lt;p&gt;If your downstream revocation partially fails, keep the permission cleared and mark the receipt incomplete for review. Do not restore access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptance checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Approved with policy true yields permission_granted true and grant_count 1.&lt;/li&gt;
&lt;li&gt;A replayed approval event ID leaves both fields unchanged.&lt;/li&gt;
&lt;li&gt;A new Declined event yields status declined, permission_granted false and grant_count 1.&lt;/li&gt;
&lt;li&gt;Stale In Progress or In Review after Approved changes nothing.&lt;/li&gt;
&lt;li&gt;A late Approved after the correction does not restore permission.&lt;/li&gt;
&lt;li&gt;A correction with the wrong tenant or subject is rejected.&lt;/li&gt;
&lt;li&gt;live_permission_changed stays false throughout.&lt;/li&gt;
&lt;li&gt;Your own receipt and downstream undo steps are tested separately.&lt;/li&gt;
&lt;li&gt;Your correction semantics are confirmed against the real provider contract before any live use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run the checked synthetic example
&lt;/h2&gt;

&lt;p&gt;Download and extract &lt;a href="https://gridzen.ai/downloads/verification-delivery.zip" rel="noopener noreferrer"&gt;https://gridzen.ai/downloads/verification-delivery.zip&lt;/a&gt;, 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 delivery.py identity &lt;span class="nt"&gt;--db&lt;/span&gt; /tmp/gridzen-article-fixture.sqlite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sources and scope
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/guides/approved-seller-permissions/" rel="noopener noreferrer"&gt;https://gridzen.ai/guides/approved-seller-permissions/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://gridzen.ai/guides/current-capabilities/" rel="noopener noreferrer"&gt;https://gridzen.ai/guides/current-capabilities/&lt;/a&gt;&lt;br&gt;
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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task": "identity_onboarding", "scenario": "success"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is synthetic integration testing, not identity verification or production authorization. Real payments are disabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/console/?source=dev&amp;amp;content_id=article-demand-bb35866048571740" rel="noopener noreferrer"&gt;Create a free sandbox account&lt;/a&gt; · &lt;a href="https://gridzen.ai/guides/approved-seller-permissions/?source=dev&amp;amp;content_id=article-demand-bb35866048571740" rel="noopener noreferrer"&gt;Integration guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Written and tested by the GridZen team with AI assistance.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Should an unknown phone result reject a signup?</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Sat, 17 Oct 2026 03:32:29 +0000</pubDate>
      <link>https://dev.to/ismurray/should-an-unknown-phone-result-reject-a-signup-94h</link>
      <guid>https://dev.to/ismurray/should-an-unknown-phone-result-reject-a-signup-94h</guid>
      <description>&lt;p&gt;A signup form calls a phone check and gets back something that is neither valid nor invalid. Either the result is unknown or the provider call failed. The tempting shortcut is to reject anything other than a clean pass. This article argues against that shortcut and walks through the four-branch synthetic example to show how to route each outcome. The core distinction is simple. An unknown result is missing evidence, not negative evidence. If you reject on missing evidence, every provider outage becomes a user lockout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem and the decision you own
&lt;/h2&gt;

&lt;p&gt;The decision in front of you is not whether a number is good. It is what your signup flow should do when the check cannot answer. Three outcomes look alike in code but mean different things. A confirmed invalid number is evidence. An unknown result means the check returned no usable signal. A provider failure means the check never completed. Only the first justifies blocking on format grounds. The other two need an explicit path: bounded recovery, manual review, or a possession step through an OTP provider you already run.&lt;/p&gt;

&lt;p&gt;Keep in mind what no phone check establishes. Validity and line type are different from possession and identity. The current free production route does not request line type, ownership, reachability or OTP delivery. Its approved controlled pilot covers basic number format and validity only.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the downloadable example implements
&lt;/h2&gt;

&lt;p&gt;The downloadable example runs offline in Python 3.10 or later. It needs no credentials and makes no network calls. It has four branches: normal, unknown, provider_failure and otp. Every output includes the fields mode, branch, state, valid, line_type, next, otp_sent and live_line_type_available.&lt;/p&gt;

&lt;p&gt;Two values never change across branches. The mode field is always synthetic_fixture. The otp_sent field is always false, so the example never sends a code. The live_line_type_available field is always false, which reflects that the free route does not return line type.&lt;/p&gt;

&lt;p&gt;The mobile line type you will see in the normal branch is fixture data, not something a live check would return. Any branch name outside the four raises UNSUPPORTED_FIXTURE.&lt;/p&gt;

&lt;p&gt;This example is not the same as the anonymous HTTP trial, which covers three scenarios. Do not treat results from one as results from the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked hypothetical: four signups, four routes
&lt;/h2&gt;

&lt;p&gt;Imagine a hypothetical marketplace that receives four signups in one hour. The fixture's expected outputs map onto them as follows.&lt;/p&gt;

&lt;p&gt;The first signup takes the normal branch. The output is state simulated, valid true, line_type mobile and next continue_customer_policy. Your own rules decide what happens next. The check alone does not admit the user.&lt;/p&gt;

&lt;p&gt;The second signup takes the unknown branch. The output is state unknown, valid null, line_type null and next review_or_existing_otp. The account is held for review or sent to your existing OTP step. It is not rejected.&lt;/p&gt;

&lt;p&gt;The third signup takes the provider_failure branch. The output is also state unknown with valid null and line_type null, but next is bounded_retry_then_review. You retry a limited number of times and then hand the case to review.&lt;/p&gt;

&lt;p&gt;The fourth signup takes the otp branch, which models a request to prove possession. The output is state unsupported, valid null, line_type null and next customer_otp_provider. GridZen sends nothing here. That step belongs to your OTP provider.&lt;/p&gt;

&lt;p&gt;All four outputs carry otp_sent false.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed application design for failure handling
&lt;/h2&gt;

&lt;p&gt;The following is design guidance for your application, not behavior the fixture implements.&lt;/p&gt;

&lt;p&gt;Bound retries with a fixed attempt count and a deadline, so a provider failure cannot loop forever or block the form indefinitely.&lt;/p&gt;

&lt;p&gt;Store unknown and failed outcomes as their own state, kept separate from invalid. That way, reviewers and analytics do not mistake an outage for fraud.&lt;/p&gt;

&lt;p&gt;When review is the route, decide what the user may do while pending. You might let them browse but block payouts or listings until the case is resolved.&lt;/p&gt;

&lt;p&gt;If you route to OTP, call your existing provider and record its result separately from the phone check result.&lt;/p&gt;

&lt;p&gt;Do not let a null valid value coerce to false anywhere in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits you should state plainly
&lt;/h2&gt;

&lt;p&gt;The fixture produces synthetic outputs only. It proves branch handling in your code, not production success. Its tests cover simulated transitions, bindings, duplicate callbacks, restart and timeouts. They do not demonstrate live accuracy, external adoption or agent superiority.&lt;/p&gt;

&lt;p&gt;An OTP recommendation does not send a code and does not establish possession.&lt;/p&gt;

&lt;p&gt;Live route availability requires separate confirmation. Production phone checks are limited to the controlled pilot, and self-service live verification is disabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptance checklist
&lt;/h2&gt;

&lt;p&gt;Before you ship, confirm each of the following.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running all four branches reproduces exactly the next values listed above.&lt;/li&gt;
&lt;li&gt;Your code treats a null valid value as unknown, never as invalid.&lt;/li&gt;
&lt;li&gt;The unknown branch never auto-rejects a signup.&lt;/li&gt;
&lt;li&gt;The provider_failure branch retries a bounded number of times, then moves to review.&lt;/li&gt;
&lt;li&gt;The otp branch calls only your own OTP provider and records otp_sent as false on the GridZen side.&lt;/li&gt;
&lt;li&gt;No logic reads line_type from a live result.&lt;/li&gt;
&lt;li&gt;Your code handles UNSUPPORTED_FIXTURE.&lt;/li&gt;
&lt;li&gt;The unit tests pass.&lt;/li&gt;
&lt;li&gt;Documentation does not claim ownership, reachability or identity from a phone check.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run the checked synthetic example
&lt;/h2&gt;

&lt;p&gt;Download and extract &lt;a href="https://gridzen.ai/downloads/verification-delivery.zip" rel="noopener noreferrer"&gt;https://gridzen.ai/downloads/verification-delivery.zip&lt;/a&gt;, 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 delivery.py phone &lt;span class="nt"&gt;--branch&lt;/span&gt; unknown
python3 delivery.py phone &lt;span class="nt"&gt;--branch&lt;/span&gt; provider_failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sources and scope
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/guides/signup-phone-check/" rel="noopener noreferrer"&gt;https://gridzen.ai/guides/signup-phone-check/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://gridzen.ai/guides/current-capabilities/" rel="noopener noreferrer"&gt;https://gridzen.ai/guides/current-capabilities/&lt;/a&gt;&lt;br&gt;
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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task": "signup_phone", "scenario": "success"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is synthetic integration testing, not identity verification or production authorization. Real payments are disabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/console/?source=dev&amp;amp;content_id=article-demand-58d65e7616143b97" rel="noopener noreferrer"&gt;Create a free sandbox account&lt;/a&gt; · &lt;a href="https://gridzen.ai/guides/signup-phone-check/?source=dev&amp;amp;content_id=article-demand-58d65e7616143b97" rel="noopener noreferrer"&gt;Integration guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Written and tested by the GridZen team with AI assistance.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Recover a delayed verification callback</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 13 Oct 2026 07:09:46 +0000</pubDate>
      <link>https://dev.to/ismurray/recover-a-delayed-verification-callback-3f68</link>
      <guid>https://dev.to/ismurray/recover-a-delayed-verification-callback-3f68</guid>
      <description>&lt;p&gt;Recover a delayed callback without creating duplicate sessions. The runnable local example stores state in SQLite, enforces a deadline and rejects late permission grants. Its callback channel is explicitly synthetic: it is not a live signature verifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;Recover a delayed callback without creating duplicate sessions. The runnable local example stores state in SQLite, enforces a deadline and rejects late permission grants. Its callback channel is explicitly synthetic: it is not a live signature verifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run and inspect the three outcomes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;scenario &lt;span class="k"&gt;in &lt;/span&gt;success reject unavailable&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task":"identity_onboarding","scenario":"'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scenario&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Expected output (synthetic)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_fixture"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local_response_trial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"identity_onboarding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"real_person_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"upstream_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_scenario"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hosted_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"business_decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"caller_owned"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connect_verified_account_for_persisted_sandbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"production_available_from_this_trial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Local state machine: restart, duplicates and deadlines
&lt;/h2&gt;

&lt;p&gt;The local script uses only fixture identifiers. Run the code and tests in the linked repository; no account or provider key is required. Production callbacks need their own authorized signature verifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/immurray/gridzen-developer-kit.git
&lt;span class="nb"&gt;cd &lt;/span&gt;gridzen-developer-kit
python3 examples/verification_delivery/delivery.py identity &lt;span class="nt"&gt;--db&lt;/span&gt; /tmp/gridzen-local-delivery.sqlite3
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest examples/verification_delivery/test_delivery.py &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decide what your app does next
&lt;/h2&gt;

&lt;p&gt;Treat success as a fixture branch, not real-person approval. For reject, show a recoverable message or review path. For unavailable, leave the outcome unknown and avoid silently granting permission. In a real integration, bind tenant, account and session; verify the provider notification, re-read authoritative state and apply your business policy before changing permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits and production handoff
&lt;/h2&gt;

&lt;p&gt;No documents, phone numbers, credentials or real users are accepted by this trial. Production is a separate owner-approved free pilot. Current phone production provides basic validity only; hosted identity requires current confirmed free allowance and applicable supplier rights. Real payments remain disabled.&lt;/p&gt;

&lt;p&gt;Synthetic integration example; no real person verified, no OTP, no production permission and no payment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/console/start?lang=en&amp;amp;source=dev&amp;amp;content_id=article-callback-recovery" rel="noopener noreferrer"&gt;Run without signup&lt;/a&gt; · &lt;a href="https://gridzen.ai/console/assessment?lang=en&amp;amp;source=dev&amp;amp;content_id=article-callback-recovery" rel="noopener noreferrer"&gt;Get task guidance&lt;/a&gt; · &lt;a href="https://github.com/immurray/gridzen-developer-kit" rel="noopener noreferrer"&gt;Source and tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Written and tested by the GridZen team with AI assistance.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Separate verification results from business permissions</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 13 Oct 2026 07:00:48 +0000</pubDate>
      <link>https://dev.to/ismurray/separate-verification-results-from-business-permissions-44om</link>
      <guid>https://dev.to/ismurray/separate-verification-results-from-business-permissions-44om</guid>
      <description>&lt;p&gt;Turn a verification result into a bounded application decision. Test account binding, explicit policy, duplicate events and rejection handling in a local fixture database. The permission shown in this walkthrough is a demo flag, not a real seller permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;Turn a verification result into a bounded application decision. Test account binding, explicit policy, duplicate events and rejection handling in a local fixture database. The permission shown in this walkthrough is a demo flag, not a real seller permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run and inspect the three outcomes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;scenario &lt;span class="k"&gt;in &lt;/span&gt;success reject unavailable&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task":"identity_onboarding","scenario":"'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scenario&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Expected output (synthetic)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_fixture"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local_response_trial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"identity_onboarding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"real_person_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"upstream_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_scenario"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hosted_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"business_decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"caller_owned"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connect_verified_account_for_persisted_sandbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"production_available_from_this_trial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Local state machine: restart, duplicates and deadlines
&lt;/h2&gt;

&lt;p&gt;The local script uses only fixture identifiers. Run the code and tests in the linked repository; no account or provider key is required. Production callbacks need their own authorized signature verifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/immurray/gridzen-developer-kit.git
&lt;span class="nb"&gt;cd &lt;/span&gt;gridzen-developer-kit
python3 examples/verification_delivery/delivery.py identity &lt;span class="nt"&gt;--db&lt;/span&gt; /tmp/gridzen-local-delivery.sqlite3
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest
.venv/bin/python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest examples/verification_delivery/test_delivery.py &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decide what your app does next
&lt;/h2&gt;

&lt;p&gt;Treat success as a fixture branch, not real-person approval. For reject, show a recoverable message or review path. For unavailable, leave the outcome unknown and avoid silently granting permission. In a real integration, bind tenant, account and session; verify the provider notification, re-read authoritative state and apply your business policy before changing permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits and production handoff
&lt;/h2&gt;

&lt;p&gt;No documents, phone numbers, credentials or real users are accepted by this trial. Production is a separate owner-approved free pilot. Current phone production provides basic validity only; hosted identity requires current confirmed free allowance and applicable supplier rights. Real payments remain disabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task": "identity_onboarding", "scenario": "success"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is synthetic integration testing, not identity verification or production authorization. Real payments are disabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/console/?source=dev&amp;amp;content_id=article-business-permission-boundary" rel="noopener noreferrer"&gt;Create a free sandbox account&lt;/a&gt; · &lt;a href="https://gridzen.ai/guides/approved-seller-permissions/?source=dev&amp;amp;content_id=article-business-permission-boundary" rel="noopener noreferrer"&gt;Integration guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Written and tested by the GridZen team with AI assistance.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Add a phone check without promising ownership</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:27:41 +0000</pubDate>
      <link>https://dev.to/ismurray/add-a-phone-check-without-promising-ownership-5dpj</link>
      <guid>https://dev.to/ismurray/add-a-phone-check-without-promising-ownership-5dpj</guid>
      <description>&lt;p&gt;Add a pre-registration number check while keeping invalid numbers separate from unavailable results. A format or validity signal cannot prove ownership or SMS reachability. This walkthrough returns no line type and sends no OTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;Add a pre-registration number check while keeping invalid numbers separate from unavailable results. A format or validity signal cannot prove ownership or SMS reachability. This walkthrough returns no line type and sends no OTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run and inspect the three outcomes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;scenario &lt;span class="k"&gt;in &lt;/span&gt;success reject unavailable&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task":"signup_phone","scenario":"'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scenario&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Expected output (synthetic)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_fixture"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local_response_trial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"signup_phone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"real_person_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"upstream_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mock_transport"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"simulation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"identity_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not_checked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ownership"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not_checked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1791873442.459438&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number_valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"business_decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"caller_owned"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connect_verified_account_for_persisted_sandbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"production_available_from_this_trial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decide what your app does next
&lt;/h2&gt;

&lt;p&gt;Treat success as a fixture branch, not real-person approval. For reject, show a recoverable message or review path. For unavailable, leave the outcome unknown and avoid silently granting permission. In a real integration, bind tenant, account and session; verify the provider notification, re-read authoritative state and apply your business policy before changing permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits and production handoff
&lt;/h2&gt;

&lt;p&gt;No documents, phone numbers, credentials or real users are accepted by this trial. Production is a separate owner-approved free pilot. Current phone production provides basic validity only; hosted identity requires current confirmed free allowance and applicable supplier rights. Real payments remain disabled.&lt;/p&gt;

&lt;p&gt;Synthetic integration example; no real person verified, no OTP, no production permission and no payment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/console/start?lang=en&amp;amp;source=dev&amp;amp;content_id=article-phone-check-boundaries" rel="noopener noreferrer"&gt;Run without signup&lt;/a&gt; · &lt;a href="https://gridzen.ai/console/assessment?lang=en&amp;amp;source=dev&amp;amp;content_id=article-phone-check-boundaries" rel="noopener noreferrer"&gt;Get task guidance&lt;/a&gt; · &lt;a href="https://github.com/immurray/gridzen-developer-kit" rel="noopener noreferrer"&gt;Source and tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Written and tested by the GridZen team with AI assistance.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Your first identity onboarding sandbox run</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:27:41 +0000</pubDate>
      <link>https://dev.to/ismurray/your-first-identity-onboarding-sandbox-run-1ej2</link>
      <guid>https://dev.to/ismurray/your-first-identity-onboarding-sandbox-run-1ej2</guid>
      <description>&lt;p&gt;Build the first identity onboarding result handler before collecting any document. This walkthrough uses an account-free synthetic response; it does not create a hosted provider session. Your application must keep verification decisions separate from seller permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;Build the first identity onboarding result handler before collecting any document. This walkthrough uses an account-free synthetic response; it does not create a hosted provider session. Your application must keep verification decisions separate from seller permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run and inspect the three outcomes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;scenario &lt;span class="k"&gt;in &lt;/span&gt;success reject unavailable&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://gridzen.ai/console/api/quickstart/trial &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task":"identity_onboarding","scenario":"'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$scenario&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;'"}'&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Expected output (synthetic)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_fixture"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local_response_trial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"identity_onboarding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"real_person_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount_minor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"upstream_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"synthetic_scenario"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hosted_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"business_decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"caller_owned"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next_step"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connect_verified_account_for_persisted_sandbox"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"production_available_from_this_trial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decide what your app does next
&lt;/h2&gt;

&lt;p&gt;Treat success as a fixture branch, not real-person approval. For reject, show a recoverable message or review path. For unavailable, leave the outcome unknown and avoid silently granting permission. In a real integration, bind tenant, account and session; verify the provider notification, re-read authoritative state and apply your business policy before changing permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits and production handoff
&lt;/h2&gt;

&lt;p&gt;No documents, phone numbers, credentials or real users are accepted by this trial. Production is a separate owner-approved free pilot. Current phone production provides basic validity only; hosted identity requires current confirmed free allowance and applicable supplier rights. Real payments remain disabled.&lt;/p&gt;

&lt;p&gt;Synthetic integration example; no real person verified, no OTP, no production permission and no payment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/console/start?lang=en&amp;amp;source=dev&amp;amp;content_id=article-identity-first-run" rel="noopener noreferrer"&gt;Run without signup&lt;/a&gt; · &lt;a href="https://gridzen.ai/console/assessment?lang=en&amp;amp;source=dev&amp;amp;content_id=article-identity-first-run" rel="noopener noreferrer"&gt;Get task guidance&lt;/a&gt; · &lt;a href="https://github.com/immurray/gridzen-developer-kit" rel="noopener noreferrer"&gt;Source and tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Written and tested by the GridZen team with AI assistance.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
  </channel>
</rss>
