<?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>Start a hosted identity journey without putting documents in an AI chat</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 11:00:32 +0000</pubDate>
      <link>https://dev.to/ismurray/start-a-hosted-identity-journey-without-putting-documents-in-an-ai-chat-1ja0</link>
      <guid>https://dev.to/ismurray/start-a-hosted-identity-journey-without-putting-documents-in-an-ai-chat-1ja0</guid>
      <description>&lt;p&gt;An assistant is helping your team onboard a new business user, and someone pastes a passport photo into the chat to speed things up. That puts an identity document in a transcript that no verification flow controls. The decision is how to start verification so the person submits documents and liveness only on a hosted page, while your application reads back three things: a hosted URL, a task state and an authoritative completion status. This article covers the inputs, a hypothetical scenario, the exact synthetic fixture output, failure handling and an acceptance checklist. It separates what the product contract states from the application design we propose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: documents in the wrong channel
&lt;/h2&gt;

&lt;p&gt;When identity onboarding runs through an AI assistant, the easiest path is often the worst one: asking the user to upload an ID card or selfie directly into the conversation. Chat transcripts are stored, shared and reviewed in ways a verification flow does not control. The alternative is a hosted journey. Your service, or the assistant acting for it, asks for a verification link; the person opens it and completes the identity and liveness steps there. The assistant never sees a document or a face. Your decision is not which camera widget to build, but which few values your application sends and which values it trusts when it reads the result back. Getting that boundary right early keeps sensitive images out of logs, prompts and support tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the product contract states
&lt;/h2&gt;

&lt;p&gt;According to the reviewed product contract, the hosted identity task takes three inputs: a subject authorization reference, a stable request UUID and an explicit trial or paid mode. No document or face belongs in the assistant chat. The output is a hosted URL, a task state and an authoritative completion status. The listed tools are production_capabilities, start_identity_onboarding, get_production_verification and refresh_production_verification. Two statements matter most. First, the person, not the assistant, completes identity and liveness steps. Second, Approved does not grant application permissions; your own rules decide what an approved person may do. The contract also records limits: acceptance of a new person passing the full hosted journey remains pending, and online purchases require payment-channel acceptance. Merchant execution requires current capabilities, authorization and an accepted quote. Documentation is not evidence of a live check.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hypothetical scenario and the expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetical: a small SaaS team adds a seller. Its onboarding service records the seller's authorization reference, generates one request UUID and stores it before any call, and selects trial mode explicitly. It then asks to start the hosted journey. In the supplied synthetic contract example, no live provider request is performed, and the expected fixture output is exactly: status pending, hosted_url null, verified false, automatic_business_approval false. Read each field literally. status pending means the task exists but no result is final. hosted_url null means there is no link to show the person yet, so the interface must not render a broken button or invent one. verified false is not a rejection; it only says verification has not completed. automatic_business_approval false confirms that even a later approval will not switch on seller access by itself. This is a synthetic contract illustration, not live acceptance, and the fixture's permission to run is not business permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed application design
&lt;/h2&gt;

&lt;p&gt;The following is our proposed design, not implemented product behavior. Persist the request UUID with the subject reference before starting, so a retry reuses the same request instead of creating a second journey. Store the task state as pending with a local deadline. Show the hosted URL to the person only when the task returns a non-null value, and send it through your own interface, never by asking for images in chat. When the person finishes, read the authoritative completion status with get_production_verification rather than trusting a browser redirect or a message from the assistant. Keep a separate business decision step: an authoritative Approved becomes one input to your seller policy, which checks tenant, user and the action requested. Log the UUID, state and status, never document content.&lt;/p&gt;

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

&lt;p&gt;Plan for each non-happy path explicitly. If hosted_url stays null, keep the task pending and tell the person the link is not ready; do not create a new request with a fresh UUID. If a callback never arrives, the local recovery pattern from the earlier examples expires the pending task at its deadline; it does not poll a provider, so a refresh against the original task is a deliberate, authorized step. Fixture events accept only synthetic callbacks, not real signatures, so a passing fixture says nothing about signature verification in production. If the status is Declined or incomplete, record it and route to human review instead of retrying silently. If someone posts a document in chat anyway, do not forward it; point them to the hosted link. If mode is missing, refuse to start rather than guessing trial or paid.&lt;/p&gt;

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

&lt;p&gt;Before calling this integration ready for a pilot, confirm: the request carries a subject authorization reference, a stored request UUID and an explicit mode; no document or face image appears in any prompt, transcript or log; the fixture returns status pending, hosted_url null, verified false and automatic_business_approval false; the interface handles a null hosted_url without a dead link; retries reuse the same UUID; completion is read from the authoritative status, not from a redirect; Approved passes through your own permission rules and never grants access directly; recovery expires locally with no provider polling; and the team record notes that full new-person approval acceptance is still pending and that purchases depend on payment-channel acceptance. A passing checklist shows your handling is sound, not that a live check has occurred.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Read the verification grade before deciding a beneficiary change</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 10:54:30 +0000</pubDate>
      <link>https://dev.to/ismurray/read-the-verification-grade-before-deciding-a-beneficiary-change-4b0c</link>
      <guid>https://dev.to/ismurray/read-the-verification-grade-before-deciding-a-beneficiary-change-4b0c</guid>
      <description>&lt;p&gt;A seller asks to change the account that receives payouts. Your service submits an authorized CURP and CLABE for one identity and beneficiary-account comparison and gets an answer back. The decision is whether that answer lets the change proceed. This article proposes how to read verification grade, task status, evidence and charges together, using a synthetic contract example whose output is status review, grade null, verified false and automatic_business_approval false. It is not a live check and grants no business permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a beneficiary change waits on one answer
&lt;/h2&gt;

&lt;p&gt;A marketplace seller in Mexico requests a new payout CLABE. The risky shortcut is to read any non-error response as approval and switch the account. The comparison returns several separate things: a verification grade, a task status, evidence and charges. Supported grades and required actions follow the live quote, not a fixed list in your code. Your decision is narrower than it looks: does this specific result, under this accepted quote, support changing this beneficiary, or must the request wait for review? The product does not give unconditional approval, does not look up arbitrary people and does not claim a direct identifier link between a bank and a CURP.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the inputs and outputs mean
&lt;/h2&gt;

&lt;p&gt;Inputs are an authorized CURP and CLABE, the current quote, the required evidence and consents, and a stable request UUID. The UUID lets a retry refer to the same request instead of creating a second comparison. The output carries a grade, a task status, evidence and charges. Read them as distinct signals. Status says where the task is. Grade says what the comparison concluded, if anything. Evidence says what supports that conclusion. Charges say what was billed under the quote. A null grade is no conclusion, which is not the same as a conflict between person and account. Card numbers are unsupported input, and no transfer happens unless the quote explicitly authorizes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothetical scenario and expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetically, a seller named in your records submits a new CLABE. Your onboarding service sends the authorized CURP and CLABE with the accepted quote and a fresh request UUID. The synthetic contract example returns exactly these fields: status review, grade null, verified false, automatic_business_approval false. The expected handling is: keep the current payout account active, record the new CLABE as a pending change, attach the request UUID, and route the case to your own review queue. Do not show the seller a rejection, because nothing was concluded against them. Do not switch the account, because verified is false and automatic_business_approval is false. This fixture output is a contract illustration produced without any provider request; it is not evidence of a live check or a customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed application design versus implemented behavior
&lt;/h2&gt;

