[{"data":1,"prerenderedAt":232},["ShallowReactive",2],{"project-en-form-protection":3,"recommendation-mention-sources":170},{"id":4,"title":5,"body":6,"category":136,"context":137,"cover":138,"coverAlt":139,"demo":140,"description":141,"extension":142,"featured":143,"locale":144,"meta":145,"navigation":146,"order":147,"path":148,"published":146,"repository":140,"results":149,"role":159,"seo":160,"slug":161,"stack":162,"stem":167,"year":168,"__hash__":169},"projects\u002Fen\u002Fprojects\u002Fform-protection.md","Bot protection for checkout and customer forms",{"type":7,"value":8,"toc":120},"minimark",[9,14,18,21,25,28,32,37,40,44,47,51,54,57,61,69,73,76,79,83,113,117],[10,11,13],"h2",{"id":12},"the-problem","The problem",[15,16,17],"p",{},"Customers on inSales stores use forms to place orders, sign in, register, recover access, leave reviews and contact the store. Merchants sign in to their own accounts. All of these are targets for bots: a bot attack could create hundreds of orders per second. The protection still has to let a real buyer press Send and get an order through.",[15,19,20],{},"The old flow asked people to tick \"I'm not a robot\" before sending. In 2025 a second problem came in through the product team: merchants were concerned about a foreign service processing personal data, and some of their customers could not reach Google. They needed an alternative, switched on with one setting instead of reworking every form. The hard part was the platform itself. Forms lived in two generations of customer accounts, three generations of checkout, server-rendered templates, Vue apps, standard store widgets and merchant-edited code, often several on one page.",[10,22,24],{"id":23},"my-role","My role",[15,26,27],{},"I owned the frontend: integration points, shared code and components, test builds, QA support, release and maintenance. Backend developers implemented server-side token verification, and I agreed the request contracts with them. Outside the frontend I made one targeted Rails change. I delivered the Google stage in 2023, added provider choice in 2025 (main release on 26 August 2025) and continued maintenance into 2026.",[10,29,31],{"id":30},"what-i-built","What I built",[33,34,36],"h3",{"id":35},"the-check-became-part-of-the-submit-flow","The check became part of the submit flow",[15,38,39],{},"I moved Google reCAPTCHA Invisible into the existing submit flow. The form runs its usual field validation, waits for a CAPTCHA token, then continues, and the server verifies the token before accepting the request. The provider can still show a challenge when it needs one. AJAX forms got their own continuation, so they didn't turn into a page navigation. Existing rules stayed in place: password sign-in asks for a check only after failed attempts, and sign-in by one-time code has its own forms and handlers.",[33,41,43],{"id":42},"each-verification-result-belongs-to-one-form","Each verification result belongs to one form",[15,45,46],{},"A customer opens the contact form, starts a check and closes the challenge. Then they submit a review and pass the check there. If both handlers wait for one shared event, the review's token can submit the contact form instead. An early event bus made it worse by replaying earlier data to new subscribers. I bound each verification callback to the form being submitted, together with its CAPTCHA instance and token.",[33,48,50],{"id":49},"one-setting-three-integration-layers","One setting, three integration layers",[15,52,53],{},"I proposed a CAPTCHA type setting in the store's settings and rolled it out behind a feature flag. I agreed with the backend how to pass it into Liquid templates, so system forms, the library and theme widgets read the same value. The chosen provider applies after the page reloads; an open form never swaps providers in the middle of filling it in.",[15,55,56],{},"I implemented the provider in three places that match how the platform is built: a module in CommonJS, the shared storefront JavaScript library, for its 4 form families (contact, reviews, comments, one-click orders); reusable Slim templates for customer accounts and checkout 1.0 and 2.0; and a Vue component for checkout 3.0 and merchant sign-in. Rendering, getting a token and submitting stayed separate steps, so a form whose submit is driven by its own script could use render only.",[33,58,60],{"id":59},"one-script-per-page","One script per page",[15,62,63,64,68],{},"A system form and a store widget could both load SmartCaptcha. The second load created a new ",[65,66,67],"code",{},"window.smartCaptcha"," that knew nothing about instances rendered by the first. The customer saw a Send button that did nothing, depending on which part of the page loaded first. I changed the loaders in the templates and the library to reuse a script already on the page.",[33,70,72],{"id":71},"failure-paths-and-old-accounts","Failure paths and old accounts",[15,74,75],{},"Closing a challenge used to leave the form stuck in a sending state. I reset that state so the customer can try again, and a retry or a long wait gets a fresh token. The first rollout of system forms in August 2025 had to be rolled back: on some existing stores no CAPTCHA appeared at all. Those accounts had no settings record, so a condition expecting \"google\" or \"yandex\" matched neither. I added a Google fallback for that case to the Rails account model. The fix shipped three days later.",[15,77,78],{},"I fixed a scroll jump in Safari after submission. On some Android devices the keyboard covered the field inside the provider's challenge, which we could not fully fix on our side, so I escalated it to Yandex Cloud with a test page and video.",[10,80,82],{"id":81},"results","Results",[84,85,86,90,93,96],"ul",{},[87,88,89],"li",{},"2 providers, Google reCAPTCHA and Yandex SmartCaptcha, selected by 1 store setting for supported forms.",[87,91,92],{},"Integrated into 3 checkout generations, 2 customer-account generations, 4 storefront form families and merchant sign-in.",[87,94,95],{},"Supported forms submit with an invisible check instead of a mandatory checkbox.",[87,97,98,99,106,107,112],{},"Store developers got documented APIs and examples for adding either provider to custom forms, published for ",[100,101,105],"a",{"href":102,"rel":103},"https:\u002F\u002Fdocs.liquidhub.ru\u002F%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%D1%8B%20%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%8B%20%D1%81%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%BC%D0%B8%20%D0%BC%D0%B0%D0%B3%D0%B0%D0%B7%D0%B8%D0%BD%D0%B0\u002FreCAPTCHA\u002Frecaptcha-common\u002F",[104],"nofollow","Google reCAPTCHA"," and ",[100,108,111],{"href":109,"rel":110},"https:\u002F\u002Fwww.insales.ru\u002Fcollection\u002Fprimery-raboty-s-dannymi-magazina\u002Fproduct\u002Fyandex-captcha",[104],"Yandex SmartCaptcha",".",[10,114,116],{"id":115},"constraints","Constraints",[15,118,119],{},"Provider choice did not make the check mandatory everywhere: each form kept its own rules for when a check is required. Forms built into older themes and custom widgets with their own integration needed manual updates, and not every admin form is covered.",{"title":121,"searchDepth":122,"depth":122,"links":123},"",2,[124,125,126,134,135],{"id":12,"depth":122,"text":13},{"id":23,"depth":122,"text":24},{"id":30,"depth":122,"text":31,"children":127},[128,130,131,132,133],{"id":35,"depth":129,"text":36},3,{"id":42,"depth":129,"text":43},{"id":49,"depth":129,"text":50},{"id":59,"depth":129,"text":60},{"id":71,"depth":129,"text":72},{"id":81,"depth":122,"text":82},{"id":115,"depth":122,"text":116},"Storefront platform","inSales","\u002Fimages\u002Fwork\u002Fform-protection\u002Finvisible-captcha-submit-flow.svg","Diagram of a form submit that validates fields, waits for a CAPTCHA token and resumes only the form that requested it",null,"Invisible CAPTCHA built into the submit flow of inSales checkout, customer accounts and store forms, followed by a choice between Google reCAPTCHA and Yandex SmartCaptcha through one store setting.","md",false,"en",{},true,11,"\u002Fen\u002Fprojects\u002Fform-protection",[150,153,156],{"value":151,"label":152},"2","CAPTCHA providers, Google and Yandex, available for supported store forms",{"value":154,"label":155},"3","checkout generations integrated: 1.0, 2.0 and 3.0",{"value":157,"label":158},"4","storefront form families in the shared JavaScript library","Frontend owner of the integration, shared CAPTCHA code and release",{"title":5,"description":141},"form-protection",[163,164,165,166,105,111],"JavaScript","Vue","Slim","Liquid","en\u002Fprojects\u002Fform-protection","Oct 2023 – Jul 2026","qpebxhQX3ir_ZKZA442cXnfMGk6gwcDeGYCLtZivrvw",[171,182,187,202,212,217,222,227],{"stem":172,"name":173,"date":174,"featured":122,"published":146,"mentions":175},"recommendations\u002Fandrey-stikheev","Andrey Stikheev","2026-09-08",[176,179],{"project":177,"quote":178},"product-image-editor","Among his most notable projects was a sophisticated browser-based image editor built with TypeScript and Fabric.js, now used by thousands of merchants.",{"project":180,"quote":181},"abandoned-carts","He also developed the frontend for a paid abandoned-cart product and contributed to the production migration from Vue 2 to Vue 3.",{"stem":183,"name":184,"date":185,"featured":140,"published":146,"mentions":186},"recommendations\u002Fdaniil-churakov","Daniil Churakov","2026-08-27",[],{"stem":188,"name":189,"date":190,"featured":129,"published":146,"mentions":191},"recommendations\u002Fdmitry-sadovsky","Dmitry Sadovsky","2026-08-22",[192,195,197,200],{"project":193,"quote":194},"visual-storefront","For years he was the driving force behind our visual storefront editor — a large, complex application where merchants build their online stores.",{"project":177,"quote":196},"It became the tool merchants use to design product images right inside the admin panel — the kind of component companies usually buy rather than build.",{"project":198,"quote":199},"image-editor","What impressed me most was the infographic editor he built essentially from scratch: a full canvas-based graphic editor with artboards, shapes, text styling, templates, and undo\u002Fredo history, powered by an editor core he developed himself.",{"project":180,"quote":201},"Our abandoned carts functionality — with multi-channel customer messaging, communication history, and analytics — was largely his work, from UI down to the details of how requests are cancelled and states stay in sync.",{"stem":203,"name":204,"date":205,"featured":206,"published":146,"mentions":207},"recommendations\u002Fegor-fedkin","Egor Fedkin","2026-08-31",1,[208,210],{"project":177,"quote":209},"Today, the tool is actively used by around 3,000 users.",{"project":180,"quote":211},"It has already generated approximately twice the revenue needed to cover its development costs.",{"stem":213,"name":214,"date":215,"featured":140,"published":146,"mentions":216},"recommendations\u002Femmanuel-stephen","Emmanuel Stephen","2026-10-11",[],{"stem":218,"name":219,"date":220,"featured":140,"published":146,"mentions":221},"recommendations\u002Fevgeny-lonshakov","Евгений Лоншаков","2026-09-21",[],{"stem":223,"name":224,"date":225,"featured":140,"published":146,"mentions":226},"recommendations\u002Froman-gorbachev","Роман Горбачев","2026-09-17",[],{"stem":228,"name":229,"date":230,"featured":140,"published":146,"mentions":231},"recommendations\u002Fvladimir-bedarev","Bedarev Vladimir","2026-09-16",[],1793195987911]