[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"technologies":3,"\u002Fblog\u002Fssl-certificate-pinning-flutter":167,"team-members":2146,"blog-topics":2488,"mdc--12ftuw-key":2529,"glossary":2649},[4,10,15,21,26,32,38,43,48,53,58,63,68,73,77,82,87,92,97,103,108,113,118,123,128,133,138,143,147,152,157,162],{"title":5,"description":6,"slug":7,"category":8,"icon":9},"Android","Every app we ship reaches Android — platform APIs, background execution, permissions, and the store requirements that come with them.","android","framework","logos:android-icon",{"title":11,"description":12,"slug":13,"category":8,"icon":14},"Angular","The front-end framework we work in when a product is already Angular — a batteries-included structure that suits large, long-lived apps.","angular","logos:angular-icon",{"title":16,"description":17,"slug":18,"category":19,"icon":20},"Dart","The language every Flutter app we ship is written in — sound null safety, real pattern matching, and a compiler that targets native and the web.","dart","language","logos:dart",{"title":22,"description":23,"slug":24,"category":8,"icon":25},"Django","The Python framework we use when a product needs an admin, auth and a real data model on day one rather than a hand-rolled service.","django","logos:django-icon",{"title":27,"description":28,"slug":29,"category":30,"icon":31},"Docker","Every service we build ships as a container, so what runs on a laptop, in CI and in production is one artefact rather than three of them.","docker","infrastructure","logos:docker-icon",{"title":33,"description":34,"slug":35,"category":36,"icon":37},"Fastlane","The release automation behind our mobile work — signing, builds and store uploads run from CI instead of from one engineer's laptop on release day.","fastlane","tools","logos:fastlane",{"title":39,"description":40,"slug":41,"category":8,"icon":42},"Flutter","Our primary mobile stack since 2018 — one Dart codebase shipping to iOS, Android, web and desktop without a separate team per platform.","flutter","logos:flutter",{"title":44,"description":45,"slug":46,"category":36,"icon":47},"Git","Every project we touch lives in Git — reviewed pull requests, CI on every branch, and a history that still makes sense months later.","git","logos:git-icon",{"title":49,"description":50,"slug":51,"category":19,"icon":52},"Go","Our backend language for real-time APIs and services under load — small binaries, fast builds, and concurrency that stays readable.","go","logos:gopher",{"title":54,"description":55,"slug":56,"category":30,"icon":57},"Google Cloud","The cloud our production workloads run on — managed Kubernetes, storage and networking, without hand-built servers nobody wants to maintain.","gcp","logos:google-cloud",{"title":59,"description":60,"slug":61,"category":36,"icon":62},"Gradle","The build system every Android release goes through — product flavours, signing configs, and the dependency wiring under a Flutter app.","gradle","logos:gradle",{"title":64,"description":65,"slug":66,"category":30,"icon":67},"Helm","How we package a Kubernetes deployment — service, config, secrets and ingress as one versioned unit that can be promoted and rolled back.","helm","logos:helm",{"title":69,"description":70,"slug":71,"category":19,"icon":72},"Kotlin","What we reach for when a Flutter app needs real Android underneath it — platform channels, background work, and native SDK integrations.","kotlin","logos:kotlin-icon",{"title":74,"description":75,"slug":76,"category":8,"icon":72},"Kotlin Multiplatform","Sharing business logic across iOS and Android while each platform keeps its own native UI — the alternative when Flutter is not the right fit.","kmp",{"title":78,"description":79,"slug":80,"category":30,"icon":81},"Kubernetes","How we run services in production — Helm-packaged deployments, rollouts that can be rolled back, and scaling that does not need a person at 3am.","kubernetes","logos:kubernetes",{"title":83,"description":84,"slug":85,"category":30,"icon":86},"NATS","Lightweight messaging between services — publish\u002Fsubscribe and request\u002Freply without the operational weight of a full broker cluster.","nats","logos:nats-icon",{"title":88,"description":89,"slug":90,"category":8,"icon":91},"Nuxt","Vue with server rendering, routing and SEO handled — how we build marketing sites and web apps that must be fast and indexable on first load.","nuxt","logos:nuxt-icon",{"title":93,"description":94,"slug":95,"category":19,"icon":96},"PHP","Where we work with an existing PHP backend — extending it, integrating with it, and building the mobile and web clients it has to serve.","php","logos:php",{"title":98,"description":99,"slug":100,"category":101,"icon":102},"PostgreSQL","Our default database — the one we reach for unless a product gives us a specific reason not to, from schema design through to index tuning.","postgres","database","logos:postgresql",{"title":104,"description":105,"slug":106,"category":19,"icon":107},"Python","Our language for backends, data work and AI integrations — including the Python bindings we ship for our own Rust tooling.","python","logos:python",{"title":109,"description":110,"slug":111,"category":8,"icon":112},"React","The front-end library we work in when a product is already React — components, hooks, and the ecosystem that has grown around them.","react","logos:react",{"title":114,"description":115,"slug":116,"category":101,"icon":117},"Redis","Where we put data that has to be fast and can be rebuilt — caches, sessions, rate limits, and the queues behind a product's slow paths.","redis","logos:redis",{"title":119,"description":120,"slug":121,"category":19,"icon":122},"Ruby","The language our mobile release automation is written in — Fastlane lanes, custom actions, and the CI glue that ships builds to the stores.","ruby","logos:ruby",{"title":124,"description":125,"slug":126,"category":19,"icon":127},"Rust","Where we go when performance and correctness both matter — document rendering, CLI tooling, and services that have to stay fast and predictable.","rust","simple-icons:rust",{"title":129,"description":130,"slug":131,"category":101,"icon":132},"SQLite","The database that ships inside the app — local caches, offline-first storage, and anything that still has to work with no network.","sqlite","logos:sqlite",{"title":134,"description":135,"slug":136,"category":8,"icon":137},"Strapi","A headless CMS we reach for when editors need to own the content — a real admin and a clean API, without building either from scratch.","strapi","logos:strapi-icon",{"title":139,"description":140,"slug":141,"category":19,"icon":142},"Swift","What we reach for when a Flutter app needs real iOS underneath it — platform channels, native SDK integrations, widgets and App Clips.","swift","logos:swift",{"title":144,"description":145,"slug":146,"category":8,"icon":142},"SwiftUI","Apple's UI framework, where we build the native surfaces a Flutter app cannot own — widgets, App Clips, extensions and system integrations.","swiftui",{"title":148,"description":149,"slug":150,"category":8,"icon":151},"Tailwind CSS","How we style every front end we build — utility classes and design tokens instead of a stylesheet that only ever grows and is never deleted.","tailwind","logos:tailwindcss-icon",{"title":153,"description":154,"slug":155,"category":19,"icon":156},"TypeScript","The default for everything we write for the browser and for Node services — types that catch integration bugs before they reach a review.","typescript","logos:typescript-icon",{"title":158,"description":159,"slug":160,"category":8,"icon":161},"Vue","Our front-end framework for admin panels, merchant dashboards and product web apps that have to stay maintainable for years, not sprints.","vue","logos:vue",{"title":163,"description":164,"slug":165,"category":36,"icon":166},"Whisper","OpenAI's open-source speech recognition model — the transcription engine behind voice input, running as both a backend service and on-device in the app.","whisper","simple-icons:openai",{"id":168,"title":169,"author":170,"body":171,"description":2127,"extension":2128,"hero":2129,"meta":2132,"metaDescription":2133,"metaTitle":2134,"navigation":2135,"ogDescription":2136,"path":2137,"seo":2138,"slug":2139,"stem":2140,"summary":2141,"timestamp":2142,"topic":2143,"updated":2144,"__hash__":2145},"blog_en\u002Fblog\u002Fssl-certificate-pinning-flutter.md","Your SSL Pinning Probably Isn't Working","nixan",{"type":172,"value":173,"toc":2105},"minimark",[174,182,188,195,198,203,258,262,268,280,283,289,295,298,303,313,316,319,322,326,337,405,411,415,422,429,437,441,447,464,467,471,474,539,546,557,560,566,569,572,578,582,586,589,596,599,722,725,775,782,786,793,796,992,995,1243,1250,1254,1265,1349,1352,1359,1363,1375,1390,1397,1401,1408,1423,1434,1437,1449,1453,1456,1461,1483,1494,1505,1516,1519,1540,1546,1751,1769,1783,1800,1820,1839,1854,1857,1881,1885,1888,1891,1898,1904,1908,1911,1914,1917,1920,1924,1927,2061,2065,2082,2085,2101],[175,176,177,178],"p",{},"Google's own Android documentation says certificate pinning ",[179,180,181],"strong",{},"\"is not recommended for Android apps.\"",[175,183,184,185],{},"Apple says ",[179,186,187],{},"\"in most cases, pinning is not necessary and should be avoided.\"",[175,189,190,191,194],{},"And here is a Chromium engineer answering a question about how to set pinning up properly. He opens his reply like this: ",[179,192,193],{},"\"While pinning in general should never be encouraged, as it actively harms the security and stability of the Internet at large, if you're pinning to a private CA, Android's Network Security Config is the preferred approach.\""," He is literally telling you how to do it, and he still cannot help himself.",[175,196,197],{},"Meanwhile, pinning ships in about half the mobile apps we look at, including the ones that land on our desk for an audit. Good news: if you are on Flutter, chances are it is not working. Bad news: same sentence.",[199,200,202],"h2",{"id":201},"key-takeaways","Key takeaways",[204,205,206,213,219,225,231,237,252],"ul",{},[207,208,209,212],"li",{},[179,210,211],{},"Pinning is a narrow control against a threat that has shrunk",", with a blast radius the size of your entire user base. Certificate Transparency, CAA records and the Android 7 trust-store change took most of its original job away.",[207,214,215,218],{},[179,216,217],{},"It is genuinely necessary for finance, health and government"," — apps that have to verify against OWASP's MAS-L2, the defence-in-depth profile for software handling sensitive data. For everyone else, Google, Apple, Cloudflare and half of OWASP say don't.",[207,220,221,224],{},[179,222,223],{},"Certificate lifetimes are collapsing",": 200 days as of March 2026, 100 in 2027, 47 in 2029. If you pin a leaf and cannot rotate without an app release, that is eight forced releases a year.",[207,226,227,230],{},[179,228,229],{},"Pin the key (SPKI), never the certificate",", and always ship a backup pin for an offline key you control.",[207,232,233,236],{},[179,234,235],{},"Never ship straight to hard-fail"," — and build your own failure telemetry, because neither platform has any.",[207,238,239,251],{},[179,240,241,242,246,247,250],{},"On Flutter, ",[243,244,245],"code",{},"\u003Cpin-set>"," in ",[243,248,249],{},"network_security_config.xml"," does nothing for Dart traffic."," Dart runs its own TLS stack. The bug has been open since January 2022.",[207,253,254,257],{},[179,255,256],{},"The 30-minute test"," at the end of this article tells you which of these describes your app. The naive version of that test lies to you.",[199,259,261],{"id":260},"what-pinning-actually-protects-you-from","What pinning actually protects you from",[175,263,264,265],{},"Quick baseline, so we are using the same words. Normally your app trusts any certificate signed by any certificate authority in the system trust store, and that store holds around a hundred and fifty root certificates. Pinning narrows that to something specific: ",[179,266,267],{},"this key, and nothing else.",[175,269,270,271,274,275,279],{},"A note on the name first. Everyone says \"SSL pinning,\" us included, in the title of this article. ",[179,272,273],{},"SSL has been dead for over a decade"," — it is all ",[276,277],"term",{"slug":278},"tls"," now. But that is what people search for, so it stays. Just know that if somebody says \"SSL\" and means it in 2026, that tells you something about how fresh their sources are.",[175,281,282],{},"Now the honest threat model. Two things pinning genuinely defends against:",[175,284,285,288],{},[179,286,287],{},"A compromised or rogue certificate authority."," This is pinning's actual job. The textbook case is DigiNotar in 2011: attackers breached a Dutch CA, issued fraudulent certificates for Google domains, and intercepted Iranian users. Pinning is what caught it — Chrome shipped a preloaded pin for google.com.",[175,290,291,294],{},[179,292,293],{},"Corporate interception."," DLP proxies, antivirus software doing TLS inspection, an MDM installing a root at the system level. Pinning breaks all of them. That is the point — but it also means you break your enterprise customer's legitimate proxy along with the attacker's.",[175,296,297],{},"And now the part the blog posts from 2015 do not cover, because none of it existed yet.",[299,300,302],"h3",{"id":301},"the-rogue-ca-threat-has-shrunk-a-lot","The rogue-CA threat has shrunk a lot",[175,304,305,308,309,312],{},[179,306,307],{},"Certificate Transparency"," publishes every publicly trusted certificate to open, append-only logs. The mechanism launched in 2013, but it became mandatory in practice in 2018: Chrome started enforcing it in version 68, and Apple enforces it ",[179,310,311],{},"at the OS level",", which means inside apps, not just in Safari.",[175,314,315],{},"That is not theory. September 2015 was the first time CT caught a misissued certificate in the wild — an unauthorized certificate for google.com that Symantec produced during internal testing. No malicious intent, valid for a single day. It was also the first crack in what ended, in 2018, with browsers distrusting Symantec entirely.",[175,317,318],{},"A recent one surfaced in September 2025: the Croatian CA Fina had issued twelve unauthorized certificates for Cloudflare's 1.1.1.1, spread across February 2024 and August 2025. Nobody noticed for eighteen months; CT logs are what finally turned them up. And note — Fina is not in the mobile trust stores at all, so those certificates would never have validated on a phone anyway. The log caught them regardless of who trusts the CA. That is exactly the value.",[175,320,321],{},"On top of that: CAA records, which are DNS records declaring which CAs may issue for your domain, plus mandatory multi-perspective domain validation.",[299,323,325],{"id":324},"the-platform-already-did-some-of-this-for-you","The platform already did some of this for you",[175,327,328,329,332,333,336],{},"Starting with Android 7 — more precisely, ",[243,330,331],{},"targetSdk"," 24 — apps ",[179,334,335],{},"do not trust user-installed certificates"," by default. That has been the default since 2016. So the \"victim installed a malicious root\" scenario is not something pinning saves you from any more. The operating system beat you to it.",[338,339,340,360],"table",{},[341,342,343],"thead",{},[344,345,346,351,354,357],"tr",{},[347,348,349],"th",{},[243,350,331],{},[347,352,353],{},"System CAs",[347,355,356],{},"User-installed CAs",[347,358,359],{},"Cleartext HTTP",[361,362,363,379,391],"tbody",{},[344,364,365,369,372,376],{},[366,367,368],"td",{},"≤ 23",[366,370,371],{},"trusted",[366,373,374],{},[179,375,371],{},[366,377,378],{},"allowed",[344,380,381,384,386,389],{},[366,382,383],{},"24–27",[366,385,371],{},[366,387,388],{},"not trusted",[366,390,378],{},[344,392,393,396,398,400],{},[366,394,395],{},"≥ 28",[366,397,371],{},[366,399,388],{},[366,401,402],{},[179,403,404],{},"blocked by default",[175,406,407,408,410],{},"That table is keyed on ",[243,409,331],{},", not on the device's Android version — which is why an old app on a new phone still trusts whatever the user installed.",[299,412,414],{"id":413},"what-pinning-does-not-protect-against-at-all","What pinning does not protect against at all",[175,416,417,418,421],{},"OWASP's framing is blunt: if an attacker controls the device, they simply disable your pinning logic. On a rooted Android or a jailbroken iPhone that is one command in ",[243,419,420],{},"objection"," — and for Flutter there is dedicated tooling, which we will get to, because it is interesting.",[175,423,424,425,428],{},"So: ",[179,426,427],{},"pinning protects your real users from interception on a hostile network. It does not protect your API from the owner of the device."," If you are using pinning to hide keys or to stop reverse engineering, you have already lost. That is what server-side attestation is for — Play Integrity, App Attest — not client-side tricks.",[430,431,434],"info-box",{":leading-icon":432,":title":433},"lucide:scale","OWASP contradicts itself here, and it matters when someone hands you a report",[175,435,436],{},"MASVS does require pinning — but only for an app that must verify against MAS-L2, OWASP's defence-in-depth profile for software handling sensitive data. It is not a baseline control. The testing guide says plainly that if an app does not implement pinning, that should not be reported as a vulnerability. And the OWASP Pinning Cheat Sheet says: \"The first question should be, should I pin? The answer to this is probably never.\" That language was added in March 2023; it was not there before. So when a pentest report flags \"missing SSL pinning\" as a finding, that is usually an automated checklist, not a decision about your threat model.",[199,438,440],{"id":439},"do-you-actually-need-certificate-pinning","Do you actually need certificate pinning?",[175,442,443,444],{},"Short checklist. If any one of these is you — ",[179,445,446],{},"do not pin.",[204,448,449,452,455,458,461],{},[207,450,451],{},"You do not control both ends: your server and your app",[207,453,454],{},"You cannot update the pin set safely and quickly",[207,456,457],{},"Updating pins requires an app release",[207,459,460],{},"You cannot know the key pair before it goes into production",[207,462,463],{},"It is not a native mobile app",[175,465,466],{},"Who genuinely needs it: finance, health, government services — anywhere the data is sensitive and the threat model assumes a hostile network. That is the MAS-L2 case. Everyone else: probably not.",[299,468,470],{"id":469},"why-this-got-urgent-in-2026","Why this got urgent in 2026",[175,472,473],{},"In April 2025 the CA\u002FBrowser Forum voted to phase down TLS certificate lifetimes. Apple filed the ballot. The schedule:",[338,475,476,489],{},[341,477,478],{},[344,479,480,483,486],{},[347,481,482],{},"From",[347,484,485],{},"Maximum lifetime",[347,487,488],{},"Rotations per year",[361,490,491,502,517,528],{},[344,492,493,496,499],{},[366,494,495],{},"before March 2026",[366,497,498],{},"398 days",[366,500,501],{},"~1",[344,503,504,509,514],{},[366,505,506],{},[179,507,508],{},"15 March 2026",[366,510,511],{},[179,512,513],{},"200 days",[366,515,516],{},"~2",[344,518,519,522,525],{},[366,520,521],{},"15 March 2027",[366,523,524],{},"100 days",[366,526,527],{},"~4",[344,529,530,533,536],{},[366,531,532],{},"15 March 2029",[366,534,535],{},"47 days",[366,537,538],{},"~8",[175,540,541,542,545],{},"The 200-day tier is in force right now. Forty-seven days is roughly ",[179,543,544],{},"eight certificate rotations a year",". If you pin the leaf and you cannot update pins without shipping, that is eight forced app updates a year — with store review, and with users who do not update.",[175,547,548,549,552,553,556],{},"And the ecosystem is fighting pinning on purpose. Back in 2024, Let's Encrypt started ",[179,550,551],{},"picking the issuing intermediate at random",", specifically to break the habit of pinning intermediates. In November 2025 it carried the same policy into a new hierarchy of six intermediates, and the announcement spells it out: ",[179,554,555],{},"\"as before, each issuance will choose which intermediate to use at random, to discourage intermediate key pinning.\""," The most widely used CA on the internet has spent two years deliberately breaking that kind of pinning.",[175,558,559],{},"This is not malice. It is hard-won experience, and the browser world went through it first.",[175,561,562,565],{},[179,563,564],{},"HPKP"," — pinning via an HTTP header, specified in 2015 by Google engineers — failed completely. Chrome deprecated it in version 67 and killed it in version 72, in January 2019. Google's stated reasons: very low adoption, risk of denial of service, and hostile pinning.",[175,567,568],{},"The famous casualty is Smashing Magazine, October 2016: offline for four days for most of its readers. They rotated to a certificate with a new key, shipped a header containing only the new pin, and everyone holding a cached pin set was locked out. There was no rollback, because the old certificate had already expired.",[175,570,571],{},"On the mobile side, Barclays, November 2016. Reportedly the app pinned an outdated intermediate, the chain changed, and payments stopped — on the eve of Black Friday. To fix it in time, Symantec issued a certificate under the old intermediate, and had to violate the CA\u002FBrowser Forum's serial-number requirement to do it.",[175,573,574,575],{},"Same moral in both cases: ",[179,576,577],{},"pinning does not break when you are attacked. It breaks on an ordinary Tuesday, when somebody renews a certificate.",[199,579,581],{"id":580},"five-rules-if-you-are-pinning-anyway","Five rules if you are pinning anyway",[299,583,585],{"id":584},"rule-1-pin-the-key-not-the-certificate","Rule 1. Pin the key, not the certificate",[175,587,588],{},"Technically that is the SPKI — SubjectPublicKeyInfo, the structure inside the certificate that holds the public key. You pin the base64-encoded SHA-256 hash of it.",[175,590,591,592,595],{},"Why this is the whole ballgame: ",[179,593,594],{},"the certificate changes on every renewal, the key does not."," Pin the key and a same-key renewal never touches your pin. Pin the whole certificate fingerprint and it breaks every single time — twice a year at 200 days, eight times at 47.",[175,597,598],{},"Here is the one-liner for a live host:",[600,601,606],"pre",{"className":602,"code":603,"language":604,"meta":605,"style":605},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","openssl s_client -connect api.example.com:443 -servername api.example.com \u003C\u002Fdev\u002Fnull 2>\u002Fdev\u002Fnull \\\n  | openssl x509 -pubkey -noout \\\n  | openssl pkey -pubin -outform der \\\n  | openssl dgst -sha256 -binary \\\n  | openssl enc -base64\n","bash","",[243,607,608,650,670,691,709],{"__ignoreMap":605},[609,610,613,617,621,625,628,631,634,638,641,644,646],"span",{"class":611,"line":612},"line",1,[609,614,616],{"class":615},"sbgvK","openssl",[609,618,620],{"class":619},"s_sjI"," s_client",[609,622,624],{"class":623},"stzsN"," -connect",[609,626,627],{"class":619}," api.example.com:443",[609,629,630],{"class":623}," -servername",[609,632,633],{"class":619}," api.example.com",[609,635,637],{"class":636},"smGrS"," \u003C",[609,639,640],{"class":619},"\u002Fdev\u002Fnull",[609,642,643],{"class":636}," 2>",[609,645,640],{"class":619},[609,647,649],{"class":648},"s_hVV"," \\\n",[609,651,653,656,659,662,665,668],{"class":611,"line":652},2,[609,654,655],{"class":636},"  |",[609,657,658],{"class":615}," openssl",[609,660,661],{"class":619}," x509",[609,663,664],{"class":623}," -pubkey",[609,666,667],{"class":623}," -noout",[609,669,649],{"class":648},[609,671,673,675,677,680,683,686,689],{"class":611,"line":672},3,[609,674,655],{"class":636},[609,676,658],{"class":615},[609,678,679],{"class":619}," pkey",[609,681,682],{"class":623}," -pubin",[609,684,685],{"class":623}," -outform",[609,687,688],{"class":619}," der",[609,690,649],{"class":648},[609,692,694,696,698,701,704,707],{"class":611,"line":693},4,[609,695,655],{"class":636},[609,697,658],{"class":615},[609,699,700],{"class":619}," dgst",[609,702,703],{"class":623}," -sha256",[609,705,706],{"class":623}," -binary",[609,708,649],{"class":648},[609,710,712,714,716,719],{"class":611,"line":711},5,[609,713,655],{"class":636},[609,715,658],{"class":615},[609,717,718],{"class":619}," enc",[609,720,721],{"class":623}," -base64\n",[175,723,724],{},"And the same hash from a private key you have generated but not yet certified — which is how you compute a backup pin:",[600,726,728],{"className":602,"code":727,"language":604,"meta":605,"style":605},"openssl pkey -in backup.key -pubout -outform der \\\n  | openssl dgst -sha256 -binary \\\n  | openssl enc -base64\n",[243,729,730,751,765],{"__ignoreMap":605},[609,731,732,734,736,739,742,745,747,749],{"class":611,"line":612},[609,733,616],{"class":615},[609,735,679],{"class":619},[609,737,738],{"class":623}," -in",[609,740,741],{"class":619}," backup.key",[609,743,744],{"class":623}," -pubout",[609,746,685],{"class":623},[609,748,688],{"class":619},[609,750,649],{"class":648},[609,752,753,755,757,759,761,763],{"class":611,"line":652},[609,754,655],{"class":636},[609,756,658],{"class":615},[609,758,700],{"class":619},[609,760,703],{"class":623},[609,762,706],{"class":623},[609,764,649],{"class":648},[609,766,767,769,771,773],{"class":611,"line":672},[609,768,655],{"class":636},[609,770,658],{"class":615},[609,772,718],{"class":619},[609,774,721],{"class":623},[175,776,777,778,781],{},"The good news: both platforms natively support ",[179,779,780],{},"only"," SPKI, on Android and iOS both. So if you are pinning a whole certificate, you either hand-rolled it or you picked a library that does it for you — and almost certainly for no good reason.",[299,783,785],{"id":784},"rule-2-backup-pins-are-mandatory","Rule 2. Backup pins are mandatory",[175,787,788,789,792],{},"At least one key ",[179,790,791],{},"entirely under your control."," That is Google's phrasing, and \"entirely under your control\" is the load-bearing part. Not \"a second certificate from the same CA.\" A key pair you generated ahead of time and keep offline, one you can get a certificate for on demand. Its pin ships in the app alongside the live one and just sits there waiting.",[175,794,795],{},"That missing backup pin is exactly what turned Smashing Magazine's bad day into four days of downtime. They still had the old key — their host had kept it. What they had shipped in the header was the fingerprint of the new one only.",[600,797,801],{"className":798,"code":799,"language":800,"meta":605,"style":605},"language-xml shiki shiki-themes material-theme-lighter github-light github-dark","\u003C!-- res\u002Fxml\u002Fnetwork_security_config.xml -->\n\u003Cnetwork-security-config>\n  \u003Cdomain-config>\n    \u003Cdomain includeSubdomains=\"true\">api.example.com\u003C\u002Fdomain>\n    \u003Cpin-set expiration=\"2027-03-01\">\n      \u003C!-- live key -->\n      \u003Cpin digest=\"SHA-256\">YLh1dUR9y6Kja30RrAn7JKnbQG\u002FuEtLMkBgFF2Fuihg=\u003C\u002Fpin>\n      \u003C!-- offline backup key, never yet used to serve traffic -->\n      \u003Cpin digest=\"SHA-256\">sRHdihwgkaib1P1gxX8HFszlD+7\u002FgTfNvuAybgLPNis=\u003C\u002Fpin>\n    \u003C\u002Fpin-set>\n  \u003C\u002Fdomain-config>\n\u003C\u002Fnetwork-security-config>\n","xml",[243,802,803,809,822,832,870,891,897,929,935,963,973,983],{"__ignoreMap":605},[609,804,805],{"class":611,"line":612},[609,806,808],{"class":807},"sutJx","\u003C!-- res\u002Fxml\u002Fnetwork_security_config.xml -->\n",[609,810,811,815,819],{"class":611,"line":652},[609,812,814],{"class":813},"sP7_E","\u003C",[609,816,818],{"class":817},"sQzsp","network-security-config",[609,820,821],{"class":813},">\n",[609,823,824,827,830],{"class":611,"line":672},[609,825,826],{"class":813},"  \u003C",[609,828,829],{"class":817},"domain-config",[609,831,821],{"class":813},[609,833,834,837,840,844,847,851,854,856,859,863,866,868],{"class":611,"line":693},[609,835,836],{"class":813},"    \u003C",[609,838,839],{"class":817},"domain",[609,841,843],{"class":842},"s9AJx"," includeSubdomains",[609,845,846],{"class":813},"=",[609,848,850],{"class":849},"sjJ54","\"",[609,852,853],{"class":619},"true",[609,855,850],{"class":849},[609,857,858],{"class":813},">",[609,860,862],{"class":861},"su5hD","api.example.com",[609,864,865],{"class":813},"\u003C\u002F",[609,867,839],{"class":817},[609,869,821],{"class":813},[609,871,872,874,877,880,882,884,887,889],{"class":611,"line":711},[609,873,836],{"class":813},[609,875,876],{"class":817},"pin-set",[609,878,879],{"class":842}," expiration",[609,881,846],{"class":813},[609,883,850],{"class":849},[609,885,886],{"class":619},"2027-03-01",[609,888,850],{"class":849},[609,890,821],{"class":813},[609,892,894],{"class":611,"line":893},6,[609,895,896],{"class":807},"      \u003C!-- live key -->\n",[609,898,900,903,906,909,911,913,916,918,920,923,925,927],{"class":611,"line":899},7,[609,901,902],{"class":813},"      \u003C",[609,904,905],{"class":817},"pin",[609,907,908],{"class":842}," digest",[609,910,846],{"class":813},[609,912,850],{"class":849},[609,914,915],{"class":619},"SHA-256",[609,917,850],{"class":849},[609,919,858],{"class":813},[609,921,922],{"class":861},"YLh1dUR9y6Kja30RrAn7JKnbQG\u002FuEtLMkBgFF2Fuihg=",[609,924,865],{"class":813},[609,926,905],{"class":817},[609,928,821],{"class":813},[609,930,932],{"class":611,"line":931},8,[609,933,934],{"class":807},"      \u003C!-- offline backup key, never yet used to serve traffic -->\n",[609,936,938,940,942,944,946,948,950,952,954,957,959,961],{"class":611,"line":937},9,[609,939,902],{"class":813},[609,941,905],{"class":817},[609,943,908],{"class":842},[609,945,846],{"class":813},[609,947,850],{"class":849},[609,949,915],{"class":619},[609,951,850],{"class":849},[609,953,858],{"class":813},[609,955,956],{"class":861},"sRHdihwgkaib1P1gxX8HFszlD+7\u002FgTfNvuAybgLPNis=",[609,958,865],{"class":813},[609,960,905],{"class":817},[609,962,821],{"class":813},[609,964,966,969,971],{"class":611,"line":965},10,[609,967,968],{"class":813},"    \u003C\u002F",[609,970,876],{"class":817},[609,972,821],{"class":813},[609,974,976,979,981],{"class":611,"line":975},11,[609,977,978],{"class":813},"  \u003C\u002F",[609,980,829],{"class":817},[609,982,821],{"class":813},[609,984,986,988,990],{"class":611,"line":985},12,[609,987,865],{"class":813},[609,989,818],{"class":817},[609,991,821],{"class":813},[175,993,994],{},"The iOS equivalent, which is declarative and enforced by the OS:",[600,996,998],{"className":798,"code":997,"language":800,"meta":605,"style":605},"\u003C!-- Info.plist -->\n\u003Ckey>NSAppTransportSecurity\u003C\u002Fkey>\n\u003Cdict>\n  \u003Ckey>NSPinnedDomains\u003C\u002Fkey>\n  \u003Cdict>\n    \u003Ckey>api.example.com\u003C\u002Fkey>\n    \u003Cdict>\n      \u003Ckey>NSIncludesSubdomains\u003C\u002Fkey>\u003Ctrue\u002F>\n      \u003Ckey>NSPinnedLeafIdentities\u003C\u002Fkey>\n      \u003Carray>\n        \u003Cdict>\u003Ckey>SPKI-SHA256-BASE64\u003C\u002Fkey>\u003Cstring>YLh1dUR9y6Kja30RrAn7JKnbQG\u002FuEtLMkBgFF2Fuihg=\u003C\u002Fstring>\u003C\u002Fdict>\n        \u003Cdict>\u003Ckey>SPKI-SHA256-BASE64\u003C\u002Fkey>\u003Cstring>sRHdihwgkaib1P1gxX8HFszlD+7\u002FgTfNvuAybgLPNis=\u003C\u002Fstring>\u003C\u002Fdict>\n      \u003C\u002Farray>\n    \u003C\u002Fdict>\n  \u003C\u002Fdict>\n\u003C\u002Fdict>\n",[243,999,1000,1005,1023,1032,1049,1057,1073,1081,1104,1121,1130,1170,1206,1216,1225,1234],{"__ignoreMap":605},[609,1001,1002],{"class":611,"line":612},[609,1003,1004],{"class":807},"\u003C!-- Info.plist -->\n",[609,1006,1007,1009,1012,1014,1017,1019,1021],{"class":611,"line":652},[609,1008,814],{"class":813},[609,1010,1011],{"class":817},"key",[609,1013,858],{"class":813},[609,1015,1016],{"class":861},"NSAppTransportSecurity",[609,1018,865],{"class":813},[609,1020,1011],{"class":817},[609,1022,821],{"class":813},[609,1024,1025,1027,1030],{"class":611,"line":672},[609,1026,814],{"class":813},[609,1028,1029],{"class":817},"dict",[609,1031,821],{"class":813},[609,1033,1034,1036,1038,1040,1043,1045,1047],{"class":611,"line":693},[609,1035,826],{"class":813},[609,1037,1011],{"class":817},[609,1039,858],{"class":813},[609,1041,1042],{"class":861},"NSPinnedDomains",[609,1044,865],{"class":813},[609,1046,1011],{"class":817},[609,1048,821],{"class":813},[609,1050,1051,1053,1055],{"class":611,"line":711},[609,1052,826],{"class":813},[609,1054,1029],{"class":817},[609,1056,821],{"class":813},[609,1058,1059,1061,1063,1065,1067,1069,1071],{"class":611,"line":893},[609,1060,836],{"class":813},[609,1062,1011],{"class":817},[609,1064,858],{"class":813},[609,1066,862],{"class":861},[609,1068,865],{"class":813},[609,1070,1011],{"class":817},[609,1072,821],{"class":813},[609,1074,1075,1077,1079],{"class":611,"line":899},[609,1076,836],{"class":813},[609,1078,1029],{"class":817},[609,1080,821],{"class":813},[609,1082,1083,1085,1087,1089,1092,1094,1096,1099,1101],{"class":611,"line":931},[609,1084,902],{"class":813},[609,1086,1011],{"class":817},[609,1088,858],{"class":813},[609,1090,1091],{"class":861},"NSIncludesSubdomains",[609,1093,865],{"class":813},[609,1095,1011],{"class":817},[609,1097,1098],{"class":813},">\u003C",[609,1100,853],{"class":817},[609,1102,1103],{"class":813},"\u002F>\n",[609,1105,1106,1108,1110,1112,1115,1117,1119],{"class":611,"line":937},[609,1107,902],{"class":813},[609,1109,1011],{"class":817},[609,1111,858],{"class":813},[609,1113,1114],{"class":861},"NSPinnedLeafIdentities",[609,1116,865],{"class":813},[609,1118,1011],{"class":817},[609,1120,821],{"class":813},[609,1122,1123,1125,1128],{"class":611,"line":965},[609,1124,902],{"class":813},[609,1126,1127],{"class":817},"array",[609,1129,821],{"class":813},[609,1131,1132,1135,1137,1139,1141,1143,1146,1148,1150,1152,1155,1157,1159,1161,1163,1166,1168],{"class":611,"line":975},[609,1133,1134],{"class":813},"        \u003C",[609,1136,1029],{"class":817},[609,1138,1098],{"class":813},[609,1140,1011],{"class":817},[609,1142,858],{"class":813},[609,1144,1145],{"class":861},"SPKI-SHA256-BASE64",[609,1147,865],{"class":813},[609,1149,1011],{"class":817},[609,1151,1098],{"class":813},[609,1153,1154],{"class":817},"string",[609,1156,858],{"class":813},[609,1158,922],{"class":861},[609,1160,865],{"class":813},[609,1162,1154],{"class":817},[609,1164,1165],{"class":813},">\u003C\u002F",[609,1167,1029],{"class":817},[609,1169,821],{"class":813},[609,1171,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204],{"class":611,"line":985},[609,1173,1134],{"class":813},[609,1175,1029],{"class":817},[609,1177,1098],{"class":813},[609,1179,1011],{"class":817},[609,1181,858],{"class":813},[609,1183,1145],{"class":861},[609,1185,865],{"class":813},[609,1187,1011],{"class":817},[609,1189,1098],{"class":813},[609,1191,1154],{"class":817},[609,1193,858],{"class":813},[609,1195,956],{"class":861},[609,1197,865],{"class":813},[609,1199,1154],{"class":817},[609,1201,1165],{"class":813},[609,1203,1029],{"class":817},[609,1205,821],{"class":813},[609,1207,1209,1212,1214],{"class":611,"line":1208},13,[609,1210,1211],{"class":813},"      \u003C\u002F",[609,1213,1127],{"class":817},[609,1215,821],{"class":813},[609,1217,1219,1221,1223],{"class":611,"line":1218},14,[609,1220,968],{"class":813},[609,1222,1029],{"class":817},[609,1224,821],{"class":813},[609,1226,1228,1230,1232],{"class":611,"line":1227},15,[609,1229,978],{"class":813},[609,1231,1029],{"class":817},[609,1233,821],{"class":813},[609,1235,1237,1239,1241],{"class":611,"line":1236},16,[609,1238,865],{"class":813},[609,1240,1029],{"class":817},[609,1242,821],{"class":813},[175,1244,1245,1246,1249],{},"That sample pins leaf keys. Apple's own documentation uses ",[243,1247,1248],{},"NSPinnedCAIdentities"," instead — the same structure one level up the chain, which is what rule 3 is about.",[299,1251,1253],{"id":1252},"rule-3-pick-your-level-in-the-chain-and-know-what-it-costs","Rule 3. Pick your level in the chain — and know what it costs",[175,1255,1256,1257,1260,1261,1264],{},"There is no consensus here, so both positions follow. ",[179,1258,1259],{},"Apple recommends pinning the CA rather than the server",", so that you can rotate server certificates without shipping an app update. ",[179,1262,1263],{},"OWASP recommends the opposite",": pin the leaf, but always with a backup.",[338,1266,1267,1283],{},[341,1268,1269],{},[344,1270,1271,1274,1277,1280],{},[347,1272,1273],{},"Level",[347,1275,1276],{},"Rotation cost",[347,1278,1279],{},"What you are trusting",[347,1281,1282],{},"Verdict in 2026",[361,1284,1285,1302,1319,1333],{},[344,1286,1287,1290,1293,1296],{},[366,1288,1289],{},"Leaf",[366,1291,1292],{},"Free with a same-key renewal; a release otherwise",[366,1294,1295],{},"Exactly one key",[366,1297,1298,1299,1301],{},"Workable ",[179,1300,780],{}," with backup pins",[344,1303,1304,1307,1310,1313],{},[366,1305,1306],{},"Intermediate",[366,1308,1309],{},"Unpredictable",[366,1311,1312],{},"Everything that intermediate issues",[366,1314,1315,1318],{},[179,1316,1317],{},"Off the table"," on public CAs",[344,1320,1321,1324,1327,1330],{},[366,1322,1323],{},"Public root",[366,1325,1326],{},"Rare",[366,1328,1329],{},"Everything that CA will ever issue",[366,1331,1332],{},"So broad it is nearly pointless",[344,1334,1335,1340,1343,1346],{},[366,1336,1337],{},[179,1338,1339],{},"Your own private root",[366,1341,1342],{},"You decide",[366,1344,1345],{},"Your own PKI",[366,1347,1348],{},"The one clearly good case",[175,1350,1351],{},"Pinning an intermediate for publicly trusted certificates has been basically off the table since 2024: Let's Encrypt has six intermediates now and you cannot predict which one your certificate gets. If you are on a public CA, that leaves the leaf with backup pins, or the root.",[175,1353,1354,1355,1358],{},"The configuration where pinning is still genuinely a good idea is ",[179,1356,1357],{},"your own private CA",", one you control end to end. Pinning to your own root is sensible, predictable, and it does not break because somebody else changed their mind. Which, incidentally, is exactly what that Chromium engineer said in the quote at the top.",[299,1360,1362],{"id":1361},"rule-4-never-ship-straight-to-hard-fail","Rule 4. Never ship straight to hard-fail",[175,1364,1365,1366,1369,1370,1374],{},"The order is: ",[179,1367,1368],{},"report-only mode"," first — the pin gets checked, the connection does not get blocked, and misses go to telemetry. Watch for a week. Then hard-fail for one percent of users. Then a ",[276,1371,1373],{"slug":1372},"staged-rollout","staged rollout"," to everybody.",[175,1376,1377,1378,1381,1382,1385,1386,1389],{},"And here is the annoying part: ",[179,1379,1380],{},"there is no built-in failure reporting on either platform."," Not in Android's Network Security Config, not in iOS App Transport Security. The one mechanism that ever did this was HPKP's ",[243,1383,1384],{},"report-uri",", and it is dead. So you build the telemetry yourself: catch the exception, build an event — domain, key you got, keys you expected, app version — and send it ",[179,1387,1388],{},"over a channel that is not pinned",", or the report about your outage will not make it out.",[175,1391,1392,1393,1396],{},"One more thing: learn to tell an outage from an attack. ",[179,1394,1395],{},"Everyone failing at once means you broke your own rotation."," Scattered failures mean a corporate proxy, an antivirus, or an actual interception attempt.",[299,1398,1400],{"id":1399},"rule-5-build-the-kill-switch-before-you-need-it","Rule 5. Build the kill switch before you need it",[175,1402,1403,1404,1407],{},"On Android there is one built in — the ",[243,1405,1406],{},"expiration"," attribute on the pin set. After that date, pinning simply stops: pins are not checked, traffic flows as if nothing had happened. A kill switch on a timer.",[175,1409,1410,1411,1414,1415,1418,1419,1422],{},"But it has a price, and Google says so itself: ",[179,1412,1413],{},"\"setting an expiration time on pins may enable attackers to bypass your pinned certificates.\""," So it is both your insurance against bricking the app ",[179,1416,1417],{},"and"," a way to bypass your pinning by waiting. On a different page of the same documentation, Google recommends ",[179,1420,1421],{},"\"a sufficiently short expiration period.\""," Both statements are official and both are current.",[175,1424,1425,1426,1433],{},"And here is our favourite detail in this whole story. ",[179,1427,1428,1429,1432],{},"The canonical example in Google's documentation still carries ",[243,1430,1431],{},"expiration=\"2018-01-01\"","."," If you copied it — and everybody copies it — your pinning has been fully disabled for eight years. They updated that page in June 2026. The date stayed.",[175,1435,1436],{},"On iOS there is no equivalent mechanism at all. Turning pinning off means shipping a release.",[175,1438,1439,1440,1444,1445,1448],{},"If you are doing a kill switch over remote config or ",[276,1441,1443],{"slug":1442},"feature-flags","feature flags",", watch out for the chicken-and-egg problem: the delivery channel cannot be protected by the same pins, or you can never recover. The right answer is to ",[179,1446,1447],{},"sign the pin set with a key whose public half is baked into the app."," Then it does not matter what channel it arrived on. Add a monotonic version counter so nobody can replay an older, validly signed set.",[199,1450,1452],{"id":1451},"flutter-where-certificate-pinning-falls-apart","Flutter: where certificate pinning falls apart",[175,1454,1455],{},"Everything above applies to everyone. This is the part we wrote the article for — we build Flutter apps, and this is the part we have had our hands in.",[175,1457,1458],{},[179,1459,1460],{},"Here is the headline, and it goes against most people's intuition: Dart does not use the platform networking stack.",[175,1462,1463,1466,1467,1470,1471,1474,1475,1478,1479,1482],{},[243,1464,1465],{},"HttpClient"," from ",[243,1468,1469],{},"dart:io"," does not go through ",[243,1472,1473],{},"OkHttp"," or ",[243,1476,1477],{},"HttpURLConnection"," on Android, or ",[243,1480,1481],{},"NSURLSession"," on iOS. It opens a socket and runs the TLS handshake itself, through a copy of BoringSSL compiled into the Flutter engine.",[175,1484,1485,1486,1493],{},"The practical consequence: ",[179,1487,1488,1489,246,1491,250],{},"your ",[243,1490,245],{},[243,1492,249],{}," Silently. No error, no warning — the request just goes through.",[175,1495,1496,1497,1504],{},"This is not our discovery. It is ",[1498,1499,1503],"a",{"href":1500,"rel":1501},"https:\u002F\u002Fgithub.com\u002Fflutter\u002Fflutter\u002Fissues\u002F96722",[1502],"nofollow","Flutter issue #96722",", filed in January 2022 and still open. A member of the Flutter team reproduced it and wrote the finding down: on iOS the request is cancelled properly, on Android it goes through, and on a native Android control app carrying the same config it fails as it should.",[175,1506,1507,1508,1511,1512,1515],{},"On iOS the behaviour is different: it is reported to work, because Dart on Apple platforms hands the trust decision to the system's ",[243,1509,1510],{},"SecTrust",". That is not documented anywhere, and honestly it is the worst outcome. ",[179,1513,1514],{},"Pinning that works on one platform and silently no-ops on the other is more dangerous than pinning that works nowhere"," — because you tested it. On an iPhone.",[175,1517,1518],{},"Then three traps. We have hit all three in production.",[175,1520,1521,1524,1525,1528,1529,1532,1533,1532,1536,1539],{},[179,1522,1523],{},"Dart gives you no access to the SPKI."," The ",[243,1526,1527],{},"X509Certificate"," class exposes ",[243,1530,1531],{},"der",", ",[243,1534,1535],{},"pem",[243,1537,1538],{},"sha1",", subject, issuer and validity dates. The public key is not among them. So virtually every tutorial pins a SHA-256 of the entire certificate — which, per rule 1, breaks on every renewal. Doing it properly means parsing ASN.1 by hand.",[175,1541,1542,1545],{},[179,1543,1544],{},"dio's own README shows two ways to get this wrong."," Here is the first, condensed from it:",[600,1547,1550],{"className":1548,"code":1549,"language":18,"meta":605,"style":605},"language-dart shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F dio's README example. Three problems in nine lines.\ndio.httpClientAdapter = IOHttpClientAdapter(\n  createHttpClient: () {\n    final client = HttpClient(context: SecurityContext(withTrustedRoots: false));\n    client.badCertificateCallback = (cert, host, port) => true;\n    return client;\n  },\n  validateCertificate: (cert, host, port) =>\n      cert != null && fingerprint == sha256.convert(cert.der).toString(),\n);\n",[243,1551,1552,1557,1576,1587,1624,1658,1669,1677,1697,1744],{"__ignoreMap":605},[609,1553,1554],{"class":611,"line":612},[609,1555,1556],{"class":807},"\u002F\u002F dio's README example. Three problems in nine lines.\n",[609,1558,1559,1562,1564,1567,1569,1573],{"class":611,"line":652},[609,1560,1561],{"class":861},"dio",[609,1563,1432],{"class":813},[609,1565,1566],{"class":861},"httpClientAdapter ",[609,1568,846],{"class":636},[609,1570,1572],{"class":1571},"sZMiF"," IOHttpClientAdapter",[609,1574,1575],{"class":861},"(\n",[609,1577,1578,1581,1584],{"class":611,"line":672},[609,1579,1580],{"class":861},"  createHttpClient",[609,1582,1583],{"class":636},":",[609,1585,1586],{"class":861}," () {\n",[609,1588,1589,1593,1596,1598,1601,1604,1606,1609,1612,1614,1618,1621],{"class":611,"line":693},[609,1590,1592],{"class":1591},"sbsja","    final",[609,1594,1595],{"class":861}," client ",[609,1597,846],{"class":636},[609,1599,1600],{"class":1571}," HttpClient",[609,1602,1603],{"class":861},"(context",[609,1605,1583],{"class":636},[609,1607,1608],{"class":1571}," SecurityContext",[609,1610,1611],{"class":861},"(withTrustedRoots",[609,1613,1583],{"class":636},[609,1615,1617],{"class":1616},"s39Yj"," false",[609,1619,1620],{"class":861},"))",[609,1622,1623],{"class":813},";\n",[609,1625,1626,1629,1631,1634,1636,1639,1642,1645,1647,1650,1653,1656],{"class":611,"line":711},[609,1627,1628],{"class":861},"    client",[609,1630,1432],{"class":813},[609,1632,1633],{"class":861},"badCertificateCallback ",[609,1635,846],{"class":636},[609,1637,1638],{"class":861}," (cert",[609,1640,1641],{"class":813},",",[609,1643,1644],{"class":861}," host",[609,1646,1641],{"class":813},[609,1648,1649],{"class":861}," port) ",[609,1651,1652],{"class":636},"=>",[609,1654,1655],{"class":1616}," true",[609,1657,1623],{"class":813},[609,1659,1660,1664,1667],{"class":611,"line":893},[609,1661,1663],{"class":1662},"sVHd0","    return",[609,1665,1666],{"class":861}," client",[609,1668,1623],{"class":813},[609,1670,1671,1674],{"class":611,"line":899},[609,1672,1673],{"class":861},"  }",[609,1675,1676],{"class":813},",\n",[609,1678,1679,1682,1684,1686,1688,1690,1692,1694],{"class":611,"line":931},[609,1680,1681],{"class":861},"  validateCertificate",[609,1683,1583],{"class":636},[609,1685,1638],{"class":861},[609,1687,1641],{"class":813},[609,1689,1644],{"class":861},[609,1691,1641],{"class":813},[609,1693,1649],{"class":861},[609,1695,1696],{"class":636},"=>\n",[609,1698,1699,1702,1705,1708,1711,1714,1717,1720,1722,1726,1729,1731,1734,1736,1739,1742],{"class":611,"line":937},[609,1700,1701],{"class":861},"      cert ",[609,1703,1704],{"class":636},"!=",[609,1706,1707],{"class":1616}," null",[609,1709,1710],{"class":636}," &&",[609,1712,1713],{"class":861}," fingerprint ",[609,1715,1716],{"class":636},"==",[609,1718,1719],{"class":861}," sha256",[609,1721,1432],{"class":813},[609,1723,1725],{"class":1724},"sGLFI","convert",[609,1727,1728],{"class":861},"(cert",[609,1730,1432],{"class":813},[609,1732,1733],{"class":861},"der)",[609,1735,1432],{"class":813},[609,1737,1738],{"class":1724},"toString",[609,1740,1741],{"class":861},"()",[609,1743,1676],{"class":813},[609,1745,1746,1749],{"class":611,"line":965},[609,1747,1748],{"class":861},")",[609,1750,1623],{"class":813},[175,1752,1753,1756,1757,1760,1761,1764,1765,1768],{},[243,1754,1755],{},"withTrustedRoots: false"," plus a blanket ",[243,1758,1759],{},"badCertificateCallback"," disables chain building, expiry and hostname verification. The fingerprint it compares is a hash of ",[243,1762,1763],{},"cert.der"," — the whole certificate, which rule 1 says breaks on every renewal. And ",[243,1766,1767],{},"validateCertificate"," runs later than you think; see below.",[175,1770,1771,1772,1774,1775,1778,1779,1782],{},"The README's second variant is worse: it pins inside ",[243,1773,1759],{}," itself, comparing ",[243,1776,1777],{},"cert.pem"," to a stored PEM. By definition that callback fires ",[179,1780,1781],{},"only when validation has already failed."," If an attacker presents a certificate validly signed by any trusted CA, it never runs at all and nothing is ever compared. As a pinning mechanism it is broken by design.",[175,1784,1785,1788,1789,1791,1792,1795,1796,1799],{},[179,1786,1787],{},"The right hook runs too late."," dio does have one — ",[243,1790,1767],{},", which fires on successful validation. But it runs ",[179,1793,1794],{},"after the request has gone out and the response has already come back."," It is not just your request body and ",[243,1797,1798],{},"Authorization"," header on the wire: you have already accepted the response status and headers. The only thing that check saves is the response body. The request has already leaked.",[430,1801,1804],{":leading-icon":1802,":title":1803},"lucide:git-branch","And then the packages",[175,1805,1806,1807,1811,1812,1815,1816,1819],{},"The most popular pinning package on pub.dev — around a hundred and sixty likes and close to fifty thousand downloads a month when we checked in August 2026 — uses a ",[276,1808,1810],{"slug":1809},"platform-channels","platform channel"," bridge to make a ",[179,1813,1814],{},"separate native request"," to your host, checks the fingerprint on that, and if it matches, sends the real request over a ",[179,1817,1818],{},"different connection"," with no pinning on it at all. One connection gets validated; another carries your data. Also, every request is now two requests. We are not naming it, because the point is not one maintainer: read the source of whichever pinning package you are using, and find out which connection it actually checks.",[175,1821,1822,1825,1826,1829,1830,1832,1833,246,1835,1838],{},[179,1823,1824],{},"What to actually do."," On Apple platforms there is a clean answer: move your HTTP layer onto ",[243,1827,1828],{},"cupertino_http",". That is a real ",[243,1831,1481],{},", which means pinning is declarative — ",[243,1834,1042],{},[243,1836,1837],{},"Info.plist",", exactly as in rule 2. OS-enforced, SPKI-based, zero third-party code.",[175,1840,1841,1842,1845,1846,1849,1850,1853],{},"On Android there is no equivalent. Cronet has its own pinning API, but the ",[243,1843,1844],{},"cronet_http"," package ",[179,1847,1848],{},"does not export it",". The JNI binding is right there in the package. What the package does export is ",[243,1851,1852],{},"enablePublicKeyPinningBypassForLocalTrustAnchors"," — the switch that bypasses pinning, not the pinning.",[175,1855,1856],{},"There is also one question nobody on the internet answers clearly, and we would rather flag it than pretend otherwise: whether Android's Network Security Config applies to traffic going through Cronet. Sources contradict each other and there is no official documentation either way. Test it yourself before you rely on either answer.",[175,1858,1859,1860,1863,1864,1868,1869,1876,1877,1880],{},"Two closing warnings for Flutter specifically. ",[179,1861,1862],{},"Third-party SDKs"," — analytics, crash reporting, payments — make network calls through their own native clients, and your pinning does not touch them. The same split is what makes ",[1498,1865,1867],{"href":1866},"\u002Fblog\u002Fdeferred-deep-linking-app-clips-install-referrer","deferred deep linking on Flutter"," a per-platform problem rather than one implementation. And ",[179,1870,1871,1872,1875],{},"if you migrate to ",[243,1873,1874],{},"native_dio_adapter"," to get the native networking stack, the dio-based pinning you copied from a blog post silently switches off",", because ",[243,1878,1879],{},"SecurityContext"," and both callbacks stop being invoked.",[199,1882,1884],{"id":1883},"how-to-check-your-pinning-in-half-an-hour","How to check your pinning in half an hour",[175,1886,1887],{},"Do this properly, because the naive version lies to you.",[175,1889,1890],{},"The naive version is: stand up a proxy, install its certificate on the phone, see if the app breaks. It will break. And that means nothing — as covered above, since Android 7 apps do not trust user-installed certificates anyway. You will conclude that you are protected by pinning when the operating system was protecting you all along.",[1892,1893],"how-to-schema",{":description":1894,":name":1895,":steps":1896,":total-time":1897},"A 30-minute test that distinguishes real certificate pinning from the platform trust defaults, run separately on Android and iOS.","How to verify certificate pinning in a mobile app","[{\"name\": \"Build a release build\", \"text\": \"Test the build your users get. Debug builds often carry a relaxed network security config, a debug-overrides block, or a proxy-friendly HTTP client that production does not have.\"}, {\"name\": \"Install the proxy certificate into the SYSTEM store\", \"text\": \"Not the user store. Since targetSdk 24, Android apps do not trust user-installed certificates, so a break in that setup proves nothing about your pinning. Use an emulator with a writable system partition, or Magisk on a rooted device. On iOS, install the profile and then enable full trust for the root certificate in Settings.\"}, {\"name\": \"Make sure the traffic actually reaches the proxy\", \"text\": \"On Flutter this is a separate step: Dart ignores system proxy settings, so a device-level proxy configuration will not capture dart:io traffic. Route it with a VPN-based capture or iptables and confirm you see requests arriving before you conclude anything.\"}, {\"name\": \"Read the captured traffic\", \"text\": \"If your API traffic is readable, you do not have working pinning, whatever the configuration file says. Exercise a real authenticated flow, not just the splash screen, so you cover every client the app uses.\"}, {\"name\": \"Check for the two-stack pattern\", \"text\": \"Watch specifically for third-party SDK traffic being visible while your own is not. That is not your pinning working; that is two different network stacks, one of which your pinning does not cover.\"}, {\"name\": \"Repeat on the other platform\", \"text\": \"Run the whole test on Android and on iOS separately. Comparing the two is the entire point: the common Flutter outcome is pinning that holds on iOS and silently no-ops on Android.\"}]","PT30M",[1899,1900],"checklist",{":groups":1901,":subtitle":1902,":title":1903},"[{\"title\": \"Setup\", \"items\": [\"Release build, not debug — a debug-overrides block invalidates the whole test\", \"Proxy certificate installed in the SYSTEM trust store, not the user store\", \"Flutter: traffic confirmed to reach the proxy via VPN capture or iptables, because Dart ignores system proxy settings\", \"A real authenticated flow exercised, not just the first screen\"]}, {\"title\": \"What to look for\", \"items\": [\"Your API traffic is unreadable — if you can read it, you have no working pinning\", \"Third-party SDK traffic and your own traffic behave the same way, or you know why they do not\", \"The failure you see is a pinning failure, not a trust-store failure\", \"The same result on Android and on iOS\"]}, {\"title\": \"Then check the configuration itself\", \"items\": [\"Pins are SPKI hashes, not certificate fingerprints\", \"At least one backup pin, for an offline key you control\", \"Android: the pin-set expiration date is in the future and deliberately chosen\", \"A kill switch exists that does not require a store release\", \"Failure telemetry is sent over a channel that is not itself pinned\"]}]","Run it once per platform. Anything unchecked is a finding.","The 30-minute pinning verification",[199,1905,1907],{"id":1906},"the-short-version","The short version",[175,1909,1910],{},"Pinning is a narrow control against a threat that has become far less likely, with a blast radius covering your whole user base. It is genuinely necessary for finance, health and government. For everyone else, Google, Apple, Cloudflare and half of OWASP say don't, and they have reasons — the same reasons that killed HPKP and that are now cutting certificate lifetimes to 47 days.",[175,1912,1913],{},"If you do it: the key, not the certificate. Backup pins with an offline key. Observation before hard-fail. Your own kill switch. And a clear view of what you are paying for your position in the chain.",[175,1915,1916],{},"And if you are on Flutter, first find out whether it works at all. The three most likely answers: pinning disabled by a 2018 date copied out of Google's documentation; pinning declared in a configuration file Dart never reads; or pinning that validates a connection your data does not travel on.",[175,1918,1919],{},"The blog-post version of this advice says \"enable pinning, protect your users.\" The engineering reality is finding out what you are actually pinned to, verifying it fires on both platforms, and putting a spare key in a safe. Nobody makes viral videos about that.",[199,1921,1923],{"id":1922},"sources","Sources",[175,1925,1926],{},"Every claim above is checkable, so here is where each one comes from.",[204,1928,1929,1937,1955,1963,1975,1983,1991,1999,2007,2015,2022,2037,2048],{},[207,1930,1931,1932],{},"Google, \"Security with HTTPS and SSL\" — pinning \"is not recommended for Android apps\"; backup pins and \"at least one key that's fully in your control\": ",[1498,1933,1936],{"href":1934,"rel":1935},"https:\u002F\u002Fdeveloper.android.com\u002Fprivacy-and-security\u002Fsecurity-ssl",[1502],"developer.android.com\u002Fprivacy-and-security\u002Fsecurity-ssl",[207,1938,1939,1940,1942,1943,1945,1946,1949,1950],{},"Google, \"Network security configuration\" — the ",[243,1941,1406],{}," attribute, the bypass warning, \"only ",[243,1944,915],{}," is supported\", and the ",[243,1947,1948],{},"2018-01-01"," sample: ",[1498,1951,1954],{"href":1952,"rel":1953},"https:\u002F\u002Fdeveloper.android.com\u002Fprivacy-and-security\u002Fsecurity-config",[1502],"developer.android.com\u002Fprivacy-and-security\u002Fsecurity-config",[207,1956,1957,1958],{},"Apple, \"Identity Pinning: How to configure server certificates for your app\" — \"in most cases, pinning is not necessary and should be avoided\", and the CA-over-leaf recommendation: ",[1498,1959,1962],{"href":1960,"rel":1961},"https:\u002F\u002Fdeveloper.apple.com\u002Fnews\u002F?id=g9ejcf8y",[1502],"developer.apple.com\u002Fnews",[207,1964,1965,1966,1969,1970],{},"Ryan Sleevi on ",[243,1967,1968],{},"net-dev@chromium.org",", March 2021 — the private-CA quote in full: ",[1498,1971,1974],{"href":1972,"rel":1973},"https:\u002F\u002Fgroups.google.com\u002Fa\u002Fchromium.org\u002Fg\u002Fnet-dev\u002Fc\u002Fq_oSyjtMhts",[1502],"groups.google.com",[207,1976,1977,1978],{},"OWASP Pinning Cheat Sheet — \"The answer to this is probably never\", and the do-not-pin list: ",[1498,1979,1982],{"href":1980,"rel":1981},"https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FPinning_Cheat_Sheet.html",[1502],"cheatsheetseries.owasp.org",[207,1984,1985,1986],{},"OWASP MASTG, network communication — \"If an app does not implement pinning, this shouldn't be reported as a vulnerability. However, if the app must verify against MAS-L2 it must be implemented\": ",[1498,1987,1990],{"href":1988,"rel":1989},"https:\u002F\u002Fgithub.com\u002FOWASP\u002Fmastg\u002Fblob\u002Fmaster\u002FDocument\u002F0x04f-Testing-Network-Communication.md",[1502],"github.com\u002FOWASP\u002Fmastg",[207,1992,1993,1994],{},"CA\u002FBrowser Forum ballot SC-081v3 — the 398 → 200 → 100 → 47 schedule, filed by Apple, passed 11 April 2025: ",[1498,1995,1998],{"href":1996,"rel":1997},"https:\u002F\u002Fcabforum.org\u002F2025\u002F04\u002F11\u002Fballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods\u002F",[1502],"cabforum.org",[207,2000,2001,2002],{},"Let's Encrypt, \"Generation Y\" — six intermediates, chosen at random \"to discourage intermediate key pinning\": ",[1498,2003,2006],{"href":2004,"rel":2005},"https:\u002F\u002Fletsencrypt.org\u002F2025\u002F11\u002F24\u002Fgen-y-hierarchy\u002F",[1502],"letsencrypt.org",[207,2008,2009,2010],{},"Cloudflare — the twelve unauthorized 1.1.1.1 certificates, and Fina's absence from the mobile root stores: ",[1498,2011,2014],{"href":2012,"rel":2013},"https:\u002F\u002Fblog.cloudflare.com\u002Funauthorized-issuance-of-certificates-for-1-1-1-1",[1502],"blog.cloudflare.com",[207,2016,2017,2018],{},"Flutter issue #96722 — open since January 2022: ",[1498,2019,2021],{"href":1500,"rel":2020},[1502],"github.com\u002Fflutter\u002Fflutter",[207,2023,2024,2025,2028,2029,2031,2032],{},"dio, ",[243,2026,2027],{},"IOHttpClientAdapter"," — where ",[243,2030,1767],{}," actually runs, in source: ",[1498,2033,2036],{"href":2034,"rel":2035},"https:\u002F\u002Fgithub.com\u002Fcfug\u002Fdio\u002Fblob\u002Fmain\u002Fdio\u002Flib\u002Fsrc\u002Fadapters\u002Fio_adapter.dart",[1502],"github.com\u002Fcfug\u002Fdio",[207,2038,2039,2040,2042,2043],{},"Dart ",[243,2041,1527],{}," — the full list of what it exposes, with no public key on it: ",[1498,2044,2047],{"href":2045,"rel":2046},"https:\u002F\u002Fapi.dart.dev\u002Fstable\u002Fdart-io\u002FX509Certificate-class.html",[1502],"api.dart.dev",[207,2049,2050,1532,2052,2055,2056],{},[243,2051,1844],{},[243,2053,2054],{},"CronetEngine.build"," — the parameter list, bypass included and pinning absent: ",[1498,2057,2060],{"href":2058,"rel":2059},"https:\u002F\u002Fpub.dev\u002Fdocumentation\u002Fcronet_http\u002Flatest\u002Fcronet_http\u002FCronetEngine\u002Fbuild.html",[1502],"pub.dev",[2062,2063],"questions",{":items":2064},"[{\"title\": \"Should I use certificate pinning in my mobile app?\", \"text\": \"Probably not, unless you are in finance, health or government services, or your threat model explicitly assumes a hostile network and an untrusted device. Google calls pinning not recommended for Android apps, Apple says it is unnecessary and should be avoided in most cases, and the OWASP Pinning Cheat Sheet answers should I pin with probably never. Do not pin if you cannot update the pin set without an app release, cannot know the key pair before production, or do not control both the server and the app.\"}, {\"title\": \"Is missing SSL pinning a real vulnerability?\", \"text\": \"Not on its own. The OWASP Mobile Application Security Testing Guide states that an app not implementing pinning should not be reported as a vulnerability, and MASVS requires it only for apps that must verify against MAS-L2, the defence-in-depth profile for software handling sensitive data. A pentest report flagging missing SSL pinning as a finding is usually the output of an automated checklist rather than a judgement about your threat model.\"}, {\"title\": \"What should I pin — the certificate, the public key, or the CA?\", \"text\": \"The public key, as an SPKI SHA-256 hash. The certificate changes on every renewal; the key does not, so a same-key renewal never touches your pin. Both Android and iOS natively support only SPKI pinning. For the level in the chain: pinning an intermediate is off the table for public CAs since the largest public CA began choosing intermediates at random in 2024, a public root is so broad it is nearly pointless, and the leaf works only with backup pins. Your own private root is the one clearly good case.\"}, {\"title\": \"Does certificate pinning work in Flutter?\", \"text\": \"Not the way most people configure it. Dart does not use the platform networking stack: dart:io runs TLS through BoringSSL compiled into the Flutter engine, so a pin-set in network_security_config.xml is never consulted for Dart traffic on Android. That is Flutter issue 96722, filed in January 2022 and still open. On iOS it is reported to work because Dart hands the trust decision to SecTrust, which means the common outcome is pinning that holds on one platform and silently does nothing on the other.\"}, {\"title\": \"Why is badCertificateCallback the wrong place to pin in Dart?\", \"text\": \"Because it fires only after validation has already failed. If an attacker presents a certificate validly signed by any trusted CA, the callback never runs and no fingerprint is ever compared. The commonly copied example that pairs it with SecurityContext withTrustedRoots false also disables chain building, expiry and hostname verification. The validateCertificate hook in dio is closer to correct, but it runs after the request has been sent and the response received, so it protects only the response body.\"}, {\"title\": \"How do the new 47-day certificate lifetimes affect pinning?\", \"text\": \"They make leaf pinning that requires an app release untenable. The CA\u002FBrowser Forum ballot passed in April 2025 steps maximum TLS certificate lifetimes down from 398 days to 200 in March 2026, 100 in March 2027 and 47 in March 2029. Forty-seven days is about eight rotations a year. If your pin set can only change with a store release, that is eight forced releases a year, each subject to review and to users who do not update. Pinning the key rather than the certificate removes most of that cost, because a same-key renewal does not change the pin.\"}, {\"title\": \"How do I test whether my pinning actually works?\", \"text\": \"Install your proxy certificate into the system trust store, not the user store, using an emulator with a writable system partition or Magisk, then read the captured traffic from a release build. The naive test — a user-installed certificate — proves nothing, because Android apps have not trusted user-installed certificates since targetSdk 24. On Flutter, separately confirm the traffic reaches the proxy at all, since Dart ignores system proxy settings. Then run the same test on the other platform and compare.\"}]",[175,2066,2067,2068,2072,2073,2077,2078,1432],{},"If you want the background on what a certificate authority actually vouches for, we wrote that up in ",[1498,2069,2071],{"href":2070},"\u002Fblog\u002Fhttps-free-ssl-certificate","HTTPS made simple",", and the cryptography underneath it in ",[1498,2074,2076],{"href":2075},"\u002Fblog\u002Fencryption-explained","how encryption works",". This article is the companion to our video episode on the same subject; the previous one was ",[1498,2079,2081],{"href":2080},"\u002Fblog\u002Feveryone-got-the-timeline-wrong","everyone got the timeline wrong",[175,2083,2084],{},"And if you found something surprising when you ran the test — particularly the one-platform failure — we would genuinely like to hear it. Our bet is that half of all Flutter apps have pinning that fails on at least one platform.",[2086,2087,2088],"post-credits",{},[175,2089,2090,2091,2095,2096,2100],{},"Ilya Nixan is Founder & Lead Developer at ",[1498,2092,2094],{"href":2093},"\u002F","Nerdy Production",", a Flutter-first agency that builds and maintains apps across fintech, healthcare, and retail. We also do ",[1498,2097,2099],{"href":2098},"\u002Fservices\u002Fflutter-app-development","Flutter app development"," for teams who would rather not find this out in production.",[2102,2103,2104],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sQzsp, html code.shiki .sQzsp{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s9AJx, html code.shiki .s9AJx{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":605,"searchDepth":652,"depth":652,"links":2106},[2107,2108,2113,2116,2123,2124,2125,2126],{"id":201,"depth":652,"text":202},{"id":260,"depth":652,"text":261,"children":2109},[2110,2111,2112],{"id":301,"depth":672,"text":302},{"id":324,"depth":672,"text":325},{"id":413,"depth":672,"text":414},{"id":439,"depth":652,"text":440,"children":2114},[2115],{"id":469,"depth":672,"text":470},{"id":580,"depth":652,"text":581,"children":2117},[2118,2119,2120,2121,2122],{"id":584,"depth":672,"text":585},{"id":784,"depth":672,"text":785},{"id":1252,"depth":672,"text":1253},{"id":1361,"depth":672,"text":1362},{"id":1399,"depth":672,"text":1400},{"id":1451,"depth":652,"text":1452},{"id":1883,"depth":652,"text":1884},{"id":1906,"depth":652,"text":1907},{"id":1922,"depth":652,"text":1923},"Google's own Android documentation says certificate pinning \"is not recommended for Android apps.\"","md",{"type":2130,"url":2131},"youtube","https:\u002F\u002Fyoutu.be\u002F44y-WOyP5WQ",{},"Google and Apple both advise against certificate pinning. What it really protects, five rules if you ship it, and why it silently no-ops on Flutter.","Why Your SSL Certificate Pinning Isn't Working",true,"Half the apps we audit ship certificate pinning. Most of it is switched off, aimed at the wrong threat, or — on Flutter — validating a connection the data never travels on.","\u002Fblog\u002Fssl-certificate-pinning-flutter",{"title":169,"description":2127},"ssl-certificate-pinning-flutter","blog\u002Fssl-certificate-pinning-flutter","Google says certificate pinning \"is not recommended for Android apps.\" Apple says \"in most cases, pinning is not necessary and should be avoided.\" OWASP now says the answer to \"should I pin?\" is \"probably never.\" Meanwhile pinning ships in roughly half the mobile apps we audit — and on Flutter it usually protects nothing at all, because Dart does not use the platform networking stack, so the pin-set in your network_security_config.xml is never read. Here is what pinning actually buys you in 2026, whether you need it, five rules if you do, why Flutter breaks all of them, and how to find out in half an hour which of those applies to your app.","2026-08-27T12:00:00Z","cybersecurity",null,"JqSw1oDMHGB6Qkj9rUIot-frRzcUvkK8_ujmon8MY9I",[2147,2204,2251,2301,2390,2442],{"id":2148,"bio":2149,"expertise":2154,"extension":2170,"links":2171,"meta":2175,"metaDescription":2176,"name":2181,"ogDescription":2184,"photo":2189,"role":2190,"seniority":2195,"skills":2196,"slug":2197,"specialization":2198,"stem":2197,"__hash__":2203},"team_members\u002Fdima.yaml",{"en":2150,"ru":2151,"es":2152,"nl":2153},"Dima has been building with [Flutter](\u002Ftechnologies\u002Fflutter) since 2021, and specializes in :term[state management]{slug=\"state-management\"} and app architecture.\n\nHe has hands-on experience with real-time communication protocols — :term{slug=\"webrtc\"} for audio and video, and :term{slug=\"xmpp\"} for messaging — which makes him comfortable with the network-heavy, stateful features many teams struggle to get right.\n","Дима работает с [Flutter](\u002Ftechnologies\u002Fflutter) с 2021 года и специализируется на :term[state management]{slug=\"state-management\"} и архитектуре приложений.\n\nУ него есть практический опыт с протоколами реального времени — :term{slug=\"webrtc\"} для аудио и видео и :term{slug=\"xmpp\"} для обмена сообщениями, — поэтому ему близки сетевые stateful-фичи, которые многим командам даются с трудом.\n","Dima trabaja con [Flutter](\u002Ftechnologies\u002Fflutter) desde 2021 y se especializa en :term[gestión de estado]{slug=\"state-management\"} y arquitectura de aplicaciones.\n\nTiene experiencia práctica con protocolos de comunicación en tiempo real —:term{slug=\"webrtc\"} para audio y vídeo, y :term{slug=\"xmpp\"} para mensajería—, lo que le hace sentirse cómodo con esas funcionalidades con mucho estado y mucha red que a muchos equipos se les atragantan.\n","Dima werkt sinds 2021 met [Flutter](\u002Ftechnologies\u002Fflutter) en is gespecialiseerd in :term[state management]{slug=\"state-management\"} en applicatiearchitectuur.\n\nHij heeft praktijkervaring met protocollen voor realtime communicatie — :term{slug=\"webrtc\"} voor audio en video, en :term{slug=\"xmpp\"} voor berichten — waardoor hij zich thuis voelt bij de netwerkzware functies met veel staat waar veel teams moeite mee hebben.\n",[2155,2158,2163,2165],{"en":2156,"ru":2156,"es":2157,"nl":2156},"State management","Gestión de estado",{"en":2159,"ru":2160,"es":2161,"nl":2162},"Application architecture","Архитектура приложений","Arquitectura de aplicaciones","Applicatiearchitectuur",{"en":2164,"ru":2164,"es":2164,"nl":2164},"WebRTC",{"en":2166,"ru":2167,"es":2168,"nl":2169},"XMPP messaging","Обмен сообщениями по XMPP","Mensajería XMPP","Berichten via XMPP","yaml",[2172],{"type":2173,"address":2174},"email","konopatov@nerdy.pro",{},{"en":2177,"ru":2178,"es":2179,"nl":2180},"Lead Flutter developer at Nerdy Production, building real-time apps since 2021 — state management, app architecture, WebRTC and XMPP.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года — state management, архитектура, WebRTC и XMPP.","Lead de desarrollo Flutter en Nerdy Production, construyendo apps en tiempo real desde 2021: gestión de estado, arquitectura de aplicaciones, WebRTC y XMPP.","Lead Flutter-developer bij Nerdy Production, bouwt sinds 2021 realtime-apps — state management, applicatiearchitectuur, WebRTC en XMPP.",{"en":2182,"ru":2183,"es":2182,"nl":2182},"Dima","Дима",{"en":2185,"ru":2186,"es":2187,"nl":2188},"Lead Flutter developer at Nerdy Production — real-time apps since 2021, and the state management that keeps them from falling over.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года и state management, на котором они держатся.","Lead de desarrollo Flutter en Nerdy Production: apps en tiempo real desde 2021 y la gestión de estado que evita que se caigan.","Lead Flutter-developer bij Nerdy Production — realtime-apps sinds 2021, en het state management dat ze overeind houdt.","\u002Fteam\u002Fdima.webp",{"en":2191,"ru":2192,"es":2193,"nl":2194},"Lead Flutter Developer","Ведущий Flutter-разработчик","Lead de desarrollo Flutter","Lead Flutter-developer","lead",[41,18,155,160,90,111,150,95],"dima",{"en":2199,"ru":2200,"es":2201,"nl":2202},"Real-time apps, state management, and architecture","Приложения реального времени, state management и архитектура","Apps en tiempo real, gestión de estado y arquitectura","Realtime-apps, state management en architectuur","1TuxUt3zjdVkQUvMf9dBITG872C0exrdZyyoKw1beiE",{"id":2205,"bio":2206,"expertise":2211,"extension":2170,"links":2144,"meta":2222,"metaDescription":2223,"name":2228,"ogDescription":2231,"photo":2236,"role":2237,"seniority":2242,"skills":2243,"slug":2244,"specialization":2245,"stem":2244,"__hash__":2250},"team_members\u002Fmasha.yaml",{"en":2207,"ru":2208,"es":2209,"nl":2210},"Masha builds [Flutter](\u002Ftechnologies\u002Fflutter) apps where design and copy are treated as one job.\n\nShe specializes in UI\u002FUX — turning product requirements into clean, usable interfaces — and in the writing inside the app, from onboarding flows to the microcopy that makes a screen make sense. The result is apps that feel considered, not just functional.\n","Маша делает приложения на [Flutter](\u002Ftechnologies\u002Fflutter), где дизайн и текст — одна задача.\n\nОна специализируется на UI\u002FUX, превращая продуктовые требования в чистые и удобные интерфейсы, и на текстах внутри приложения — от онбординга до микрокопирайта, который делает экран понятным. В итоге приложения получаются продуманными, а не просто рабочими.\n","Masha crea apps en [Flutter](\u002Ftechnologies\u002Fflutter) donde el diseño y el texto se tratan como un mismo trabajo.\n\nSe especializa en UI\u002FUX —convertir requisitos de producto en interfaces limpias y usables— y en la escritura dentro de la app, desde los flujos de onboarding hasta el microcopy que hace que una pantalla se entienda. El resultado son apps que se sienten pensadas, no solo funcionales.\n","Masha bouwt [Flutter](\u002Ftechnologies\u002Fflutter)-apps waarin ontwerp en tekst als één taak worden behandeld.\n\nZe is gespecialiseerd in UI\u002FUX — productwensen omzetten in heldere, bruikbare interfaces — en in de teksten binnen de app, van onboarding tot de microteksten die een scherm begrijpelijk maken. Het resultaat zijn apps die doordacht aanvoelen en niet alleen werken.\n",[2212,2217],{"en":2213,"ru":2214,"es":2215,"nl":2216},"UI\u002FUX design","UI\u002FUX-дизайн","Diseño UI\u002FUX","UI\u002FUX-ontwerp",{"en":2218,"ru":2219,"es":2220,"nl":2221},"Product copywriting","Продуктовый копирайтинг","Redacción de producto","Productteksten",{},{"en":2224,"ru":2225,"es":2226,"nl":2227},"Flutter developer at Nerdy Production working where design meets copy — UI\u002FUX, onboarding flows, and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: UI\u002FUX, онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: UI\u002FUX, onboarding y el microcopy que hace que una pantalla se entienda.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — UI\u002FUX, onboarding, en de microteksten die een scherm begrijpelijk maken.",{"en":2229,"ru":2230,"es":2229,"nl":2229},"Masha","Маша",{"en":2232,"ru":2233,"es":2234,"nl":2235},"Flutter developer at Nerdy Production working where design meets copy — onboarding flows and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: onboarding y el microcopy que hace entender una pantalla.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — onboarding en de microteksten die een scherm begrijpelijk maken.","\u002Fteam\u002Fmasha.webp",{"en":2238,"ru":2239,"es":2240,"nl":2241},"Flutter Developer","Flutter-разработчик","Desarrolladora Flutter","Flutter-developer","middle",[41,18],"masha",{"en":2246,"ru":2247,"es":2248,"nl":2249},"UI\u002FUX and copywriting for Flutter apps","UI\u002FUX и копирайтинг для Flutter-приложений","UI\u002FUX y redacción para apps Flutter","UI\u002FUX en teksten voor Flutter-apps","z3c64iWR39RIrn8wE0elSy00IfCITYgNkbgp_G_sslk",{"id":2252,"bio":2253,"expertise":2258,"extension":2170,"links":2269,"meta":2272,"metaDescription":2273,"name":2278,"ogDescription":2281,"photo":2286,"role":2287,"seniority":2292,"skills":2293,"slug":2294,"specialization":2295,"stem":2294,"__hash__":2300},"team_members\u002Fmaxim.yaml",{"en":2254,"ru":2255,"es":2256,"nl":2257},"Maxim is a polyglot engineer who moves comfortably across [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython), and [TypeScript](\u002Ftechnologies\u002Ftypescript), which lets him own a feature from the backend to the screen.\n\nHe came up at Ozon, one of Russia's largest marketplaces, where scale makes reliability non-negotiable — and it shows in his work: testing is a first-class part of how he builds, not an afterthought bolted on at the end.\n","Максим — полиглот-инженер, свободно работающий с [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) и [TypeScript](\u002Ftechnologies\u002Ftypescript), что позволяет ему вести фичу от бэкенда до экрана.\n\nОн вырос в Ozon, одном из крупнейших маркетплейсов России, где масштаб делает надёжность обязательной, — и это видно в его работе: тестирование для него первоклассная часть разработки, а не то, что прикручивают в конце.\n","Maxim es un ingeniero políglota que se mueve con soltura entre [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) y [TypeScript](\u002Ftechnologies\u002Ftypescript), lo que le permite hacerse cargo de una funcionalidad desde el backend hasta la pantalla.\n\nSe formó en Ozon, uno de los mayores marketplaces de Rusia, donde la escala hace que la fiabilidad no sea negociable, y se le nota: el testing es para él una parte de primera clase de cómo construye, no un añadido al final.\n","Maxim is een polyglotte engineer die zich moeiteloos beweegt tussen [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) en [TypeScript](\u002Ftechnologies\u002Ftypescript), waardoor hij een functie van de backend tot het scherm kan dragen.\n\nHij is opgegroeid bij Ozon, een van de grootste marktplaatsen van Rusland, waar schaal betrouwbaarheid onderhandelbaar maakt noch toestaat — en dat zie je terug in zijn werk: testen is bij hem een volwaardig onderdeel van hoe hij bouwt, geen bijzaak die er aan het eind bij komt.\n",[2259,2264],{"en":2260,"ru":2261,"es":2262,"nl":2263},"Automated testing","Автоматизированное тестирование","Testing automatizado","Geautomatiseerd testen",{"en":2265,"ru":2266,"es":2267,"nl":2268},"High-load backend systems","Высоконагруженные бэкенд-системы","Sistemas backend de alta carga","Backendsystemen met hoge belasting",[2270],{"type":2173,"address":2271},"maxim@nerdy.pro",{},{"en":2274,"ru":2275,"es":2276,"nl":2277},"Senior engineer at Nerdy Production across Go, Flutter, Python and TypeScript — from backend to screen, with automated testing built in.","Старший инженер Nerdy Production: Go, Flutter, Python и TypeScript — от бэкенда до экрана, с автотестами как частью разработки.","Ingeniero senior en Nerdy Production con Go, Flutter, Python y TypeScript: del backend a la pantalla, con testing automatizado incorporado.","Senior engineer bij Nerdy Production met Go, Flutter, Python en TypeScript — van backend tot scherm, met geautomatiseerd testen ingebouwd.",{"en":2279,"ru":2280,"es":2279,"nl":2279},"Maxim","Максим",{"en":2282,"ru":2283,"es":2284,"nl":2285},"Senior engineer at Nerdy Production working from backend to screen — Go, Flutter, Python and TypeScript, with the tests written as he goes.","Старший инженер Nerdy Production, работающий от бэкенда до экрана: Go, Flutter, Python и TypeScript — и тесты, которые пишутся по ходу дела.","Ingeniero senior en Nerdy Production, del backend a la pantalla: Go, Flutter, Python y TypeScript, con los tests escritos sobre la marcha.","Senior engineer bij Nerdy Production, van backend tot scherm — Go, Flutter, Python en TypeScript, met de tests die hij onderweg schrijft.","\u002Fteam\u002Fmax.webp",{"en":2288,"ru":2289,"es":2290,"nl":2291},"Senior Software Engineer","Старший инженер-программист","Ingeniero de software senior","Senior software engineer","senior",[41,18,51,106,155,13,150],"maxim",{"en":2296,"ru":2297,"es":2298,"nl":2299},"Backend and Flutter engineering with a testing focus","Бэкенд и Flutter с фокусом на тестирование","Ingeniería backend y Flutter con foco en testing","Backend- en Flutter-engineering met focus op testen","a-8Kx1i-PQXIGU92_Rimqr3bPEur6ZwMfPKQWr2uk4I",{"id":2302,"bio":2303,"expertise":2308,"extension":2170,"links":2350,"meta":2362,"metaDescription":2363,"name":2368,"ogDescription":2371,"photo":2376,"role":2377,"seniority":2382,"skills":2383,"slug":170,"specialization":2384,"stem":170,"__hash__":2389},"team_members\u002Fnixan.yaml",{"en":2304,"ru":2305,"es":2306,"nl":2307},"Ilya founded Nerdy Production and leads its engineering. He has been building software since 2010 and shipping production Flutter since 2018.\n\nBefore that he was CTO of QIWI, one of Russia's largest payment platforms, where he ran roughly 12 engineering teams spanning web products down to card processing, :term{slug=\"pci-dss\"} scope, and contactless payments — including building contactless card payments on Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, with EMV Contactless (Visa PayWave) on top.\n\nHe was also a principal developer at Yandex, where he worked on Yandex.Auto — taking native Android deep into the vehicle, with heavy CAN-bus integration through a custom CAN shield — and a principal at Evotor, whose point-of-sale devices run on a forked :term{slug=\"aosp\"}, giving him a low-level view of Android most app developers never touch.\n\nToday he leads delivery on the agency's flagship apps — from the chart-heavy fintech UI of [ExtraETF](\u002Fportfolio\u002Fextraetf) to the fully custom design system of [Arcana](\u002Fportfolio\u002Farcana). He writes most of the essays on this blog and maintains the agency's open-source work, including the [dxpdf](\u002Fopen-source\u002Fdxpdf) DOCX-to-PDF engine.\n\nHe works across [Flutter](\u002Ftechnologies\u002Fflutter), native iOS and Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes), and [Docker](\u002Ftechnologies\u002Fdocker), with a focus on app architecture, cross-platform delivery, and building teams that ship.\n","Илья основал Nerdy Production и руководит инженерной командой. Он занимается разработкой с 2010 года и выпускает продакшн-приложения на Flutter с 2018-го.\n\nДо этого он был CTO QIWI — одной из крупнейших платёжных платформ России, — где руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга, зоны :term{slug=\"pci-dss\"} и бесконтактных платежей, включая бесконтактную оплату картой на Android через :term[Host Card Emulation]{slug=\"host-card-emulation\"} поверх ISO\u002FIEC 14443, с платёжным протоколом EMV Contactless (Visa PayWave).\n\nОн также был принципал-разработчиком в Яндексе, где работал над Яндекс.Авто, уводя нативный Android глубоко в автомобиль, с серьёзной интеграцией по шине CAN через собственный CAN-шилд, и принципалом в Эвоторе, чьи кассовые устройства работают на форке :term{slug=\"aosp\"}, что дало ему низкоуровневый взгляд на Android, недоступный большинству прикладных разработчиков.\n\nСейчас он ведёт поставку флагманских приложений агентства — от насыщенного графиками финтех-интерфейса [ExtraETF](\u002Fportfolio\u002Fextraetf) до полностью кастомной дизайн-системы [Arcana](\u002Fportfolio\u002Farcana). Он пишет большую часть материалов этого блога и поддерживает open-source агентства, включая движок [dxpdf](\u002Fopen-source\u002Fdxpdf) для конвертации DOCX в PDF.\n\nРаботает с [Flutter](\u002Ftechnologies\u002Fflutter), нативными iOS и Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) и [Docker](\u002Ftechnologies\u002Fdocker); его фокус — архитектура приложений, кросс-платформенная поставка и построение команд, которые доводят продукт до релиза.\n","Ilya fundó Nerdy Production y dirige su ingeniería. Lleva construyendo software desde 2010 y entregando Flutter en producción desde 2018.\n\nAntes fue CTO de QIWI, una de las mayores plataformas de pago de Rusia, donde dirigió alrededor de 12 equipos de ingeniería que abarcaban desde productos web hasta procesamiento de tarjetas, alcance :term{slug=\"pci-dss\"} y pagos contactless, incluida la construcción de pagos contactless con tarjeta en Android mediante :term[Host Card Emulation]{slug=\"host-card-emulation\"} sobre ISO\u002FIEC 14443, con EMV Contactless (Visa PayWave) por encima.\n\nTambién fue principal developer en Yandex, donde trabajó en Yandex.Auto —llevando Android nativo hasta el interior del vehículo, con una integración intensiva por bus CAN a través de un CAN shield propio— y principal en Evotor, cuyos terminales de punto de venta funcionan sobre un fork de :term{slug=\"aosp\"}, lo que le dio una visión de bajo nivel de Android que la mayoría de desarrolladores de apps nunca llega a tocar.\n\nHoy lidera la entrega de las apps insignia de la agencia, desde la interfaz fintech cargada de gráficos de [ExtraETF](\u002Fportfolio\u002Fextraetf) hasta el sistema de diseño totalmente a medida de [Arcana](\u002Fportfolio\u002Farcana). Escribe la mayoría de los artículos de este blog y mantiene el trabajo de código abierto de la agencia, incluido el motor de conversión de DOCX a PDF [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nTrabaja con [Flutter](\u002Ftechnologies\u002Fflutter), iOS y Android nativos, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) y [Docker](\u002Ftechnologies\u002Fdocker), con foco en arquitectura de aplicaciones, entrega multiplataforma y la construcción de equipos que entregan.\n","Ilya richtte Nerdy Production op en leidt de engineering. Hij bouwt software sinds 2010 en levert sinds 2018 Flutter in productie.\n\nDaarvoor was hij CTO van QIWI, een van de grootste betaalplatforms van Rusland, waar hij zo'n 12 engineeringteams aanstuurde die reikten van webproducten tot kaartverwerking, :term{slug=\"pci-dss\"}-scope en contactloos betalen — waaronder het bouwen van contactloze kaartbetalingen op Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, met EMV Contactless (Visa PayWave) daarbovenop.\n\nHij was ook principal developer bij Yandex, waar hij aan Yandex.Auto werkte — native Android diep de auto in brengen, met zware integratie over de CAN-bus via een eigen CAN-shield — en principal bij Evotor, waarvan de kassa-apparaten op een fork van :term{slug=\"aosp\"} draaien, wat hem een blik op Android op laag niveau gaf die de meeste app-ontwikkelaars nooit krijgen.\n\nVandaag leidt hij de oplevering van de vlaggenschipapps van het bureau — van de grafiekzware fintech-UI van [ExtraETF](\u002Fportfolio\u002Fextraetf) tot het volledig eigen designsysteem van [Arcana](\u002Fportfolio\u002Farcana). Hij schrijft de meeste artikelen op deze blog en onderhoudt het opensourcewerk van het bureau, waaronder de DOCX-naar-PDF-motor [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nHij werkt met [Flutter](\u002Ftechnologies\u002Fflutter), native iOS en Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) en [Docker](\u002Ftechnologies\u002Fdocker), met de nadruk op applicatiearchitectuur, cross-platform oplevering en het bouwen van teams die opleveren.\n",[2309,2314,2319,2324,2329,2334,2339,2344,2346,2348],{"en":2310,"ru":2311,"es":2312,"nl":2313},"iOS development","Разработка под iOS","Desarrollo iOS","iOS-ontwikkeling",{"en":2315,"ru":2316,"es":2317,"nl":2318},"Software architecture","Архитектура ПО","Arquitectura de software","Softwarearchitectuur",{"en":2320,"ru":2321,"es":2322,"nl":2323},"Engineering team leadership","Руководство инженерными командами","Liderazgo de equipos de ingeniería","Leidinggeven aan engineeringteams",{"en":2325,"ru":2326,"es":2327,"nl":2328},"Payment systems","Платёжные системы","Sistemas de pago","Betaalsystemen",{"en":2330,"ru":2331,"es":2332,"nl":2333},"Card processing","Карточный процессинг","Procesamiento de tarjetas","Kaartverwerking",{"en":2335,"ru":2336,"es":2337,"nl":2338},"PCI-DSS compliance","Соответствие PCI-DSS","Cumplimiento de PCI-DSS","Naleving van PCI-DSS",{"en":2340,"ru":2341,"es":2342,"nl":2343},"NFC and contactless payments","NFC и бесконтактные платежи","NFC y pagos contactless","NFC en contactloos betalen",{"en":2345,"ru":2345,"es":2345,"nl":2345},"Host Card Emulation",{"en":2347,"ru":2347,"es":2347,"nl":2347},"EMV Contactless",{"en":2349,"ru":2349,"es":2349,"nl":2349},"AOSP",[2351,2354,2357,2360],{"type":2352,"url":2353},"github","https:\u002F\u002Fgithub.com\u002Fthenixan",{"type":2355,"url":2356},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthenixan\u002F",{"type":2358,"url":2359},"telegram","https:\u002F\u002Ft.me\u002Fthenixan",{"type":2173,"address":2361},"nixan@nerdy.pro",{},{"en":2364,"ru":2365,"es":2366,"nl":2367},"Founder and lead developer at Nerdy Production, former CTO of QIWI. Flutter architecture, payments infrastructure, and engineering teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI. Архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI. Arquitectura Flutter, infraestructura de pagos y equipos de ingeniería que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI. Flutter-architectuur, betaalinfrastructuur en teams die opleveren.",{"en":2369,"ru":2370,"es":2369,"nl":2369},"Ilya Nixan","Илья Никсан",{"en":2372,"ru":2373,"es":2374,"nl":2375},"Founder and lead developer at Nerdy Production, former CTO of QIWI — Flutter architecture, payments infrastructure, and teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI: архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI: arquitectura Flutter, infraestructura de pagos y equipos que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI — Flutter-architectuur, betaalinfrastructuur en teams die opleveren.","\u002Fteam\u002Fnixan.webp",{"en":2378,"ru":2379,"es":2380,"nl":2381},"Founder & Lead Developer","Основатель и ведущий разработчик","Fundador y lead developer","Oprichter & lead developer","founder_lead",[41,18,71,141,76,146,51,126,121,155,160,90,13,150,136,7,29,80],{"en":2385,"ru":2386,"es":2387,"nl":2388},"Flutter architecture and leading delivery teams","Архитектура на Flutter и руководство командами поставки","Arquitectura Flutter y liderazgo de equipos de entrega","Flutter-architectuur en het leiden van opleverteams","BmeQZ7JyhRFIx8527WO2t1-WklP8AAvevAxiN9U4TEs",{"id":2391,"bio":2392,"expertise":2397,"extension":2170,"links":2409,"meta":2414,"metaDescription":2415,"name":2420,"ogDescription":2423,"photo":2428,"role":2429,"seniority":2292,"skills":2434,"slug":2435,"specialization":2436,"stem":2435,"__hash__":2441},"team_members\u002Froma.yaml",{"en":2393,"ru":2394,"es":2395,"nl":2396},"Roman works across native iOS and Android and [Flutter](\u002Ftechnologies\u002Fflutter), and pairs that mobile depth with [Python](\u002Ftechnologies\u002Fpython) and applied AI.\n\nMuch of his background is in ERP and CRM development — complex, data-heavy business systems where getting the domain model right matters more than the UI — which gives him a pragmatic eye for how an app fits the process behind it.\n","Роман работает с нативными iOS и Android и с [Flutter](\u002Ftechnologies\u002Fflutter), дополняя мобильную экспертизу [Python](\u002Ftechnologies\u002Fpython) и прикладным AI.\n\nЗначительная часть его опыта — разработка ERP и CRM, сложных систем с большим объёмом данных, где правильная доменная модель важнее интерфейса, — что даёт ему прагматичный взгляд на то, как приложение встраивается в процесс за ним.\n","Roman trabaja con iOS y Android nativos y con [Flutter](\u002Ftechnologies\u002Fflutter), y combina esa profundidad móvil con [Python](\u002Ftechnologies\u002Fpython) e IA aplicada.\n\nBuena parte de su trayectoria está en el desarrollo de ERP y CRM —sistemas de negocio complejos y con mucho dato, donde acertar con el modelo de dominio importa más que la interfaz—, lo que le da una mirada pragmática sobre cómo encaja una app en el proceso que hay detrás.\n","Roman werkt met native iOS en Android en met [Flutter](\u002Ftechnologies\u002Fflutter), en combineert die mobiele diepgang met [Python](\u002Ftechnologies\u002Fpython) en toegepaste AI.\n\nEen groot deel van zijn achtergrond ligt in ERP- en CRM-ontwikkeling — complexe, datazware bedrijfssystemen waarin het domeinmodel goed krijgen zwaarder weegt dan de interface — wat hem een pragmatische blik geeft op hoe een app in het proces erachter past.\n",[2398,2399,2404],{"en":2310,"ru":2311,"es":2312,"nl":2313},{"en":2400,"ru":2401,"es":2402,"nl":2403},"Applied AI","Прикладной AI","IA aplicada","Toegepaste AI",{"en":2405,"ru":2406,"es":2407,"nl":2408},"ERP and CRM systems","ERP- и CRM-системы","Sistemas ERP y CRM","ERP- en CRM-systemen",[2410,2412],{"type":2355,"url":2411},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fromanbatler\u002F",{"type":2173,"address":2413},"roma@nerdy.pro",{},{"en":2416,"ru":2417,"es":2418,"nl":2419},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and a background in data-heavy ERP and CRM systems.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и опыт в ERP- и CRM-системах.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y experiencia en sistemas ERP y CRM con gran volumen de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en een achtergrond in datazware ERP- en CRM-systemen.",{"en":2421,"ru":2422,"es":2421,"nl":2421},"Roman","Рома",{"en":2424,"ru":2425,"es":2426,"nl":2427},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and years of data-heavy ERP and CRM behind him.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и годы работы с нагруженными ERP и CRM.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y años de ERP y CRM con mucha carga de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en jaren datazware ERP en CRM achter zich.","\u002Fteam\u002Froma.webp",{"en":2430,"ru":2431,"es":2432,"nl":2433},"Senior Mobile Developer","Старший мобильный разработчик","Desarrollador móvil senior","Senior mobiele developer",[7,41,18,141,146,106],"roma",{"en":2437,"ru":2438,"es":2439,"nl":2440},"Mobile, AI, and ERP\u002FCRM systems","Мобильная разработка, AI и системы ERP\u002FCRM","Móvil, IA y sistemas ERP\u002FCRM","Mobiel, AI en ERP\u002FCRM-systemen","caPSPO2BX35G9lEjNb6p3U4uF_V7yh8cexCe6l5hm8Y",{"id":2443,"bio":2444,"expertise":2449,"extension":2170,"links":2144,"meta":2460,"metaDescription":2461,"name":2466,"ogDescription":2469,"photo":2474,"role":2475,"seniority":2292,"skills":2480,"slug":2481,"specialization":2482,"stem":2481,"__hash__":2487},"team_members\u002Fxsox.yaml",{"en":2445,"ru":2446,"es":2447,"nl":2448},"Eugene builds the systems that apps depend on. His background is in backend engineering — designing and running the APIs, data models, and services behind a product, primarily in [Python](\u002Ftechnologies\u002Fpython) and [Django](\u002Ftechnologies\u002Fdjango).\n\nHe also works in [Flutter](\u002Ftechnologies\u002Fflutter), so he can reason about a feature from the database to the device and build the backend so the app that consumes it stays simple.\n","Женя строит системы, на которые опираются приложения. Его основной опыт — бэкенд-инженерия: проектирование и эксплуатация API, моделей данных и сервисов за продуктом, в первую очередь на [Python](\u002Ftechnologies\u002Fpython) и [Django](\u002Ftechnologies\u002Fdjango).\n\nОн также работает с [Flutter](\u002Ftechnologies\u002Fflutter), поэтому может рассуждать о фиче от базы данных до устройства и строить бэкенд так, чтобы потребляющее его приложение оставалось простым.\n","Eugene construye los sistemas de los que dependen las apps. Su trayectoria está en la ingeniería backend: diseñar y operar las APIs, los modelos de datos y los servicios que hay detrás de un producto, principalmente en [Python](\u002Ftechnologies\u002Fpython) y [Django](\u002Ftechnologies\u002Fdjango).\n\nTambién trabaja con [Flutter](\u002Ftechnologies\u002Fflutter), así que puede razonar sobre una funcionalidad desde la base de datos hasta el dispositivo y construir el backend de forma que la app que lo consume siga siendo simple.\n","Eugene bouwt de systemen waar apps op leunen. Zijn achtergrond ligt in backend-engineering: de API's, datamodellen en services achter een product ontwerpen en draaien, vooral in [Python](\u002Ftechnologies\u002Fpython) en [Django](\u002Ftechnologies\u002Fdjango).\n\nHij werkt daarnaast met [Flutter](\u002Ftechnologies\u002Fflutter), zodat hij over een functie kan nadenken van de database tot het toestel en de backend zo kan bouwen dat de app die hem verbruikt eenvoudig blijft.\n",[2450,2455],{"en":2451,"ru":2452,"es":2453,"nl":2454},"API design","Проектирование API","Diseño de APIs","API-ontwerp",{"en":2456,"ru":2457,"es":2458,"nl":2459},"Data modelling","Моделирование данных","Modelado de datos","Datamodellering",{},{"en":2462,"ru":2463,"es":2464,"nl":2465},"Senior backend developer at Nerdy Production building the APIs, data models and services behind our apps, primarily in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы за нашими приложениями, в первую очередь на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios detrás de nuestras apps, sobre todo en Python y Django.","Senior backenddeveloper bij Nerdy Production die de API's, datamodellen en services achter onze apps bouwt, vooral in Python en Django.",{"en":2467,"ru":2468,"es":2467,"nl":2467},"Eugene Xsox","Женя Xsox",{"en":2470,"ru":2471,"es":2472,"nl":2473},"Senior backend developer at Nerdy Production — the APIs, data models and services our apps run on, mostly in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы, на которых работают наши приложения, в основном на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios sobre los que corren nuestras apps, en Python y Django.","Senior backenddeveloper bij Nerdy Production — de API's, datamodellen en services waarop onze apps draaien, vooral in Python en Django.","\u002Fteam\u002Fxsox.webp",{"en":2476,"ru":2477,"es":2478,"nl":2479},"Senior Backend Developer","Старший бэкенд-разработчик","Desarrollador backend senior","Senior backenddeveloper",[106,24,41,18,155,160,90,150],"xsox",{"en":2483,"ru":2484,"es":2485,"nl":2486},"Python\u002FDjango backends, plus Flutter","Бэкенды на Python\u002FDjango и Flutter","Backends en Python\u002FDjango, además de Flutter","Backends in Python\u002FDjango, plus Flutter","w_QAKKq_Mh_dtMvAxFpAJBC10PW1I7zgVOxELwRHwO8",[2489,2497,2504,2509,2519],{"id":2490,"extension":2170,"meta":2491,"name":2492,"slug":2143,"stem":2143,"__hash__":2496},"blog_topics\u002Fcybersecurity.yaml",{},{"en":2493,"ru":2494,"es":2495,"nl":2493},"Cybersecurity","Кибербезопасность","Ciberseguridad","kt1rdsMy5W4b9MvGHm6mZVCr0inHidEuqaosPYmmk4E",{"id":2498,"extension":2170,"meta":2499,"name":2500,"slug":2502,"stem":2502,"__hash__":2503},"blog_topics\u002Fdevops.yaml",{},{"en":2501,"ru":2501,"es":2501,"nl":2501},"DevOps","devops","D3mxlZuFHKCGeszKZuWCiQmHmP5EiCvzg1qEAIQhnyU",{"id":2505,"extension":2170,"meta":2506,"name":2507,"slug":41,"stem":41,"__hash__":2508},"blog_topics\u002Fflutter.yaml",{},{"en":39,"ru":39,"es":39,"nl":39},"OOeALAmwFuEqByuJvTGk4g15FIHENPfYMdpfZh_HTAE",{"id":2510,"extension":2170,"meta":2511,"name":2512,"slug":2517,"stem":2517,"__hash__":2518},"blog_topics\u002Ffor-founders.yaml",{},{"en":2513,"ru":2514,"es":2515,"nl":2516},"For Founders","Для основателей","Para fundadores","Voor oprichters","for-founders","lp2u6Ol2qF4DGxpB83HxbbGzWioT-e40mJhD4lr28-U",{"id":2520,"extension":2170,"meta":2521,"name":2522,"slug":2527,"stem":2527,"__hash__":2528},"blog_topics\u002Fsoftware-engineering.yaml",{},{"en":2523,"ru":2524,"es":2525,"nl":2526},"Software Engineering","Разработка ПО","Ingeniería de software","Software-engineering","software-engineering","aZMb3aUDptoFQk_cEE6l5H4wrYcgZuBYUubmKqOAaaU",{"data":2530,"body":2531},{},{"type":2532,"children":2533},"root",[2534,2541,2559,2570,2599],{"type":2535,"tag":175,"props":2536,"children":2537},"element",{},[2538],{"type":2539,"value":2540},"text","Ilya founded Nerdy Production and leads its engineering. He has been building software since 2010 and shipping production Flutter since 2018.",{"type":2535,"tag":175,"props":2542,"children":2543},{},[2544,2546,2550,2552,2557],{"type":2539,"value":2545},"Before that he was CTO of QIWI, one of Russia's largest payment platforms, where he ran roughly 12 engineering teams spanning web products down to card processing, ",{"type":2535,"tag":276,"props":2547,"children":2549},{"slug":2548},"pci-dss",[],{"type":2539,"value":2551}," scope, and contactless payments — including building contactless card payments on Android via ",{"type":2535,"tag":276,"props":2553,"children":2555},{"slug":2554},"host-card-emulation",[2556],{"type":2539,"value":2345},{"type":2539,"value":2558}," over ISO\u002FIEC 14443, with EMV Contactless (Visa PayWave) on top.",{"type":2535,"tag":175,"props":2560,"children":2561},{},[2562,2564,2568],{"type":2539,"value":2563},"He was also a principal developer at Yandex, where he worked on Yandex.Auto — taking native Android deep into the vehicle, with heavy CAN-bus integration through a custom CAN shield — and a principal at Evotor, whose point-of-sale devices run on a forked ",{"type":2535,"tag":276,"props":2565,"children":2567},{"slug":2566},"aosp",[],{"type":2539,"value":2569},", giving him a low-level view of Android most app developers never touch.",{"type":2535,"tag":175,"props":2571,"children":2572},{},[2573,2575,2581,2583,2589,2591,2597],{"type":2539,"value":2574},"Today he leads delivery on the agency's flagship apps — from the chart-heavy fintech UI of ",{"type":2535,"tag":1498,"props":2576,"children":2578},{"href":2577},"\u002Fportfolio\u002Fextraetf",[2579],{"type":2539,"value":2580},"ExtraETF",{"type":2539,"value":2582}," to the fully custom design system of ",{"type":2535,"tag":1498,"props":2584,"children":2586},{"href":2585},"\u002Fportfolio\u002Farcana",[2587],{"type":2539,"value":2588},"Arcana",{"type":2539,"value":2590},". He writes most of the essays on this blog and maintains the agency's open-source work, including the ",{"type":2535,"tag":1498,"props":2592,"children":2594},{"href":2593},"\u002Fopen-source\u002Fdxpdf",[2595],{"type":2539,"value":2596},"dxpdf",{"type":2539,"value":2598}," DOCX-to-PDF engine.",{"type":2535,"tag":175,"props":2600,"children":2601},{},[2602,2604,2609,2611,2616,2617,2622,2623,2628,2629,2634,2635,2640,2642,2647],{"type":2539,"value":2603},"He works across ",{"type":2535,"tag":1498,"props":2605,"children":2607},{"href":2606},"\u002Ftechnologies\u002Fflutter",[2608],{"type":2539,"value":39},{"type":2539,"value":2610},", native iOS and Android, ",{"type":2535,"tag":1498,"props":2612,"children":2614},{"href":2613},"\u002Ftechnologies\u002Fgo",[2615],{"type":2539,"value":49},{"type":2539,"value":1532},{"type":2535,"tag":1498,"props":2618,"children":2620},{"href":2619},"\u002Ftechnologies\u002Frust",[2621],{"type":2539,"value":124},{"type":2539,"value":1532},{"type":2535,"tag":1498,"props":2624,"children":2626},{"href":2625},"\u002Ftechnologies\u002Ftypescript",[2627],{"type":2539,"value":153},{"type":2539,"value":1532},{"type":2535,"tag":1498,"props":2630,"children":2632},{"href":2631},"\u002Ftechnologies\u002Fkotlin",[2633],{"type":2539,"value":69},{"type":2539,"value":1532},{"type":2535,"tag":1498,"props":2636,"children":2638},{"href":2637},"\u002Ftechnologies\u002Fkubernetes",[2639],{"type":2539,"value":78},{"type":2539,"value":2641},", and ",{"type":2535,"tag":1498,"props":2643,"children":2645},{"href":2644},"\u002Ftechnologies\u002Fdocker",[2646],{"type":2539,"value":27},{"type":2539,"value":2648},", with a focus on app architecture, cross-platform delivery, and building teams that ship.",[2650,2662,2675,2690,2704,2718,2734,2746,2760,2773,2786,2799,2813,2826,2842,2855,2866,2876,2889,2899,2910,2925,2934,2947,2960,2967,2978,2990,3001,3016,3028,3041,3051,3063,3075,3087,3098,3109,3120,3131,3143,3153,3164,3177,3189,3198,3210,3222,3232,3243,3256,3265,3276],{"slug":2566,"term":2349,"definition":2651,"category":2652,"aliases":2653,"links":2655,"related":2659,"readMore":-1,"target":2660,"hasArticle":2661},"The Android Open Source Project — Android without the Google layer on top, which anyone may fork. Point-of-sale terminals, kiosks and in-car systems run on forks of it, and working at that level exposes parts of the OS an app developer never sees.","platform",[2654],"Android Open Source Project",[2656],{"kind":2657,"url":2658},"website","https:\u002F\u002Fsource.android.com\u002F",[2554],"\u002Fglossary#aosp",false,{"slug":2663,"term":2664,"definition":2665,"category":2652,"aliases":2666,"links":2667,"related":2671,"readMore":-1,"target":2674,"hasArticle":2661},"app-clips","App Clips","An Apple feature that runs a small slice of an iOS app — under 15 MB — without installing the whole thing. Invoked from a QR code, an NFC tag or a link, for when the first thing a user does should not require a store visit.",[],[2668],{"kind":2669,"url":2670},"documentation","https:\u002F\u002Fdeveloper.apple.com\u002Fapp-clips\u002F",[2672,2673],"deep-linking","install-referrer","\u002Fglossary#app-clips",{"slug":2676,"term":2677,"definition":2678,"category":2652,"aliases":2679,"links":2680,"related":2686,"readMore":-1,"target":2689,"hasArticle":2661},"bigquery","BigQuery","Google Cloud's analytics warehouse. You point SQL at billions of rows and it scans them in seconds, on storage held separately from the machines doing the querying — which is what keeps reporting and exploration off the database that is serving live traffic.",[],[2681,2683],{"kind":2657,"url":2682},"https:\u002F\u002Fcloud.google.com\u002Fbigquery",{"kind":2684,"url":2685},"wikipedia","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBigQuery",[2687,2688],"object-storage","elasticsearch","\u002Fglossary#bigquery",{"slug":2691,"term":2692,"definition":2693,"category":2694,"aliases":2695,"links":2699,"related":2702,"readMore":-1,"target":2703,"hasArticle":2661},"ci-cd","CI\u002FCD","Automation that builds, tests and ships every change without anyone running commands by hand. On mobile it is what turns a release into a button press instead of an afternoon of someone else being unavailable.","practice",[2696,2697,2698],"CI","continuous integration","continuous delivery",[2700],{"kind":2684,"url":2701},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCI\u002FCD",[],"\u002Fglossary#ci-cd",{"slug":2705,"term":2706,"definition":2707,"category":2708,"aliases":2709,"links":2711,"related":2714,"readMore":-1,"target":2717,"hasArticle":2661},"crud","CRUD","Create, read, update, delete — the four operations behind almost every form and admin screen. Shorthand for the routine data-management half of an app, as opposed to the parts carrying real domain logic.","architecture",[2710],"Create, Read, Update, Delete",[2712],{"kind":2684,"url":2713},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete",[2715,2716],"rest","graphql","\u002Fglossary#crud",{"slug":2719,"term":2720,"definition":2721,"category":2708,"aliases":2722,"links":2727,"related":2730,"readMore":-1,"target":2733,"hasArticle":2661},"container-registry","Container registry","A hosted store for container images, addressed by name and tag — Docker Hub, GitHub Container Registry, or a cloud provider's own. Pushing a build there turns 'works on my machine' into an image anyone can pull and run unchanged.",[2723,2724,2725,2726],"image registry","Docker registry","GHCR","ghcr.io",[2728],{"kind":2669,"url":2729},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fpackages\u002Fworking-with-a-github-packages-registry\u002Fworking-with-the-container-registry",[2731,2732],"dev-container","multi-arch-image","\u002Fglossary#container-registry",{"slug":2672,"term":2735,"definition":2736,"category":2652,"aliases":2737,"links":2741,"related":2744,"readMore":2745,"target":2745,"hasArticle":2135},"Deep linking","A link that opens a specific screen inside an installed app instead of its home screen or a web page. Deferred deep linking survives an install, so a tap that leads through the app store still lands on the right screen.",[2738,2739,2740],"deferred deep linking","universal links","Android App Links",[2742],{"kind":2684,"url":2743},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMobile_deep_linking",[2663,2673],"\u002Fglossary\u002Fdeep-linking",{"slug":2731,"term":2747,"definition":2748,"category":2694,"aliases":2749,"links":2753,"related":2758,"readMore":-1,"target":2759,"hasArticle":2661},"Dev Container","A development environment described once in devcontainer.json — the OS, tools, and runtime versions a project needs — and opened identically inside a container by every contributor's editor, instead of a setup guide everyone interprets differently.",[2750,2751,2752],"devcontainer.json","Dev Containers","VS Code Dev Containers",[2754,2756],{"kind":2657,"url":2755},"https:\u002F\u002Fcontainers.dev\u002F",{"kind":2669,"url":2757},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fdevcontainers\u002Fcontainers",[2719],"\u002Fglossary#dev-container",{"slug":2688,"term":2761,"definition":2762,"category":2652,"aliases":2763,"links":2766,"related":2771,"readMore":-1,"target":2772,"hasArticle":2661},"Elasticsearch","A search and analytics engine that indexes records so they can be filtered and searched interactively instead of scanned. What you reach for when the question is \"show me these particular sessions, narrowed six ways\" rather than \"sum this column\".",[2764,2765],"Elastic","ELK",[2767,2769],{"kind":2657,"url":2768},"https:\u002F\u002Fwww.elastic.co\u002Felasticsearch",{"kind":2684,"url":2770},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FElasticsearch",[2676],"\u002Fglossary#elasticsearch",{"slug":2774,"term":2775,"definition":2776,"category":2708,"aliases":2777,"links":2780,"related":2783,"readMore":-1,"target":2785,"hasArticle":2661},"end-to-end-encryption","End-to-end encryption","Encryption applied on the sending device and undone only on the receiving one, so the service carrying the message cannot read it — not under subpoena, not after a breach. It protects the content and never the metadata.",[2778,2779],"E2EE","end-to-end encrypted",[2781],{"kind":2684,"url":2782},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEnd-to-end_encryption",[278,2784],"jwt","\u002Fglossary#end-to-end-encryption",{"slug":2787,"term":2788,"definition":2789,"category":2708,"aliases":2790,"links":2793,"related":2794,"readMore":-1,"target":2798,"hasArticle":2661},"fan-out","Fan-out","Reading an upstream source once and delivering each update to every client subscribed to it. The naive version writes to subscribers in a loop and stalls the moment one socket is slow; a real one buffers per client and drops whoever cannot keep up.",[2791,2792],"fanout","broadcast",[],[2795,2796,2797],"websocket","pub-sub","server-sent-events","\u002Fglossary#fan-out",{"slug":1442,"term":2800,"definition":2801,"category":2694,"aliases":2802,"links":2806,"related":2809,"readMore":-1,"target":2812,"hasArticle":2661},"Feature flags","Switches that turn functionality on or off from configuration rather than from a release. They let one binary behave differently per brand, market or user, and let a risky feature be shut off without shipping a new build through review.",[2803,2804,2805],"feature flag","feature toggle","feature gating",[2807],{"kind":2684,"url":2808},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFeature_toggle",[1372,2810,2811],"white-label","multi-tenancy","\u002Fglossary#feature-flags",{"slug":2814,"term":2815,"definition":2816,"category":2708,"aliases":2817,"links":2821,"related":2824,"readMore":-1,"target":2825,"hasArticle":2661},"floating-point","Floating point","The IEEE 754 binary format behind double and float. It cannot hold 0.1 exactly, so 0.1 + 0.2 is 0.30000000000000004 — invisible in graphics and fatal in money, which belongs in integer minor units or a decimal type instead.",[2818,2819,2820],"IEEE 754","double","floating-point arithmetic",[2822],{"kind":2684,"url":2823},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FIEEE_754",[],"\u002Fglossary#floating-point",{"slug":2827,"term":2828,"definition":2829,"category":2694,"aliases":2830,"links":2833,"related":2838,"readMore":-1,"target":2841,"hasArticle":2661},"gdpr","GDPR","The EU regulation covering personal data of people in the EU: a lawful basis for collecting it, real consent for tracking, and rights to see and delete it. It follows your users, not your servers, so it applies wherever the company is registered.",[2831,2832],"General Data Protection Regulation","data protection",[2834,2836],{"kind":2657,"url":2835},"https:\u002F\u002Fgdpr.eu\u002F",{"kind":2684,"url":2837},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGeneral_Data_Protection_Regulation",[2548,2839,2840],"soc-2","hipaa","\u002Fglossary#gdpr",{"slug":2843,"term":2844,"definition":2845,"category":2694,"aliases":2846,"links":2850,"related":2853,"readMore":-1,"target":2854,"hasArticle":2661},"golden-test","Golden test","A test that renders a widget and compares the result pixel for pixel against a stored reference image. In Flutter it is the cheapest way to answer whether a redesign broke the empty state at 320pt, in dark mode, at 200% text scale.",[2847,2848,2849],"golden tests","screenshot test","snapshot test",[2851],{"kind":2669,"url":2852},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FmatchesGoldenFile.html",[2691],"\u002Fglossary#golden-test",{"slug":2716,"term":2856,"definition":2857,"category":2708,"aliases":2858,"links":2859,"related":2864,"readMore":-1,"target":2865,"hasArticle":2661},"GraphQL","A query language for APIs where the client names exactly the fields it wants and gets one response shaped to match. It removes the over-fetching REST endpoints drift into, and adds a failure mode of its own: an unbounded query that walks the whole data model.",[],[2860,2862],{"kind":2657,"url":2861},"https:\u002F\u002Fgraphql.org\u002F",{"kind":2684,"url":2863},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGraphQL",[2715],"\u002Fglossary#graphql",{"slug":2840,"term":2867,"definition":2868,"category":2694,"aliases":2869,"links":2871,"related":2874,"readMore":-1,"target":2875,"hasArticle":2661},"HIPAA","The US law governing protected health information — how it may be stored, transmitted, logged and disclosed. Like PCI-DSS it is an architectural constraint chosen at the start, not a policy document added before launch.",[2870],"Health Insurance Portability and Accountability Act",[2872],{"kind":2684,"url":2873},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHealth_Insurance_Portability_and_Accountability_Act",[2548,2827,2839],"\u002Fglossary#hipaa",{"slug":2877,"term":2878,"definition":2879,"category":2708,"aliases":2880,"links":2883,"related":2886,"readMore":-1,"target":2888,"hasArticle":2661},"headless-cms","Headless CMS","A content system with an editor and an API but no front end of its own. Editors publish in one place, and the site or app renders that content itself — so the presentation layer is yours rather than the CMS vendor's.",[2881,2882],"headless content management system","content API",[2884],{"kind":2684,"url":2885},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHeadless_content_management_system",[2887,2715],"server-side-rendering","\u002Fglossary#headless-cms",{"slug":2554,"term":2345,"definition":2890,"category":2652,"aliases":2891,"links":2894,"related":2897,"readMore":-1,"target":2898,"hasArticle":2661},"Letting an Android phone act as a contactless card over NFC in software, with no hardware secure element. It is how a wallet app pays at a terminal: the phone speaks the same EMV contactless protocol the plastic card would have.",[2892,2347,2893],"HCE","contactless payments",[2895],{"kind":2669,"url":2896},"https:\u002F\u002Fdeveloper.android.com\u002Fdevelop\u002Fconnectivity\u002Fnfc\u002Fhce",[2548,2566],"\u002Fglossary#host-card-emulation",{"slug":2900,"term":2901,"definition":2902,"category":2652,"aliases":2903,"links":2904,"related":2907,"readMore":-1,"target":2909,"hasArticle":2661},"impeller","Impeller","The rendering engine Flutter uses today, default on iOS since 2023 and on Android since 2024. It compiles its shaders ahead of time instead of during the first animation, which removed the shader-compilation jank that was Flutter's most visible production problem.",[],[2905],{"kind":2669,"url":2906},"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller",[2908],"skia","\u002Fglossary#impeller",{"slug":2911,"term":2912,"definition":2913,"category":2914,"aliases":2915,"links":2919,"related":2922,"readMore":-1,"target":2924,"hasArticle":2661},"in-app-purchase","In-app purchase","Selling digital goods or a subscription through the Apple or Google billing that both stores require for digital content and take a commission on. The hard part is never the purchase; it is restoring it on a new device and keeping entitlement state honest.","business",[2916,2917,2918],"IAP","in-app purchases","in-app subscription",[2920],{"kind":2669,"url":2921},"https:\u002F\u002Fdeveloper.apple.com\u002Fin-app-purchase\u002F",[2923],"product-market-fit","\u002Fglossary#in-app-purchase",{"slug":2673,"term":2926,"definition":2927,"category":2652,"aliases":2928,"links":2929,"related":2932,"readMore":-1,"target":2933,"hasArticle":2661},"Install Referrer","A Google Play API that hands a freshly installed Android app the campaign parameters from the link that led to the install. The Android half of deferred deep linking, and the dependable way to attribute where a user came from.",[],[2930],{"kind":2669,"url":2931},"https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Finstallreferrer",[2672,2663],"\u002Fglossary#install-referrer",{"slug":2784,"term":2935,"definition":2936,"category":2937,"aliases":2938,"links":2940,"related":2945,"readMore":-1,"target":2946,"hasArticle":2661},"JWT","A signed token carrying its own claims, so a server can tell who a request belongs to without looking a session up. Standard for mobile authentication. The signature proves it was not altered; it does not hide what is inside.","protocol",[2939],"JSON Web Token",[2941,2943],{"kind":2657,"url":2942},"https:\u002F\u002Fjwt.io\u002F",{"kind":2684,"url":2944},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FJSON_Web_Token",[2548],"\u002Fglossary#jwt",{"slug":2948,"term":2949,"definition":2950,"category":2694,"aliases":2951,"links":2955,"related":2958,"readMore":-1,"target":2959,"hasArticle":2661},"kyc","KYC","Know Your Customer — the identity checks a regulated financial product runs before it lets anyone move money: document capture, liveness, sanctions and anti-money-laundering screening. It shapes onboarding more than any design decision does.",[2952,2953,2954],"Know Your Customer","AML","KYC\u002FAML",[2956],{"kind":2684,"url":2957},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKnow_your_customer",[2548,2827],"\u002Fglossary#kyc",{"slug":76,"term":74,"definition":2961,"category":2652,"aliases":2962,"links":2964,"related":2965,"readMore":2966,"target":2966,"hasArticle":2661},"Sharing business logic written in Kotlin across Android, iOS and the server while each platform keeps its own native UI. The alternative to Flutter when the interface has to be native but the rules behind it do not.",[2963],"KMP",[],[],"\u002Ftechnologies\u002Fkmp",{"slug":2968,"term":2969,"definition":2970,"category":2914,"aliases":2971,"links":2973,"related":2976,"readMore":-1,"target":2977,"hasArticle":2661},"mvp","MVP","The smallest version of a product that can go in front of real users and still answer the question you built it to answer. A decision about scope, not about quality — an MVP still has to work.",[2972],"minimum viable product",[2974],{"kind":2684,"url":2975},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMinimum_viable_product",[2810],"\u002Fglossary#mvp",{"slug":2732,"term":2979,"definition":2980,"category":2708,"aliases":2981,"links":2985,"related":2988,"readMore":-1,"target":2989,"hasArticle":2661},"Multi-architecture image","A single image tag that resolves to different binaries per CPU architecture — linux\u002Famd64 and linux\u002Farm64 are the common pair — so the same docker pull works unchanged on Intel\u002FAMD servers and Apple Silicon laptops.",[2982,2983,2984],"multi-arch build","multi-platform image","linux\u002Famd64 + linux\u002Farm64",[2986],{"kind":2669,"url":2987},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fmulti-platform\u002F",[2719],"\u002Fglossary#multi-arch-image",{"slug":2811,"term":2991,"definition":2992,"category":2708,"aliases":2993,"links":2996,"related":2999,"readMore":-1,"target":3000,"hasArticle":2661},"Multi-tenancy","One deployment serving many customers, each seeing only its own data, configuration and enabled features because tenant context is resolved per request. It is what makes a fleet of branded apps one product instead of many forks.",[2994,2995],"multi-tenant","tenant",[2997],{"kind":2684,"url":2998},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMultitenancy",[2810,1442],"\u002Fglossary#multi-tenancy",{"slug":3002,"term":3003,"definition":3004,"category":2937,"aliases":3005,"links":3009,"related":3014,"readMore":-1,"target":3015,"hasArticle":2661},"oauth","OAuth","The standard behind Sign in with Apple, Google and the rest: the user authorises your app at a provider they already trust, and your app receives a token instead of their password. Nobody invents a new credential and you never store one.",[3006,3007,3008],"OAuth 2.0","social login","Sign in with Apple",[3010,3012],{"kind":2657,"url":3011},"https:\u002F\u002Foauth.net\u002F2\u002F",{"kind":2684,"url":3013},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOAuth",[2784],"\u002Fglossary#oauth",{"slug":2687,"term":3017,"definition":3018,"category":2708,"aliases":3019,"links":3023,"related":3026,"readMore":-1,"target":3027,"hasArticle":2661},"Object storage","Storage that holds a whole file under a key rather than in a filesystem tree — Amazon S3 and the many services that speak its API. Cheap, effectively unlimited, and the usual home for raw events, backups and media: written once, read rarely, kept forever.",[3020,3021,3022],"S3","S3-compatible storage","blob storage",[3024],{"kind":2684,"url":3025},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject_storage",[2676],"\u002Fglossary#object-storage",{"slug":2548,"term":3029,"definition":3030,"category":2694,"aliases":3031,"links":3034,"related":3039,"readMore":-1,"target":3040,"hasArticle":2661},"PCI-DSS","The card industry security standard binding anyone who stores, processes or transmits card data. Most apps stay out of its scope on purpose, by handing card entry to a certified payment provider instead.",[3032,3033],"PCI DSS","Payment Card Industry Data Security Standard",[3035,3037],{"kind":2657,"url":3036},"https:\u002F\u002Fwww.pcisecuritystandards.org\u002F",{"kind":2684,"url":3038},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPayment_Card_Industry_Data_Security_Standard",[2784],"\u002Fglossary#pci-dss",{"slug":1809,"term":3042,"definition":3043,"category":2652,"aliases":3044,"links":3046,"related":3049,"readMore":-1,"target":3050,"hasArticle":2661},"Platform channels","The bridge a Flutter app uses to call native iOS and Android code — Keychain and Keystore, biometrics, payment sheets, any SDK without a Dart package. Routine work but real work, and the first place an engineer who never left Dart will stall.",[1810,3045],"method channel",[3047],{"kind":2669,"url":3048},"https:\u002F\u002Fdocs.flutter.dev\u002Fplatform-integration\u002Fplatform-channels",[76],"\u002Fglossary#platform-channels",{"slug":3052,"term":3053,"definition":3054,"category":2937,"aliases":3055,"links":3058,"related":3061,"readMore":-1,"target":3062,"hasArticle":2661},"post-quantum-cryptography","Post-quantum cryptography","Encryption algorithms built to stay secure against a future quantum computer, now standardized by NIST. The migration is urgent ahead of the hardware because traffic captured today can be decrypted once such a machine exists.",[3056,3057],"PQC","post-quantum crypto",[3059],{"kind":2684,"url":3060},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPost-quantum_cryptography",[278,2774],"\u002Fglossary#post-quantum-cryptography",{"slug":2923,"term":3064,"definition":3065,"category":2914,"aliases":3066,"links":3069,"related":3072,"readMore":-1,"target":3074,"hasArticle":2661},"Product-market fit","The point at which a product has demonstrably found people who want it — they use it, come back, and pay. Before it, engineering answers a question; after it, engineering answers demand.",[3067,3068],"PMF","product\u002Fmarket fit",[3070],{"kind":2684,"url":3071},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct-market_fit",[2968,3073],"time-to-market","\u002Fglossary#product-market-fit",{"slug":3076,"term":3077,"definition":3078,"category":2694,"aliases":3079,"links":3081,"related":3084,"readMore":-1,"target":3086,"hasArticle":2661},"prompt-injection","Prompt injection","An attack where text supplied by a user is read by a language model as instructions rather than as data, steering it past its own rules. The LLM-era sibling of SQL injection, and it appears wherever user input is concatenated into a prompt.",[3080],"injection attack",[3082],{"kind":2684,"url":3083},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrompt_injection",[3085],"rate-limiting","\u002Fglossary#prompt-injection",{"slug":2796,"term":3088,"definition":3089,"category":2708,"aliases":3090,"links":3093,"related":3096,"readMore":-1,"target":3097,"hasArticle":2661},"Pub\u002FSub","A messaging pattern where a producer publishes an event and any number of consumers read it independently, with a broker in between. The producer never waits for them, which is how a request path stays fast while slower work happens behind it.",[3091,3092],"publish\u002Fsubscribe","Google Cloud Pub\u002FSub",[3094],{"kind":2684,"url":3095},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPublish%E2%80%93subscribe_pattern",[],"\u002Fglossary#pub-sub",{"slug":2715,"term":3099,"definition":3100,"category":2708,"aliases":3101,"links":3104,"related":3107,"readMore":-1,"target":3108,"hasArticle":2661},"REST","The conventional style for HTTP APIs: a URL names a resource and the HTTP verb says what to do with it. The default way an app talks to a backend, and what most third-party integrations expect to find.",[3102,3103],"REST API","Representational State Transfer",[3105],{"kind":2684,"url":3106},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FREST",[2795,2797],"\u002Fglossary#rest",{"slug":3085,"term":3110,"definition":3111,"category":2694,"aliases":3112,"links":3115,"related":3118,"readMore":-1,"target":3119,"hasArticle":2661},"Rate limiting","A cap on how many requests one caller may make in a given window. It is what stops a single enthusiastic user, a scraper or a bot from spending a month of paid API budget in an afternoon, and it has to live on your side of the integration.",[3113,3114],"rate limit","throttling",[3116],{"kind":2684,"url":3117},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",[3076],"\u002Fglossary#rate-limiting",{"slug":2839,"term":3121,"definition":3122,"category":2694,"aliases":3123,"links":3126,"related":3129,"readMore":-1,"target":3130,"hasArticle":2661},"SOC 2","An external auditor report on how an organisation handles customer data — security, availability, confidentiality — rather than a certificate you buy. Enterprise buyers ask for it, and it constrains architecture long before the audit itself does.",[3124,3125],"SOC2","System and Organization Controls",[3127],{"kind":2684,"url":3128},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSystem_and_Organization_Controls",[2548,2827,2840],"\u002Fglossary#soc-2",{"slug":3132,"term":3133,"definition":3134,"category":2914,"aliases":3135,"links":3138,"related":3141,"readMore":-1,"target":3142,"hasArticle":2661},"saas","SaaS","Software sold as an ongoing subscription to a hosted product rather than as a one-off license the customer installs and runs. The vendor operates the servers, ships updates continuously, and bills per seat or per usage.",[3136,3137],"Software as a Service","software-as-a-service",[3139],{"kind":2684,"url":3140},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSoftware_as_a_service",[2811,2810],"\u002Fglossary#saas",{"slug":2797,"term":3144,"definition":3145,"category":2937,"aliases":3146,"links":3148,"related":3151,"readMore":-1,"target":3152,"hasArticle":2661},"Server-Sent Events","A one-way stream from server to client over an ordinary HTTP connection. Simpler than a WebSocket and enough wherever only the server has something to say — a progress feed, an AI response arriving token by token.",[3147],"SSE",[3149],{"kind":2684,"url":3150},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-sent_events",[2795,2715],"\u002Fglossary#server-sent-events",{"slug":2887,"term":3154,"definition":3155,"category":2708,"aliases":3156,"links":3159,"related":3162,"readMore":-1,"target":3163,"hasArticle":2661},"Server-side rendering","Building a page as finished HTML on the server, so the first response already carries the content, headings, meta tags and structured data. Crawlers, link previews and slow devices read it without running JavaScript.",[3157,3158],"SSR","server-rendered",[3160],{"kind":2684,"url":3161},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-side_scripting",[2877],"\u002Fglossary#server-side-rendering",{"slug":2908,"term":3165,"definition":3166,"category":2652,"aliases":3167,"links":3170,"related":3175,"readMore":-1,"target":3176,"hasArticle":2661},"Skia","The open-source 2D graphics library from Google that draws Chrome, Android and — until Impeller — every Flutter frame. It renders to PDF as well as to a screen, which is what print-to-PDF in Chrome is doing.",[3168,3169],"Skia Graphics Engine","skia-safe",[3171,3173],{"kind":2657,"url":3172},"https:\u002F\u002Fskia.org\u002F",{"kind":2684,"url":3174},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSkia_Graphics_Engine",[2900],"\u002Fglossary#skia",{"slug":3178,"term":3179,"definition":3180,"category":2914,"aliases":3181,"links":3185,"related":3186,"readMore":-1,"target":3188,"hasArticle":2661},"staff-augmentation","Staff augmentation","A hiring model where engineers from an outside partner join your team and work under your management — in your repository, your sprints, your process — instead of delivering a project of their own. You buy capacity; the code and the context stay with you.",[3182,3183,3184],"team augmentation","dedicated developers","outstaffing",[],[3187,3073],"total-cost-of-ownership","\u002Fglossary#staff-augmentation",{"slug":1372,"term":3190,"definition":3191,"category":2694,"aliases":3192,"links":3195,"related":3196,"readMore":-1,"target":3197,"hasArticle":2661},"Staged rollout","Releasing a build to a small percentage of users first and widening only once the crash-free rate holds. A bad build caught at ten percent is a bad afternoon; the same build at a hundred percent is a bad week.",[3193,3194],"phased release","canary release",[],[1442,2691],"\u002Fglossary#staged-rollout",{"slug":3199,"term":2156,"definition":3200,"category":2708,"aliases":3201,"links":3205,"related":3208,"readMore":-1,"target":3209,"hasArticle":2661},"state-management","How an app decides where a value lives, who is allowed to change it, and which parts of the screen redraw when it does. In Flutter the choice between Riverpod, BLoC and Provider is among the first architectural decisions and the hardest to revisit.",[3202,3203,3204],"state management","BLoC","Riverpod",[3206],{"kind":2669,"url":3207},"https:\u002F\u002Fdocs.flutter.dev\u002Fdata-and-backend\u002Fstate-mgmt\u002Foptions",[2843],"\u002Fglossary#state-management",{"slug":278,"term":3211,"definition":3212,"category":2937,"aliases":3213,"links":3217,"related":3220,"readMore":-1,"target":3221,"hasArticle":2661},"TLS","The encryption layer underneath HTTPS. It proves the server is who its certificate says, agrees a fresh key for the session, and encrypts everything after that — so the network in between sees ciphertext it cannot quietly alter.",[3214,3215,3216],"SSL","HTTPS","Transport Layer Security",[3218],{"kind":2684,"url":3219},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTransport_Layer_Security",[2774,2784],"\u002Fglossary#tls",{"slug":3073,"term":3223,"definition":3224,"category":2914,"aliases":3225,"links":3227,"related":3230,"readMore":-1,"target":3231,"hasArticle":2661},"Time to market","How long it takes to get a product from decision to real users. Most stack and scope arguments are really arguments about this number, because every week saved is a week of revenue, feedback and competitive position.",[3226,3073],"TTM",[3228],{"kind":2684,"url":3229},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTime_to_market",[2968,3187],"\u002Fglossary#time-to-market",{"slug":3187,"term":3233,"definition":3234,"category":2914,"aliases":3235,"links":3238,"related":3241,"readMore":-1,"target":3242,"hasArticle":2661},"Total cost of ownership","What a product costs across its whole life rather than to build once: maintenance, upgrades, annual OS and store migrations, and the second team you staff to keep two codebases in step. Usually larger than the build quote, and almost never inside it.",[3236,3237],"TCO","cost of ownership",[3239],{"kind":2684,"url":3240},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTotal_cost_of_ownership",[3073],"\u002Fglossary#total-cost-of-ownership",{"slug":3244,"term":2164,"definition":3245,"category":2937,"aliases":3246,"links":3248,"related":3253,"readMore":-1,"target":3255,"hasArticle":2661},"webrtc","The browser and mobile standard for sending audio, video and data directly between two devices, with servers involved only in introducing them to each other. It is what an in-app video call is built on when it is not a rented SDK.",[3247],"Web Real-Time Communication",[3249,3251],{"kind":2657,"url":3250},"https:\u002F\u002Fwebrtc.org\u002F",{"kind":2684,"url":3252},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebRTC",[3254,2795],"xmpp","\u002Fglossary#webrtc",{"slug":2795,"term":3257,"definition":3258,"category":2937,"aliases":3259,"links":3260,"related":3263,"readMore":-1,"target":3264,"hasArticle":2661},"WebSocket","A protocol that holds one connection open between client and server so either side can send at any moment, instead of the client asking over and over. What live prices, chat and presence indicators run on.",[],[3261],{"kind":2684,"url":3262},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebSocket",[2797,2715],"\u002Fglossary#websocket",{"slug":2810,"term":3266,"definition":3267,"category":2914,"aliases":3268,"links":3271,"related":3274,"readMore":3275,"target":3275,"hasArticle":2135},"White-label","One product shipped under many brands. A white-label mobile platform builds each client a store-ready app with its own name, design and content from a single shared codebase, instead of forking the project per customer.",[3269,3270],"white label","multi-tenant app",[3272],{"kind":2684,"url":3273},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhite-label_product",[2968],"\u002Fglossary\u002Fwhite-label",{"slug":3254,"term":3277,"definition":3278,"category":2937,"aliases":3279,"links":3282,"related":3287,"readMore":-1,"target":3288,"hasArticle":2661},"XMPP","An open, federated messaging protocol, and the long-standing alternative to writing a chat backend or renting one. It extends to presence, typing indicators and file transfer, and it is old enough that every platform has a mature client library.",[3280,3281],"Jabber","Extensible Messaging and Presence Protocol",[3283,3285],{"kind":2657,"url":3284},"https:\u002F\u002Fxmpp.org\u002F",{"kind":2684,"url":3286},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FXMPP",[3244,2795],"\u002Fglossary#xmpp"]