&lt;p&gt;Implemented behavior, as documented: the comparison accepts the listed inputs and reports grade, status, evidence and charges, with grades and actions governed by the live quote and current capabilities. Merchant execution requires current capabilities, authorization and the accepted quote. Proposed design, which is yours to build: a decision table that maps status and grade to an internal action. For example, review or a null grade maps to hold; a supported grade with evidence maps to your own policy check before any change. Even a positive grade never flips automatic_business_approval in your system; your rule, recorded separately, decides whether the beneficiary changes.&lt;/p&gt;

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

&lt;p&gt;If the call fails technically, keep the old beneficiary and retry with the same request UUID rather than a new one, so you do not pay for or record two comparisons. If the quote expired or changed, obtain a current quote before resubmitting; do not reuse stale terms. If required evidence or consent is missing, stop and collect it instead of submitting. If a grade appears that your table does not recognize, treat it as hold and check current capabilities, since supported grades follow the live quote. Log charges as reported in the output rather than assuming a fixed amount.&lt;/p&gt;

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

&lt;p&gt;Before enabling beneficiary changes on this result: the fixture output status review, grade null, verified false, automatic_business_approval false leads to a hold, not a rejection or a switch; retries reuse the stable request UUID; the quote used is current and accepted; CURP and CLABE are authorized and consents stored; no card input is sent; no transfer occurs without explicit quote authorization; unknown grades hold; charges are recorded from the output; and the final beneficiary change is made by your own recorded rule. Passing the synthetic example shows your handling logic, not live business permission or provider coverage.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Separate a name mismatch from missing evidence in CURP and CLABE onboarding</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 10:47:20 +0000</pubDate>
      <link>https://dev.to/ismurray/separate-a-name-mismatch-from-missing-evidence-in-curp-and-clabe-onboarding-o35</link>
      <guid>https://dev.to/ismurray/separate-a-name-mismatch-from-missing-evidence-in-curp-and-clabe-onboarding-o35</guid>
      <description>&lt;p&gt;A Mexico onboarding flow submits an authorized CURP and CLABE for one identity and beneficiary-account comparison. The answer is not a clean pass. Is that a conflict between the person and the account, or simply no conclusion yet? Treating both as rejection blocks legitimate businesses; treating both as acceptable can let a wrong beneficiary through. This article proposes how an application could read verification grade, task status, evidence and charges as separate fields, using the synthetic contract example whose tested output is status review, grade null, verified false and automatic_business_approval false. That output is a contract illustration, not live acceptance.&lt;/p&gt;

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

&lt;p&gt;Your service lets a business register or change the CLABE that receives payouts. Before accepting it, you submit to the mx_identity_account task the authorized CURP and CLABE, the current quote, the required evidence and consents, and a stable request UUID. The decision is not pass or reject. It is a three-way question: does the result show a conflict between identity and account, does it show that evidence is missing or incomplete, or is the task simply not finished? Each answer leads somewhere different. A mismatch supported by evidence is a reason to stop the beneficiary change. Missing evidence is a reason to request more or route to manual review. An unfinished task is a reason to wait or recover, not to decide. The product boundary is explicit: no conclusion is not a conflict, and nothing in the output is an automatic business approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the product returns and what it does not
&lt;/h2&gt;

&lt;p&gt;The output contract names four things: verification grade, task status, evidence and charges. Supported grades and required actions follow the live quote, so your code should not hard-code a grade list taken from an article; read what the current capabilities and accepted quote allow. The boundary matters as much as the output. The comparison is not an arbitrary person lookup: you only submit identifiers you are authorized to check. It does not claim that a bank holds a CURP as a direct identifier. It does not accept unsupported card input instead of a CLABE. It does not move money unless the quote explicitly authorizes a transfer. Your application should store each field on its own rather than collapsing them into one boolean, because a single boolean hides exactly the difference between a mismatch and missing evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothetical scenario and expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetically, a synthetic seller asks to replace its payout CLABE. Your proposed handler keeps the old CLABE active, creates a request UUID, attaches the accepted quote and consents, and calls the task. In the synthetic contract example the tested output is exactly: status review, grade null, verified false, automatic_business_approval false. Read it field by field. status review means the task has not reached a final conclusion and needs review. grade null means no grade was assigned, which is not a low grade and not a mismatch. verified false means you must not treat the beneficiary as verified. automatic_business_approval false means even a later positive grade would not, by itself, grant your business permission. The correct proposed action is to keep the change pending, leave payouts on the existing account and record that the outcome is unresolved. No live provider request is performed by this example, and its synthetic result is not permission to act on a real business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed application design versus implemented behavior
&lt;/h2&gt;

&lt;p&gt;Implemented, according to the product contract: the task accepts authorized CURP and CLABE with a bound quote, returns grade, status, evidence and charges, and refuses unconditional approval. Also implemented in the content example: a synthetic output with the four fields above, executed locally without network. Proposed, and your responsibility: a beneficiary-change state machine with states such as pending_check, needs_evidence, blocked_mismatch and accepted_by_policy; a rule that only a supported grade backed by evidence, plus your own business policy, moves a change to accepted; and a separate ledger entry for charges reported by the task. The proposed design never infers a mismatch from grade null, and never infers approval from verified true alone. Your business rules, not the comparison, grant the right to pay a new account.&lt;/p&gt;

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

&lt;p&gt;Handle each failure on its own path. If the call times out or the task stays in review, reuse the same request UUID and use the recover step rather than submitting again, so one beneficiary change does not become two charged tasks. If evidence or consents are missing, ask the business for them and keep the change pending. If the quote has expired or does not cover the requested action, obtain a new quote before any retry; do not assume an old quote still authorizes a transfer. If the input is a card number or otherwise unsupported, reject it locally before calling the task. If the grade indicates a conflict supported by evidence, block the change and notify a reviewer, without exposing identifiers in logs. Record charges exactly as reported; a failed or incomplete task is not a reason to invent a cost.&lt;/p&gt;

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

&lt;p&gt;Before relying on this flow, confirm: the handler stores grade, status, evidence and charges as separate fields; grade null with status review keeps the change pending and the old CLABE active; verified false never enables payouts to the new account; automatic_business_approval false is respected and your own policy is required for acceptance; retries reuse the stable request UUID; the quote is current and its authorization is checked before any transfer; unsupported card input is rejected locally; no arbitrary lookup of people you are not authorized to check; logs exclude CURP and CLABE values. Finally, label every fixture result as synthetic. Passing this checklist locally shows your state handling is consistent; it does not show live coverage, a real customer or business permission. Live execution requires current capabilities, enrollment, authorization and the accepted quote.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Use an IP risk signal in signup without calling it identity verification</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 10:38:42 +0000</pubDate>
      <link>https://dev.to/ismurray/use-an-ip-risk-signal-in-signup-without-calling-it-identity-verification-2448</link>
      <guid>https://dev.to/ismurray/use-an-ip-risk-signal-in-signup-without-calling-it-identity-verification-2448</guid>
      <description>&lt;p&gt;A signup handler receives an IP risk result and someone proposes rejecting anything above a threshold, or labeling low scores as verified users. Both moves treat a network signal as a verdict about a person. This article shows how to keep an IP risk check as one input to your own decision, using the synthetic contract sample for the registration_risk task, and lists what must be true before any real check runs.&lt;/p&gt;

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

&lt;p&gt;Your SaaS or marketplace signup collects an IP address and you want to know whether the connection looks risky. The decision you face is not whether to trust the person; it is what your application does with a signal that describes a network origin. The product output is defined narrowly: IP risk signals and available explanatory evidence for the event, not an identity or fraud verdict. A risk score alone must not approve or reject a person. So the design question becomes: which branch of your signup does a signal move the user into, and which evidence does a human or a later step still need?&lt;/p&gt;

&lt;h2&gt;
  
  
  What the contract asks for and returns
&lt;/h2&gt;

&lt;p&gt;Inputs are an authorized public IP address, a consent reference, a stable request UUID and an explicit trial or paid mode. The stable UUID lets a retried request be recognized as the same event rather than a second check. The explicit mode matters because paid mode requires confirmed prepaid credit; nothing should silently switch from trial to a charge. Real execution also requires current supplier readiness and an eligible merchant. The tested sample output in the supplied material is: status unavailable, ip_risk_score null, verified false, automatic_business_approval false. This is a synthetic contract example, not live acceptance, and no live provider request is performed by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hypothetical worked scenario
&lt;/h2&gt;

&lt;p&gt;Hypothetically, a developer wires the signup form so that after email entry the backend sends the authorized IP, the consent reference and a new request UUID in trial mode. The supplier is not ready, so the result matches the fixture: status unavailable, ip_risk_score null, verified false, automatic_business_approval false. A naive handler reads null as zero and lets the account through as low risk. A stricter naive handler reads unavailable as risky and blocks it. The proposed design does neither: it records the event as unscored, continues signup through your normal controls, and keeps the same request UUID so a later retry is not counted as a new event. The verified field stays false in every case, because this product never verifies identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expected fixture outputs to assert
&lt;/h2&gt;

&lt;p&gt;Write tests that assert exactly these values for the sample: status equals unavailable; ip_risk_score is null, not 0; verified is false; automatic_business_approval is false. Add assertions in your own code that a null score never maps to a low-risk label, that no branch sets an identity-verified flag from this result, and that automatic_business_approval false prevents your handler from granting or denying an account on the IP signal alone. These tests check your application logic against the contract shape. They do not prove a supplier answered, that a merchant is eligible, or that any real user was assessed.&lt;/p&gt;

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

&lt;p&gt;Treat unavailable as its own state, separate from low risk and from high risk. If credit is not confirmed, do not attempt paid mode; surface a configuration problem rather than a user-facing rejection. If consent is missing, do not send the IP at all. On a timeout or retry, reuse the request UUID. When a real score does arrive, route it to a review or additional-check branch, together with any explanatory evidence, instead of a final approve or reject. Log what was asked, in which mode and what came back, without storing more personal data than your consent covers.&lt;/p&gt;

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

&lt;p&gt;Before enabling a real check, confirm: the IP is public and its use is authorized; a consent reference exists per event; each event has one stable request UUID; mode is explicit; paid mode is blocked without confirmed prepaid credit; supplier readiness and merchant eligibility are checked at run time, not assumed from documentation; unavailable and null are handled distinctly from low risk; no code path labels a user verified from this result; no code path approves or rejects a person on the score alone; tests assert the four fixture fields above. Passing synthetic tests is evidence about your handler, not live business permission.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Separate the transfer principal, source cost and fee in a bank-name quote</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:50:34 +0000</pubDate>
      <link>https://dev.to/ismurray/separate-the-transfer-principal-source-cost-and-fee-in-a-bank-name-quote-156p</link>
      <guid>https://dev.to/ismurray/separate-the-transfer-principal-source-cost-and-fee-in-a-bank-name-quote-156p</guid>
      <description>&lt;p&gt;A Mexico payout team wants to compare a beneficiary name against bank evidence for one CLABE. The quote may include a microtransfer. If your ledger records the whole quote as one fee, you cannot tell what left your account as transfer principal, what the evidence source charged, and what the service fee was. This article proposes how an application could keep those amounts apart, how to read the synthetic contract example, and what to check before trusting any result. The example is a synthetic contract illustration, not a live check or live acceptance.&lt;/p&gt;

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

&lt;p&gt;Your payout integration must check the name on a new beneficiary CLABE. The product compares an expected beneficiary name with supported bank evidence for one authorized CLABE. Its inputs are the CLABE, expected beneficiary name, holder type, current quote, authorization reference and a stable request UUID. A quoted microtransfer may apply. That matters because a microtransfer moves money to a recipient; it is not only a service charge. Your decision is whether to accept a quote at all and, if you do, how to record it so finance, support and audit see three different things: the transfer principal sent to the recipient, any source cost incurred obtaining evidence, and the fee. Folding them into one number hides who received money, why, and whether spending was authorized for that recipient.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the product contract states versus what your application designs
&lt;/h2&gt;

&lt;p&gt;Implemented contract, as documented: the output is a task state and a name comparison with its evidence and charges; uncertain similarity requires review. A microtransfer requires explicit recipient and spending authority. The result is not document identity and not proof of current account control. Merchant execution requires current capabilities, authorization and the accepted quote. The listed tools are mexico_account_name_capabilities, quote_mexico_account_name, verify_mexico_account_name, get_mexico_account_name and recover_mexico_account_name. Proposed application design, which is yours to build: a quote record with three separate amount lines (principal, source cost, fee), a recipient field bound to the CLABE, a spending-authority reference, and a rule that no line may be accepted unless its authority exists. The product does not create these ledger lines for you, and this article does not claim a specific quote layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hypothetical scenario and the expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetical: a marketplace seller asks to route payouts to a new CLABE and gives an expected name. Your service calls the capabilities tool, then requests a quote. Suppose the quote shows that a microtransfer may apply. Your design splits it: the principal line names the CLABE holder as recipient; the source cost and fee lines name the evidence path and the service. An operator holding spending authority for that recipient accepts it, and the request is sent with the same stable request UUID. The synthetic contract example returns exactly: status review, name_match inconclusive, verified false, automatic_business_approval false. Read it literally. Review means a person must look. Inconclusive is not a mismatch and not a match. Verified false means no verified conclusion. Automatic_business_approval false means no payout detail may change automatically. No live provider request is performed by this example.&lt;/p&gt;

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

&lt;p&gt;If capabilities do not list the task, do not quote. If the quote has expired or changed, request a new one rather than reusing the old acceptance. If a microtransfer appears without a named recipient or spending authority, stop before submitting. If the result is review or inconclusive, keep the old payout CLABE active and open a review case; never treat uncertain similarity as a pass. If a task stays pending or a worker restarts, use get or recover with the same request UUID instead of submitting again, so one change request cannot produce two charges. Record which charge lines actually occurred versus were only quoted. A technical failure should not silently become an accepted spend in your ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits to keep visible
&lt;/h2&gt;

&lt;p&gt;A name comparison does not establish who the seller is and does not prove they control the account today. It covers one authorized CLABE, not a portfolio. Documentation and the synthetic example are not evidence of a live check or of any customer. Synthetic output grants no live business permission. Product feedback counts are aggregates and currently show no observed use, so nothing here claims effectiveness. Current access, supported inputs and charges follow current capabilities and the accepted quote, not this article.&lt;/p&gt;

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

&lt;p&gt;Before trusting the integration: the quote is stored with principal, source cost and fee as separate lines; each microtransfer line names a recipient and a spending-authority reference; the authorization reference and stable request UUID are persisted before submission; a retry reuses the UUID; the fixture output status review, name_match inconclusive, verified false and automatic_business_approval false routes to manual review and leaves payout details unchanged; an expired quote forces a new quote; recovery does not duplicate a task; and your notes label the fixture as synthetic contract evidence, not live acceptance.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Check a new beneficiary CLABE name before changing payout details</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:44:35 +0000</pubDate>
      <link>https://dev.to/ismurray/check-a-new-beneficiary-clabe-name-before-changing-payout-details-1ele</link>
      <guid>https://dev.to/ismurray/check-a-new-beneficiary-clabe-name-before-changing-payout-details-1ele</guid>
      <description>&lt;p&gt;A seller on your Mexico marketplace asks to change the CLABE that receives payouts. If you swap it without checking, the next payout may reach someone else's account. This article shows how a name comparison against supported bank evidence fits that decision. It covers task state, the name_match result, charges and why an inconclusive similarity goes to review. The example is a synthetic contract illustration, not a live check or proof of account control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem and your decision
&lt;/h2&gt;

&lt;p&gt;A payout change request is a high-impact edit. The seller submits a new 18-digit CLABE and expects the next payout to land there. Your team must decide one thing: does the name the bank evidence associates with this CLABE match the beneficiary you expect closely enough to apply the change, or does a person need to review it first? The comparison answers a narrow question. It is not document identity, it does not prove that the requester currently controls the account, and it is not business approval. Your application keeps the old payout details active until its own rule says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the product accepts and returns
&lt;/h2&gt;

&lt;p&gt;According to the product contract, one request carries the CLABE, the expected beneficiary name, the holder type, the current quote, an authorization reference and a stable request UUID. The output is a task state and a name comparison, together with its evidence and charges. Uncertain similarity requires review. A quoted microtransfer may apply to obtain evidence; it requires explicit recipient authority and spending authority. Reuse the same request UUID when retrying so one payout change maps to one task. Merchant execution depends on current capabilities, a valid authorization and the accepted quote. Running a well-formed CLABE format check first avoids spending a paid request on an input that cannot pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothetical scenario and expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetical: a seller registered as a company changes payout details, and the expected beneficiary name differs from the evidence name by an abbreviation in the legal suffix. The synthetic contract example returns exactly: status review, name_match inconclusive, verified false, automatic_business_approval false. Read each field literally. status review means the task needs a human decision. name_match inconclusive means the similarity is neither a clear match nor a clear conflict. verified false means nothing was confirmed. automatic_business_approval false means the result never authorizes the payout change by itself. No live provider request is performed by this example, so it demonstrates contract handling only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed application design
&lt;/h2&gt;

&lt;p&gt;This is a proposal, not implemented behavior. Store the change request as pending with the old CLABE still active. Record the request UUID, the accepted quote and the authorization reference before calling the check. On status review or name_match inconclusive, open a review case showing the expected name, the returned evidence and the charges, and keep payouts on the old details. Only a reviewer or an explicit policy should approve the switch; automatic_business_approval false must never be overridden by code. If a microtransfer is quoted, ask an authorized person to confirm both the recipient and the spend before accepting the quote. Notify the seller that the change is under review rather than rejected.&lt;/p&gt;

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

&lt;p&gt;Treat a timeout or unknown task state as incomplete, not as a mismatch. Retry retrieval with the same request UUID instead of submitting a new task, which could create a second charge. An inconclusive result is not a conflict, and a conflict still deserves review before any account action. If the quote expired or authority is missing, stop and request a fresh quote or authorization instead of proceeding. Record charges exactly as returned, keeping any microtransfer separate from the check fee. Never treat the synthetic fixture as live permission or as evidence that production coverage exists for a given bank.&lt;/p&gt;

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

&lt;p&gt;Before enabling this flow, confirm: old payout details stay active until approval; every request has a stable UUID, holder type, authorization reference and accepted quote; the fixture yields status review, name_match inconclusive, verified false and automatic_business_approval false, and your handler routes it to review; retries reuse the UUID; microtransfers require recorded recipient and spending authority; charges are stored per task; timeouts do not become rejections; review screens show evidence, not just a score; and nobody describes the check as identity verification or proof of account control. Product feedback so far is aggregate observation only and proves nothing about outcomes.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Recover a pending SPEI evidence request without creating a duplicate</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:40:00 +0000</pubDate>
      <link>https://dev.to/ismurray/recover-a-pending-spei-evidence-request-without-creating-a-duplicate-ph4</link>
      <guid>https://dev.to/ismurray/recover-a-pending-spei-evidence-request-without-creating-a-duplicate-ph4</guid>
      <description>&lt;p&gt;Your reconciliation job submitted a CEP check for an existing SPEI payment and got back status pending. Hours later the task is still pending, a worker restarted, and someone proposes simply submitting the same payment again. That shortcut can create a second task for one transfer and leave two records that disagree. This article proposes how an application could recover the original task instead, what the synthetic contract example returns, and how to keep pending or unavailable results inconclusive.&lt;/p&gt;

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

&lt;p&gt;A CEP check answers a narrow question about a payment that already exists: can the original evidence be retrieved, is its signature valid, and does it bind exactly to the transaction you described? It does not initiate a transfer, and it does not prove present account control, identity or that a payout is safe. Source access restrictions can delay a task, so pending is a normal state, not an error. The decision you face is what your job does when a task stays pending: resubmit, wait, or recover the existing task. This article argues for recovery keyed to one stored task, and for treating every non-final result as inconclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the product contract defines
&lt;/h2&gt;

&lt;p&gt;The documented inputs are operation date, tracking key, sending and receiving institution codes, account, amount and authorization reference. The output reports task status, original-evidence availability, signature status and exact transaction binding. The listed tools are mexico_cep_capabilities, submit_mexico_cep_check, get_mexico_cep_check and recover_mexico_cep_check. Merchant execution requires current capabilities, authorization and an accepted quote. Everything in this article that goes beyond those facts is a proposed application design, not implemented provider behavior. The example shown here is a synthetic contract illustration: no live provider request is performed, and it is not evidence of a live check or of any customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed design: one payment, one stored task
&lt;/h2&gt;

&lt;p&gt;Before submitting, the application could build a deduplication key from the exact transaction inputs: operation date, tracking key, both institution codes, account, amount and authorization reference. It stores that key with the returned task identifier in durable storage, in the same step that records the submission. When a worker restarts or a timer fires, the job looks up the key first. If a task exists, it calls get or recover for that task rather than submit. Only when no stored task exists, and authorization is still current, would a new submission be justified. A changed amount or tracking key is a different transaction and deserves a new key, not a silent overwrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothetical scenario and expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetically, a finance team checks one transfer. The job submits, stores the key and task, and the worker restarts. On resume it finds the stored task and recovers it instead of resubmitting. The synthetic contract example returns exactly: status pending, original_available false, signature_status not_checked, transaction_binding not_checked, verified false. The correct reading is that nothing has been established yet. The order stays unreleased, the record stays at one task, and no field is promoted to a conclusion. Your fixture test should assert these five values literally and assert that the task count for the key remains one.&lt;/p&gt;

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

&lt;p&gt;Treat pending and unavailable as inconclusive, never as failure of the payment and never as approval. If original_available is false, there is no original evidence to judge. If signature_status or transaction_binding is not_checked, do not infer validity from other fields. A delay caused by source access restrictions should lead to a bounded wait and a later recovery, recorded with timestamps, not to a duplicate submission. If authorization has lapsed or capabilities no longer list the route, stop and escalate to a person. Even a fully verified result only describes existing payment evidence; release decisions remain your business rules, and the result does not prove who controls the account today.&lt;/p&gt;

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

&lt;p&gt;Before trusting the job, confirm: the deduplication key covers every transaction input; the key and task identifier are written durably before waiting; restart paths call get or recover, never submit, when a task exists; the fixture assertion matches status pending, original_available false, signature_status not_checked, transaction_binding not_checked and verified false; pending and unavailable map to an inconclusive state with no order release; a changed input produces a new key; lapsed authorization halts work; and notes state clearly that the synthetic example is not live acceptance or business permission.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Phone validation API: tell an unknown result apart from a provider failure</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 06:34:16 +0000</pubDate>
      <link>https://dev.to/ismurray/phone-validation-api-tell-an-unknown-result-apart-from-a-provider-failure-33f3</link>
      <guid>https://dev.to/ismurray/phone-validation-api-tell-an-unknown-result-apart-from-a-provider-failure-33f3</guid>
      <description>&lt;p&gt;A signup form calls a phone validation API and gets neither valid nor invalid. Two fixture branches produce that shape, unknown and provider_failure, and both return state unknown with valid null. Only the next field tells them apart. This article uses the checked four-branch synthetic example to show why your code should route them differently, what the fixture outputs exactly, and what Basic Lookup can never prove: possession, ownership or OTP delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: two nulls that mean different things
&lt;/h2&gt;

&lt;p&gt;Your signup handler receives a phone result where valid is null and line_type is null. The decision you face is what happens to the person waiting on the form. A common shortcut treats every null as a rejection. That merges two situations. In one, the check ran and could not say anything useful about the number. In the other, the provider call itself failed, so nothing was learned at all. Rejecting in the second case punishes a user for an outage. Accepting blindly in either case pretends a check happened when it did not. The downloadable example makes the difference explicit in the next field, and your application should keep it explicit too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exact fixture outputs for the two ambiguous branches
&lt;/h2&gt;

&lt;p&gt;Every branch of the local example returns mode synthetic_fixture, otp_sent false and live_line_type_available false. The unknown branch returns state unknown, valid null, line_type null and next review_or_existing_otp. The provider_failure branch returns state unknown, valid null, line_type null and next bounded_retry_then_review. For comparison, normal returns state simulated, valid true, line_type mobile and next continue_customer_policy, and otp returns state unsupported, valid null, line_type null and next customer_otp_provider. An unsupported branch name raises UNSUPPORTED_FIXTURE. Note that state alone cannot separate unknown from provider_failure; if your code switches only on state, you have already lost the distinction. The line_type mobile in the normal branch is a synthetic value: the current free Basic Lookup route does not request line type, which is why live_line_type_available is false everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  A hypothetical scenario
&lt;/h2&gt;

&lt;p&gt;Imagine a small B2B team whose signup form collects a phone number for account contact. This is a hypothetical design, not a description of any real deployment. A user submits a number. In test, the team runs the unknown branch: the proposed handler stores the attempt, lets signup continue into a review queue, and optionally triggers the team's existing OTP flow if it already has one. Next they run provider_failure: the proposed handler retries a bounded number of times with a short delay, and if the answer still does not arrive, it places the attempt in review rather than rejecting it. Finally they run otp and confirm the screen does not wait for a code, because otp_sent is false and the fixture never sends one. The retry count, delay and review rules are choices this team would make; the fixture only names the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is implemented versus what you design
&lt;/h2&gt;

&lt;p&gt;Implemented: a dependency-free Python function that maps a branch name to a fixed dictionary, with no network calls and no credentials, plus unit tests. Not implemented: any retry loop, review queue, OTP sending or real provider adapter. Those are caller-owned. The anonymous HTTP trial is a separate three-scenario check and should not be confused with this four-branch downloadable example. Basic Lookup, where an eligible business enrolls for it, is an adjunct signal with a technical rate limit of 60 requests per minute; it does not establish possession, ownership, reachability or OTP delivery. If you need proof the person can receive a code, use your own OTP provider. If you need identity, that is a different check entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure handling rules worth keeping
&lt;/h2&gt;

&lt;p&gt;Keep retries bounded: a provider failure that loops forever becomes a hung signup. Record which branch produced a decision, so a later reviewer can see that a null came from a failure rather than a verdict. Never convert unknown or provider_failure into an automatic rejection. Treat an unrecognized branch or error as a hard stop in tests, mirroring UNSUPPORTED_FIXTURE. Do not let a synthetic valid true unlock anything real; a fixture result is not live business permission. Search interest in phone validation APIs is not evidence that any specific design works for your users.&lt;/p&gt;

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

&lt;p&gt;Before deciding whether a sandbox integration fits your task, confirm: all four branches run locally and match the outputs above field for field; your handler distinguishes unknown from provider_failure using next, not state; provider_failure has a retry limit and a review fallback; no branch leads to automatic rejection solely because valid is null; your UI never waits for a code when otp_sent is false; no code path reads line_type from a live Basic Lookup result; logs record mode synthetic_fixture so test data cannot be mistaken for production; and the team agrees in writing that a phone check proves neither possession, ownership nor identity. If all pass, the next step is an optional saved sandbox request, not a live rollout.&lt;/p&gt;

&lt;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Reject a mistyped CLABE before requesting a paid bank-name check</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 05:32:08 +0000</pubDate>
      <link>https://dev.to/ismurray/reject-a-mistyped-clabe-before-requesting-a-paid-bank-name-check-2kk6</link>
      <guid>https://dev.to/ismurray/reject-a-mistyped-clabe-before-requesting-a-paid-bank-name-check-2kk6</guid>
      <description>&lt;p&gt;A Mexico payout form or payment-file import receives an 18-digit CLABE. If one digit is mistyped, a later paid bank-name or account check is spent on an input that could never pass. This article shows how a local format check returns structure_valid, checksum_valid and an institution code without network calls, and why a correct checksum still proves nothing about whether the account exists, can receive funds or belongs to a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem and your decision
&lt;/h2&gt;

&lt;p&gt;Your payout onboarding collects a CLABE and then, in a separate authorized workflow, may request a paid verification such as a bank-name comparison. Typos are common: a swapped pair of digits, a missing digit, stray spaces from copy and paste. Sending those inputs onward costs a request and produces a confusing failure for the user. The decision is where to stop a bad input. The proposal here: run a local checksum check first, reject only inputs that are structurally wrong or fail the checksum, and treat a pass as permission to ask the next question, not as an answer to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the local utility actually does
&lt;/h2&gt;

&lt;p&gt;The implemented behavior is narrow. The check_clabe_format tool accepts an 18-digit CLABE, optionally separated by spaces or hyphens, and runs locally with no network. It reports structure_valid (the input normalizes to 18 digits), checksum_valid (the final control digit matches the weighted calculation over the first 17 digits) and institution_code (the first three digits). It performs no live bank query and makes no account-ownership assertion. A correct checksum does not prove that an account exists, can receive funds or belongs to a person. Anything beyond these fields, such as routing the user or calling a paid check, is proposed application design that your code must own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked hypothetical scenario and expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetically, a payment-file import row contains the sample value 032180000118359719. The tested local output is: structure_valid true, checksum_valid true, institution_code "032", scope "checksum_only", account_exists null, ownership_verified false, network_calls 0, charged false. Read each field literally. account_exists is null because nothing was asked about existence, not because existence was confirmed or denied. ownership_verified is false because ownership was never in scope. network_calls 0 and charged false confirm that the check cost nothing and contacted no one. In the proposed design, this row may proceed to the next authorized step. If an operator instead typed 032180000118359791, swapping the last two digits, you should expect checksum_valid to be false and the row to stop before any paid request; confirm that expectation in your own tests rather than assuming it.&lt;/p&gt;

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

&lt;p&gt;Separate three outcomes. Structure failure: fewer or more than 18 digits, or non-digit characters after removing spaces and hyphens; ask the user to re-enter the value and show the digit count. Checksum failure: 18 digits but the control digit does not match; say the number appears mistyped and request correction, without suggesting the account is fraudulent or closed. Pass: proceed, but keep the record labelled checksum_only. Do not translate a pass into account_verified in your database, and do not auto-release payouts on it. For batch imports, flag failing rows individually instead of rejecting the whole file, and never silently correct a digit, because a guessed digit can point to someone else's valid-looking number.&lt;/p&gt;

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

&lt;p&gt;This utility is a local input filter. It does not query banks, confirm that an institution code is currently active for payouts, verify a beneficiary name or establish who controls the account. Merchant examples here are synthetic contract illustrations, not live acceptance. Any paid identity or beneficiary-account verification is a separate workflow that requires current capabilities, authorization and an accepted quote. No product feedback supports claims of saved cost or improved conversion; the observed counts are zero and imply nothing causal.&lt;/p&gt;

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

&lt;p&gt;Before shipping: the sample input yields exactly the eight fields above; spaced and hyphenated forms of the same number give the same result; a 17-digit input sets structure_valid false; a swapped-digit input sets checksum_valid false and triggers no downstream request; stored records keep scope checksum_only and ownership_verified false; user messages for structure and checksum failures are distinct; no code path marks an account verified or releases funds from this check alone; batch imports report failing rows individually; and the later paid check is only called through its own authorized workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  A standalone local example
&lt;/h2&gt;

&lt;p&gt;Save this as a Python file and run it with Python 3.10 or newer. It makes no network requests and incurs no provider charges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_clabe_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clabe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clabe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clabe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CLABE_FORMAT_INPUT_INVALID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[ \-]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clabe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[0-9]{18}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;digit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;structure_valid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;checksum_valid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;digit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;institution_code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;checksum_only&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;account_exists&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ownership_verified&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;network_calls&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;charged&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;032180000118359719&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;032180000118359710&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;check_clabe_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Expected local outputs
&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"structure_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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checksum_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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"institution_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"032"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checksum_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_exists"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ownership_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;"network_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;"charged"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"structure_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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checksum_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;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;"institution_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"032"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checksum_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_exists"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ownership_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;"network_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;"charged"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"structure_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;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;"checksum_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;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;"institution_code"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checksum_only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_exists"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ownership_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;"network_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;"charged"&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;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;p&gt;Disclosure: this tutorial was drafted with AI and checked against executable local examples and an independent factual review. Sample values are test inputs, not verified people or accounts. Application policy remains the caller’s responsibility.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>A payment screenshot versus a signed CEP: what can your reconciliation job accept?</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Tue, 03 Nov 2026 04:58:26 +0000</pubDate>
      <link>https://dev.to/ismurray/a-payment-screenshot-versus-a-signed-cep-what-can-your-reconciliation-job-accept-35c8</link>
      <guid>https://dev.to/ismurray/a-payment-screenshot-versus-a-signed-cep-what-can-your-reconciliation-job-accept-35c8</guid>
      <description>&lt;p&gt;A customer sends a screenshot of a SPEI transfer and asks you to release an order. Your reconciliation job must decide whether that image is evidence or only a claim. This article proposes how a job could treat a CEP check result built from task status, original-evidence availability, signature status and exact transaction binding, and why pending or unavailable must stay inconclusive. The example output shown is a synthetic contract illustration, not a live check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: an image is not the original record
&lt;/h2&gt;

&lt;p&gt;A screenshot of a banking app shows an amount, a date and perhaps a tracking key. It can be cropped, edited or simply belong to a different transfer. A CEP (Comprobante Electrónico de Pago) is the original electronic receipt for an existing SPEI payment, and it can carry a signature and data that bind it to one operation. The reader's decision is narrow: what evidence may your reconciliation job accept before it marks an invoice as paid, and what must it leave open? A screenshot can be useful as a hint to start a lookup, but it should not be the thing your job accepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the product checks, and what it does not
&lt;/h2&gt;

&lt;p&gt;The mx-cep product retrieves the CEP for an authorized existing payment and inspects its signature and transaction binding. Inputs are the operation date, tracking key, sending and receiving institution codes, account, amount and an authorization reference. Output reports task status, original-evidence availability, signature status and exact transaction binding. Boundaries matter: it does not initiate a transfer, and it does not prove present account control, identity or that a payout is safe. Source access restrictions can delay a task, so a result may stay pending for a while. Merchant execution requires current capabilities, authorization and an accepted quote; documentation is not evidence of a live check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothetical scenario and the expected fixture output
&lt;/h2&gt;

&lt;p&gt;Hypothetical: an invoice for a synthetic merchant shows an inbound transfer and a customer screenshot. The job submits the operation date, tracking key, both institution codes, account, amount and authorization reference. The synthetic contract example returns exactly: status pending, original_available false, signature_status not_checked, transaction_binding not_checked, verified false. No live provider request is performed by this example. The correct reading is that nothing has been established yet: the original is not available, the signature was not checked and the CEP was not matched to the invoice. The invoice stays open, labeled as awaiting evidence, not as paid and not as fraudulent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed acceptance rule for your application
&lt;/h2&gt;

&lt;p&gt;This is proposed application design, not implemented product behavior. Accept a payment as evidenced only when verified is true, original_available is true, and both signature_status and transaction_binding report a successful check that you have mapped explicitly. Compare the bound transaction against your own expected amount, account, institutions and date rather than trusting a single flag. Treat pending, unavailable, not_checked and any unknown value as inconclusive. Keep the decision to ship goods or release funds as a separate business rule owned by your code; a matching CEP says a payment happened, not that the counterparty is who they claim or that a payout to them is safe.&lt;/p&gt;

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

&lt;p&gt;When a task is pending, store its identifier with the invoice and a deadline, then use get to read status or recover to resume it, instead of submitting a second request for the same payment. Delays from source access restrictions are expected; do not convert them into rejection. If the deadline passes with no conclusion, route the invoice to manual review with the reason recorded. If binding fails, flag a mismatch for a person to investigate; it may be a typo in inputs as well as a wrong transfer. Never let a timeout, an error or a screenshot fill the gap by default.&lt;/p&gt;

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

&lt;p&gt;Before relying on this in reconciliation, confirm: the synthetic output above maps to awaiting evidence, not paid; verified false never marks an invoice paid; pending and unavailable stay inconclusive; all six inputs and the authorization reference are recorded per request; repeated runs reuse the existing task instead of creating duplicates; mismatches go to review; release decisions remain separate from payment evidence; and the team understands the fixture is a synthetic contract example, not live acceptance, with no live capabilities assumed until your merchant authorization and accepted quote are in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect this product
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/tools/mx-cep/" rel="noopener noreferrer"&gt;https://gridzen.ai/tools/mx-cep/&lt;/a&gt; · Inspect the documented input, output and boundary. This command lists the product without executing a live request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gridzen tools &lt;span class="nt"&gt;--product&lt;/span&gt; mx-cep
&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/tools/mx-cep/" rel="noopener noreferrer"&gt;https://gridzen.ai/tools/mx-cep/&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;/p&gt;

&lt;p&gt;&lt;a href="https://gridzen.ai/tools/mx-cep/?source=dev&amp;amp;content_id=article-demand-a581ffbda64e714a" rel="noopener noreferrer"&gt;Inspect this tool&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does not initiate a transfer or prove present account control, identity or a safe payout. Source access restrictions can delay a task.&lt;/p&gt;

&lt;p&gt;Written with AI assistance and independently reviewed. Installation and content examples are not live acceptance or customer evidence.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
    <item>
      <title>Identity verification webhook: recover a pending state without polling</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Mon, 02 Nov 2026 16:00:31 +0000</pubDate>
      <link>https://dev.to/ismurray/identity-verification-webhook-recover-a-pending-state-without-polling-25nn</link>
      <guid>https://dev.to/ismurray/identity-verification-webhook-recover-a-pending-state-without-polling-25nn</guid>
      <description>&lt;p&gt;Your onboarding service created a hosted verification session, stored it as pending, and the webhook never arrived. You must decide what the session becomes and what happens next. This article uses the checked local example: SQLite persistence, a deadline, and a recover step that expires the session locally. It never queries a provider, and a missing callback never becomes approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a pending session with no callback
&lt;/h2&gt;

&lt;p&gt;A seller starts hosted identity verification. Your service writes a pending session with a deadline and waits. The callback may be lost, delayed or blocked by a misconfigured endpoint. If nothing acts on the deadline, the session stays pending forever. The user is stuck and your support team has no clear state to read. The tempting shortcuts are to approve after a while or to silently reject. Both turn missing evidence into a decision. Your real choice is narrower: when the deadline passes, move the session to a terminal state that means no answer arrived, then route the next action to review or to an authorized status refresh that your own integration owns.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the local example actually implements
&lt;/h2&gt;

&lt;p&gt;The downloadable example is a dependency-free Python 3.10+ fixture that needs no credentials or network. The Identity class stores sessions in SQLite with the fields status, deadline, granted and grants, plus policy_version and allow_action. create accepts only identifiers beginning with fixture- and a timeout between 1 and 3600 seconds, and writes status pending with deadline equal to now plus timeout. recover takes a session and the current time. If deadline is less than or equal to now and status is pending, review or unknown, it sets status to expired. It then returns the read result plus next set to review_or_authorized_provider_refresh and provider_polled set to false. That flag is literal: recover does not call any provider. Events accept only mode synthetic_fixture and source fixture_callback; anything else raises PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED. There is no real signature verification. This is separate from the anonymous three-scenario HTTP trial and from the four-branch phone example.&lt;/p&gt;

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

&lt;p&gt;Hypothetical: a synthetic seller session fixture-session is created at time 0 with the default 60-second timeout, so deadline is 60. No callback arrives. Your scheduler, which you would build, calls recover at time 61. The tested output is: mode synthetic_fixture, session fixture-session, status expired, deadline 60, permission_granted false, grant_count 0, live_permission_changed false, policy_version 1, allow_action true, next review_or_authorized_provider_refresh, provider_polled false. Because state lives in SQLite, restarting the process against the same database file and calling recover gives the same result. If a late fixture Approved event arrives after the deadline, the event handler records it and keeps the session expired; it grants nothing. allow_action true only means local policy would allow an action; it is not a grant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure handling your application owns
&lt;/h2&gt;

&lt;p&gt;Proposed design, not implemented behavior: run recover from a durable scheduler, not from the request that created the session. Treat expired as no answer, not as declined, and show the user a retry path. If you have an authorized provider route, a bounded status refresh may follow, with signature validation and reconciliation specific to that provider; the example establishes neither. Errors in the fixture are explicit: reading an unknown session raises SESSION_NOT_FOUND, and an event for an unknown session or with the wrong tenant or subject raises BINDING_MISMATCH. Duplicate events are recorded by event id and return the current state without a second grant. Log these codes rather than retrying blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits to keep in view
&lt;/h2&gt;

&lt;p&gt;The fixture proves local state transitions only. It does not show that a real person completed a hosted journey, that a provider callback was signed, or that any live permission changed. Synthetic permission_granted is a local record, never real business permission. The hosted_link points to an invalid example domain and link_executable is false. Engineering tests cover transitions, bindings, duplicates, restart and timeouts; they are not evidence of production success or adoption. Live route availability needs separate confirmation.&lt;/p&gt;

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

&lt;p&gt;Run the restart and timeout recovery tests and confirm they pass. Create a session, restart, and confirm status is still pending with the same deadline. Call recover after the deadline and confirm status expired, permission_granted false and provider_polled false. Send a late synthetic Approved and confirm no grant. Confirm unknown sessions raise SESSION_NOT_FOUND or BINDING_MISMATCH. Confirm your design never maps expired to approval. Write down which future component will own signature validation and authorized refresh before you request a sandbox integration.&lt;/p&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/missing-verification-webhook/" rel="noopener noreferrer"&gt;https://gridzen.ai/guides/missing-verification-webhook/&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-982d2970a626582b" rel="noopener noreferrer"&gt;Create a free sandbox account&lt;/a&gt; · &lt;a href="https://gridzen.ai/guides/missing-verification-webhook/?source=dev&amp;amp;content_id=article-demand-982d2970a626582b" 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>Identity verification API timeout: recover without assuming approval</title>
      <dc:creator>murray wang</dc:creator>
      <pubDate>Sun, 01 Nov 2026 16:01:15 +0000</pubDate>
      <link>https://dev.to/ismurray/identity-verification-api-timeout-recover-without-assuming-approval-59g7</link>
      <guid>https://dev.to/ismurray/identity-verification-api-timeout-recover-without-assuming-approval-59g7</guid>
      <description>&lt;p&gt;Your onboarding service created a hosted verification session, and then the answer never arrived in time. The call timed out, or the callback is late. The tempting shortcuts are to let the user through or to reject them. Neither is justified: a timeout means the check could not complete, which is neither approval nor decline. This article walks through the checked synthetic identity example, its local deadline and its explicit failure branches, so you can decide whether a sandbox integration fits your onboarding task. Search interest for this term is not measured here; unknown volume stays unknown.&lt;/p&gt;

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

&lt;p&gt;A seller signs up, your service opens a hosted identity session, and the provider is slow, unreachable or silent. Your code must still decide what the user sees and what your application allows. The decision is not "pass or fail" but which state to record and who acts next. The GridZen provider-failures guide frames this clearly: an unavailable provider is an operational failure, not a low-risk signal. The current Twilio adapter returns a 503 through the API when its provider is unavailable, and the alpha does not silently convert that failure to allow. A review queue or authorized fallback must be implemented by your calling workflow. Keep three outcomes separate: a mismatch is a returned assertion, insufficient evidence means your policy lacks a required signal, and a technical failure means the check could not complete. Record HTTP status separately from the decision outcome; a 503 should never be logged as decline or allow.&lt;/p&gt;

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

&lt;p&gt;The downloadable example is a dependency-free synthetic fixture, not a live provider adapter. Its identity part persists sessions in SQLite. Calling create stores a session with status pending and a deadline equal to now plus timeout; with the defaults (now 0, timeout 60) the deadline is 60. The returned hosted_link points at an invalid example domain, link_executable is false, and next is send_real_provider_link_only_after_authorization. The event method accepts only synthetic callbacks: mode must be synthetic_fixture and source must be fixture_callback, otherwise it raises PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED. Event identifiers must start with fixture-. There is no real signature verification. An event that arrives at or after the deadline marks the session expired instead of applying its status. An unrecognized status marks it unknown rather than guessing. The recover method expires a pending, review or unknown session whose deadline has passed, purely locally: it returns provider_polled false and never contacts a provider. Any permission_granted value is a local fixture record; live_permission_changed is always false.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked hypothetical scenario with expected outputs
&lt;/h2&gt;

&lt;p&gt;Hypothetical: a synthetic session fixture-session is created at time 0 with a 60-second timeout. The provider call times out and no callback arrives. At time 61 your worker calls recover. The tested output is: mode synthetic_fixture, session fixture-session, status expired, deadline 60, permission_granted false, grant_count 0, live_permission_changed false, policy_version 1, allow_action true, next review_or_authorized_provider_refresh, provider_polled false. Nothing was approved, and the next step points to a human review or an authorized provider refresh, not to an automatic retry that assumes success. Compare the happy path, also tested: an Approved fixture event with policy true inside the deadline yields status approved, permission_granted true, grant_count 1. Replaying the same event returns the identical record with grant_count still 1. A later Declined correction yields status declined, permission_granted false, grant_count 1. A late callback after expiry cannot revive the session, because expired is terminal for ordinary events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure handling in your own design
&lt;/h2&gt;

&lt;p&gt;The following is proposed application design, not implemented GridZen behavior. Treat a request timeout as technical failure: store the HTTP outcome, keep the session pending, and rely on the persisted deadline rather than an in-memory timer. Use bounded retries only where the upstream operation and your workflow permit them, then hand off to review. Never map a timeout, malformed response, missing credentials or provider rejection onto the same state. When recover returns expired, show the user a neutral message and offer a new session through your normal path, or route to an owner. The fixture's error codes are useful contract checks: an event for an unknown session raises BINDING_MISMATCH, and reading an unknown session raises SESSION_NOT_FOUND. Even after approved, the handoff output sets verified false and next_action read_current_evidence_and_policy_before_explicit_decision, because Approved is not unconditional permission.&lt;/p&gt;

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

&lt;p&gt;The fixture proves local state transitions only. It does not verify real signatures, does not poll any provider, and its grants are not live business permissions. For hosted Didit onboarding, creation, signed callback and authoritative retrieval are evidenced, but a new person passing the full hosted journey remains untested, and end-to-end live customer action remains unverified. Live route availability requires separate confirmation. The anonymous three-scenario HTTP trial is a different artifact from this downloadable example; do not treat one as the other.&lt;/p&gt;

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

&lt;p&gt;Before requesting a sandbox integration, confirm: pending sessions persist with a deadline that survives a restart; a timeout is recorded as technical failure, separate from decision outcome; recover at or after the deadline returns status expired, permission_granted false and provider_polled false; a late or duplicate callback does not change grant_count; a Declined correction revokes the local grant; unknown statuses become unknown, not approved; error codes BINDING_MISMATCH and SESSION_NOT_FOUND are handled; review or authorized fallback has a named owner; and no log line records a 503 as allow or decline. If these pass locally, the next step is an optional saved sandbox request, not live permission.&lt;/p&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/provider-failures/" rel="noopener noreferrer"&gt;https://gridzen.ai/guides/provider-failures/&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-64d70530375c5120" rel="noopener noreferrer"&gt;Create a free sandbox account&lt;/a&gt; · &lt;a href="https://gridzen.ai/guides/provider-failures/?source=dev&amp;amp;content_id=article-demand-64d70530375c5120" 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>
  </channel>
</rss>
