[{"data":1,"prerenderedAt":1828},["ShallowReactive",2],{"header-latest":3,"sitemap-content":1372},[4],{"id":5,"title":6,"body":7,"date":1346,"description":1347,"extension":1348,"faq":1349,"image":1359,"lang":1360,"meta":1361,"navigation":108,"path":1362,"published":108,"readTime":215,"seo":1363,"stem":1364,"tags":1365,"updated":1346,"__hash__":1371},"blog\u002Fblog\u002Fen\u002Ftelegram-web-apps-sells.md","Telegram Web Apps (TWA): How to Launch a Full SaaS Inside Telegram",{"type":8,"value":9,"toc":1335},"minimark",[10,19,22,25,30,33,44,52,63,70,73,856,860,863,868,875,923,927,930,1215,1219,1229,1232,1270,1283,1291,1295,1315,1318,1331],[11,12,13,14,18],"p",{},"Telegram has transcended its origins as a privacy-focused messaging client to become a powerful, cross-platform runtime environment. The introduction of ",[15,16,17],"strong",{},"Telegram Web Apps (TWA)",", officially known as Telegram Mini Apps, allows developers to build highly interactive single-page applications (SPAs) that load directly inside the Telegram application shell.",[11,20,21],{},"This provides startups and enterprises with immediate, frictionless access to over 900 million active users. By embedding your B2B SaaS, CRM interface, or utility tool inside a Telegram bot, you remove the classic conversion hurdles of app store downloads, desktop logins, and sign-up flows.",[11,23,24],{},"In this developer guide, we will analyze the technical architecture of a TWA, implement a secure hash verification check on the backend, and look at frontend integration patterns.",[26,27,29],"h2",{"id":28},"the-telegram-mini-app-ecosystem-architecture","The Telegram Mini App Ecosystem Architecture",[11,31,32],{},"Unlike traditional web applications, a TWA has a dual parent relationship. The frontend runs in a sandboxed browser component (WebView) controlled by the Telegram client app, while the backend speaks both to the Telegram Bot API and your application database.",[34,35,41],"pre",{"className":36,"code":38,"language":39,"meta":40},[37],"language-text","+-----------------------------------------------------------+\n|                      Telegram Client                      |\n|  +-----------------------------------------------------+  |\n|  |                Mini App (WebView UI)                |  |\n|  |     (Uses window.Telegram.WebApp SDK for bridge)    |  |\n|  +--------------------------+--------------------------+  |\n+-----------------------------|-----------------------------+\n       | (initData Query)     | (Secure HTTPS Request)\n       v                      v\n+--------------+       +------------------------------------+\n| Telegram API |       |            Your Backend            |\n|  (Webhooks)  |       |  (Validates initData via HMAC-256) |\n+------+-------+       +-----------------+------------------+\n       |                                 |\n       +----------------> [DB Sync] \u003C----+\n","text","",[42,43,38],"code",{"__ignoreMap":40},[26,45,47,48,51],{"id":46},"_1-security-first-validating-the-initdata-payload","1. Security First: Validating the ",[42,49,50],{},"initData"," Payload",[11,53,54,55,58,59,62],{},"When a user opens your Mini App, Telegram appends a parameter called ",[42,56,57],{},"tgWebAppData"," (or raw search parameters) containing user profiles, launch contexts, and a security ",[42,60,61],{},"hash",".",[11,64,65,66,69],{},"To prevent users from modifying their user IDs or mocking paid subscription privileges, you ",[15,67,68],{},"must validate this hash on your backend"," using your Telegram Bot Token as the HMAC key.",[11,71,72],{},"Here is the cryptographic validation implementation using Node.js:",[34,74,78],{"className":75,"code":76,"language":77,"meta":40,"style":40},"language-typescript shiki shiki-themes github-dark","import crypto from 'crypto';\n\ninterface TelegramUserData {\n  id: number;\n  first_name: string;\n  last_name?: string;\n  username?: string;\n  language_code?: string;\n  is_premium?: boolean;\n}\n\ninterface ValidationResult {\n  isValid: boolean;\n  user?: TelegramUserData;\n}\n\nexport function verifyTelegramInitData(rawQueryString: string, botToken: string): ValidationResult {\n  const urlParams = new URLSearchParams(rawQueryString);\n  const hash = urlParams.get('hash');\n  \n  if (!hash) {\n    return { isValid: false };\n  }\n\n  \u002F\u002F 1. Sort all incoming parameters alphabetically, excluding the hash itself\n  const keys = Array.from(urlParams.keys()).filter(key => key !== 'hash').sort();\n  \n  \u002F\u002F 2. Re-create the verification data-check string\n  const dataCheckString = keys\n    .map(key => `${key}=${urlParams.get(key)}`)\n    .join('\\n');\n\n  \u002F\u002F 3. Generate the secret cryptographic key\n  \u002F\u002F We use the constant string \"WebAppData\" to sign the bot token first\n  const secretKey = crypto\n    .createHmac('sha256', 'WebAppData')\n    .update(botToken)\n    .digest();\n\n  \u002F\u002F 4. Calculate the expected hash of the sorted string\n  const computedHash = crypto\n    .createHmac('sha256', secretKey)\n    .update(dataCheckString)\n    .digest('hex');\n\n  \u002F\u002F 5. Compare computed signature with the signature sent by the client\n  const isValid = computedHash === hash;\n\n  if (!isValid) {\n    return { isValid: false };\n  }\n\n  \u002F\u002F Parse user data object if validation succeeded\n  try {\n    const userRaw = urlParams.get('user');\n    const user: TelegramUserData = userRaw ? JSON.parse(userRaw) : undefined;\n    return { isValid: true, user };\n  } catch (error) {\n    return { isValid: true };\n  }\n}\n","typescript",[42,79,80,103,110,123,139,152,165,177,189,202,208,213,223,235,247,252,257,298,319,343,349,364,379,385,390,397,450,455,461,474,516,536,541,547,553,566,586,597,607,612,618,630,644,654,668,673,679,698,703,715,726,731,736,742,750,772,810,823,835,846,851],{"__ignoreMap":40},[81,82,85,89,93,96,100],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"snl16","import",[81,90,92],{"class":91},"s95oV"," crypto ",[81,94,95],{"class":87},"from",[81,97,99],{"class":98},"sU2Wk"," 'crypto'",[81,101,102],{"class":91},";\n",[81,104,106],{"class":83,"line":105},2,[81,107,109],{"emptyLinePlaceholder":108},true,"\n",[81,111,113,116,120],{"class":83,"line":112},3,[81,114,115],{"class":87},"interface",[81,117,119],{"class":118},"svObZ"," TelegramUserData",[81,121,122],{"class":91}," {\n",[81,124,126,130,133,137],{"class":83,"line":125},4,[81,127,129],{"class":128},"s9osk","  id",[81,131,132],{"class":87},":",[81,134,136],{"class":135},"sDLfK"," number",[81,138,102],{"class":91},[81,140,142,145,147,150],{"class":83,"line":141},5,[81,143,144],{"class":128},"  first_name",[81,146,132],{"class":87},[81,148,149],{"class":135}," string",[81,151,102],{"class":91},[81,153,155,158,161,163],{"class":83,"line":154},6,[81,156,157],{"class":128},"  last_name",[81,159,160],{"class":87},"?:",[81,162,149],{"class":135},[81,164,102],{"class":91},[81,166,168,171,173,175],{"class":83,"line":167},7,[81,169,170],{"class":128},"  username",[81,172,160],{"class":87},[81,174,149],{"class":135},[81,176,102],{"class":91},[81,178,180,183,185,187],{"class":83,"line":179},8,[81,181,182],{"class":128},"  language_code",[81,184,160],{"class":87},[81,186,149],{"class":135},[81,188,102],{"class":91},[81,190,192,195,197,200],{"class":83,"line":191},9,[81,193,194],{"class":128},"  is_premium",[81,196,160],{"class":87},[81,198,199],{"class":135}," boolean",[81,201,102],{"class":91},[81,203,205],{"class":83,"line":204},10,[81,206,207],{"class":91},"}\n",[81,209,211],{"class":83,"line":210},11,[81,212,109],{"emptyLinePlaceholder":108},[81,214,216,218,221],{"class":83,"line":215},12,[81,217,115],{"class":87},[81,219,220],{"class":118}," ValidationResult",[81,222,122],{"class":91},[81,224,226,229,231,233],{"class":83,"line":225},13,[81,227,228],{"class":128},"  isValid",[81,230,132],{"class":87},[81,232,199],{"class":135},[81,234,102],{"class":91},[81,236,238,241,243,245],{"class":83,"line":237},14,[81,239,240],{"class":128},"  user",[81,242,160],{"class":87},[81,244,119],{"class":118},[81,246,102],{"class":91},[81,248,250],{"class":83,"line":249},15,[81,251,207],{"class":91},[81,253,255],{"class":83,"line":254},16,[81,256,109],{"emptyLinePlaceholder":108},[81,258,260,263,266,269,272,275,277,279,282,285,287,289,292,294,296],{"class":83,"line":259},17,[81,261,262],{"class":87},"export",[81,264,265],{"class":87}," function",[81,267,268],{"class":118}," verifyTelegramInitData",[81,270,271],{"class":91},"(",[81,273,274],{"class":128},"rawQueryString",[81,276,132],{"class":87},[81,278,149],{"class":135},[81,280,281],{"class":91},", ",[81,283,284],{"class":128},"botToken",[81,286,132],{"class":87},[81,288,149],{"class":135},[81,290,291],{"class":91},")",[81,293,132],{"class":87},[81,295,220],{"class":118},[81,297,122],{"class":91},[81,299,301,304,307,310,313,316],{"class":83,"line":300},18,[81,302,303],{"class":87},"  const",[81,305,306],{"class":135}," urlParams",[81,308,309],{"class":87}," =",[81,311,312],{"class":87}," new",[81,314,315],{"class":118}," URLSearchParams",[81,317,318],{"class":91},"(rawQueryString);\n",[81,320,322,324,327,329,332,335,337,340],{"class":83,"line":321},19,[81,323,303],{"class":87},[81,325,326],{"class":135}," hash",[81,328,309],{"class":87},[81,330,331],{"class":91}," urlParams.",[81,333,334],{"class":118},"get",[81,336,271],{"class":91},[81,338,339],{"class":98},"'hash'",[81,341,342],{"class":91},");\n",[81,344,346],{"class":83,"line":345},20,[81,347,348],{"class":91},"  \n",[81,350,352,355,358,361],{"class":83,"line":351},21,[81,353,354],{"class":87},"  if",[81,356,357],{"class":91}," (",[81,359,360],{"class":87},"!",[81,362,363],{"class":91},"hash) {\n",[81,365,367,370,373,376],{"class":83,"line":366},22,[81,368,369],{"class":87},"    return",[81,371,372],{"class":91}," { isValid: ",[81,374,375],{"class":135},"false",[81,377,378],{"class":91}," };\n",[81,380,382],{"class":83,"line":381},23,[81,383,384],{"class":91},"  }\n",[81,386,388],{"class":83,"line":387},24,[81,389,109],{"emptyLinePlaceholder":108},[81,391,393],{"class":83,"line":392},25,[81,394,396],{"class":395},"sAwPA","  \u002F\u002F 1. Sort all incoming parameters alphabetically, excluding the hash itself\n",[81,398,400,402,405,407,410,412,415,418,421,424,426,429,432,435,438,441,444,447],{"class":83,"line":399},26,[81,401,303],{"class":87},[81,403,404],{"class":135}," keys",[81,406,309],{"class":87},[81,408,409],{"class":91}," Array.",[81,411,95],{"class":118},[81,413,414],{"class":91},"(urlParams.",[81,416,417],{"class":118},"keys",[81,419,420],{"class":91},"()).",[81,422,423],{"class":118},"filter",[81,425,271],{"class":91},[81,427,428],{"class":128},"key",[81,430,431],{"class":87}," =>",[81,433,434],{"class":91}," key ",[81,436,437],{"class":87},"!==",[81,439,440],{"class":98}," 'hash'",[81,442,443],{"class":91},").",[81,445,446],{"class":118},"sort",[81,448,449],{"class":91},"();\n",[81,451,453],{"class":83,"line":452},27,[81,454,348],{"class":91},[81,456,458],{"class":83,"line":457},28,[81,459,460],{"class":395},"  \u002F\u002F 2. Re-create the verification data-check string\n",[81,462,464,466,469,471],{"class":83,"line":463},29,[81,465,303],{"class":87},[81,467,468],{"class":135}," dataCheckString",[81,470,309],{"class":87},[81,472,473],{"class":91}," keys\n",[81,475,477,480,483,485,487,489,492,494,497,500,502,504,506,508,510,513],{"class":83,"line":476},30,[81,478,479],{"class":91},"    .",[81,481,482],{"class":118},"map",[81,484,271],{"class":91},[81,486,428],{"class":128},[81,488,431],{"class":87},[81,490,491],{"class":98}," `${",[81,493,428],{"class":91},[81,495,496],{"class":98},"}=${",[81,498,499],{"class":91},"urlParams",[81,501,62],{"class":98},[81,503,334],{"class":118},[81,505,271],{"class":98},[81,507,428],{"class":91},[81,509,291],{"class":98},[81,511,512],{"class":98},"}`",[81,514,515],{"class":91},")\n",[81,517,519,521,524,526,529,532,534],{"class":83,"line":518},31,[81,520,479],{"class":91},[81,522,523],{"class":118},"join",[81,525,271],{"class":91},[81,527,528],{"class":98},"'",[81,530,531],{"class":135},"\\n",[81,533,528],{"class":98},[81,535,342],{"class":91},[81,537,539],{"class":83,"line":538},32,[81,540,109],{"emptyLinePlaceholder":108},[81,542,544],{"class":83,"line":543},33,[81,545,546],{"class":395},"  \u002F\u002F 3. Generate the secret cryptographic key\n",[81,548,550],{"class":83,"line":549},34,[81,551,552],{"class":395},"  \u002F\u002F We use the constant string \"WebAppData\" to sign the bot token first\n",[81,554,556,558,561,563],{"class":83,"line":555},35,[81,557,303],{"class":87},[81,559,560],{"class":135}," secretKey",[81,562,309],{"class":87},[81,564,565],{"class":91}," crypto\n",[81,567,569,571,574,576,579,581,584],{"class":83,"line":568},36,[81,570,479],{"class":91},[81,572,573],{"class":118},"createHmac",[81,575,271],{"class":91},[81,577,578],{"class":98},"'sha256'",[81,580,281],{"class":91},[81,582,583],{"class":98},"'WebAppData'",[81,585,515],{"class":91},[81,587,589,591,594],{"class":83,"line":588},37,[81,590,479],{"class":91},[81,592,593],{"class":118},"update",[81,595,596],{"class":91},"(botToken)\n",[81,598,600,602,605],{"class":83,"line":599},38,[81,601,479],{"class":91},[81,603,604],{"class":118},"digest",[81,606,449],{"class":91},[81,608,610],{"class":83,"line":609},39,[81,611,109],{"emptyLinePlaceholder":108},[81,613,615],{"class":83,"line":614},40,[81,616,617],{"class":395},"  \u002F\u002F 4. Calculate the expected hash of the sorted string\n",[81,619,621,623,626,628],{"class":83,"line":620},41,[81,622,303],{"class":87},[81,624,625],{"class":135}," computedHash",[81,627,309],{"class":87},[81,629,565],{"class":91},[81,631,633,635,637,639,641],{"class":83,"line":632},42,[81,634,479],{"class":91},[81,636,573],{"class":118},[81,638,271],{"class":91},[81,640,578],{"class":98},[81,642,643],{"class":91},", secretKey)\n",[81,645,647,649,651],{"class":83,"line":646},43,[81,648,479],{"class":91},[81,650,593],{"class":118},[81,652,653],{"class":91},"(dataCheckString)\n",[81,655,657,659,661,663,666],{"class":83,"line":656},44,[81,658,479],{"class":91},[81,660,604],{"class":118},[81,662,271],{"class":91},[81,664,665],{"class":98},"'hex'",[81,667,342],{"class":91},[81,669,671],{"class":83,"line":670},45,[81,672,109],{"emptyLinePlaceholder":108},[81,674,676],{"class":83,"line":675},46,[81,677,678],{"class":395},"  \u002F\u002F 5. Compare computed signature with the signature sent by the client\n",[81,680,682,684,687,689,692,695],{"class":83,"line":681},47,[81,683,303],{"class":87},[81,685,686],{"class":135}," isValid",[81,688,309],{"class":87},[81,690,691],{"class":91}," computedHash ",[81,693,694],{"class":87},"===",[81,696,697],{"class":91}," hash;\n",[81,699,701],{"class":83,"line":700},48,[81,702,109],{"emptyLinePlaceholder":108},[81,704,706,708,710,712],{"class":83,"line":705},49,[81,707,354],{"class":87},[81,709,357],{"class":91},[81,711,360],{"class":87},[81,713,714],{"class":91},"isValid) {\n",[81,716,718,720,722,724],{"class":83,"line":717},50,[81,719,369],{"class":87},[81,721,372],{"class":91},[81,723,375],{"class":135},[81,725,378],{"class":91},[81,727,729],{"class":83,"line":728},51,[81,730,384],{"class":91},[81,732,734],{"class":83,"line":733},52,[81,735,109],{"emptyLinePlaceholder":108},[81,737,739],{"class":83,"line":738},53,[81,740,741],{"class":395},"  \u002F\u002F Parse user data object if validation succeeded\n",[81,743,745,748],{"class":83,"line":744},54,[81,746,747],{"class":87},"  try",[81,749,122],{"class":91},[81,751,753,756,759,761,763,765,767,770],{"class":83,"line":752},55,[81,754,755],{"class":87},"    const",[81,757,758],{"class":135}," userRaw",[81,760,309],{"class":87},[81,762,331],{"class":91},[81,764,334],{"class":118},[81,766,271],{"class":91},[81,768,769],{"class":98},"'user'",[81,771,342],{"class":91},[81,773,775,777,780,782,784,786,789,792,795,797,800,803,805,808],{"class":83,"line":774},56,[81,776,755],{"class":87},[81,778,779],{"class":135}," user",[81,781,132],{"class":87},[81,783,119],{"class":118},[81,785,309],{"class":87},[81,787,788],{"class":91}," userRaw ",[81,790,791],{"class":87},"?",[81,793,794],{"class":135}," JSON",[81,796,62],{"class":91},[81,798,799],{"class":118},"parse",[81,801,802],{"class":91},"(userRaw) ",[81,804,132],{"class":87},[81,806,807],{"class":135}," undefined",[81,809,102],{"class":91},[81,811,813,815,817,820],{"class":83,"line":812},57,[81,814,369],{"class":87},[81,816,372],{"class":91},[81,818,819],{"class":135},"true",[81,821,822],{"class":91},", user };\n",[81,824,826,829,832],{"class":83,"line":825},58,[81,827,828],{"class":91},"  } ",[81,830,831],{"class":87},"catch",[81,833,834],{"class":91}," (error) {\n",[81,836,838,840,842,844],{"class":83,"line":837},59,[81,839,369],{"class":87},[81,841,372],{"class":91},[81,843,819],{"class":135},[81,845,378],{"class":91},[81,847,849],{"class":83,"line":848},60,[81,850,384],{"class":91},[81,852,854],{"class":83,"line":853},61,[81,855,207],{"class":91},[26,857,859],{"id":858},"_2-frontend-integration-theme-synchronization","2. Frontend Integration & Theme Synchronization",[11,861,862],{},"To deliver a premium UI\u002FUX, your Mini App should visually blend with the Telegram client’s dark\u002Flight settings. You can access the styles and control client-side behaviors using the official Telegram WebApp JS library.",[864,865,867],"h3",{"id":866},"step-1-include-the-script-in-nuxt-3-html","Step 1: Include the Script in Nuxt 3 \u002F HTML",[11,869,870,871,874],{},"Add the official script to your page header or use the ",[42,872,873],{},"useHead"," composable in Nuxt:",[34,876,878],{"className":75,"code":877,"language":77,"meta":40,"style":40},"\u002F\u002F app.vue or page layout\nuseHead({\n  script: [\n    { src: 'https:\u002F\u002Ftelegram.org\u002Fjs\u002Ftelegram-web-app.js', defer: true }\n  ]\n})\n",[42,879,880,885,892,897,913,918],{"__ignoreMap":40},[81,881,882],{"class":83,"line":84},[81,883,884],{"class":395},"\u002F\u002F app.vue or page layout\n",[81,886,887,889],{"class":83,"line":105},[81,888,873],{"class":118},[81,890,891],{"class":91},"({\n",[81,893,894],{"class":83,"line":112},[81,895,896],{"class":91},"  script: [\n",[81,898,899,902,905,908,910],{"class":83,"line":125},[81,900,901],{"class":91},"    { src: ",[81,903,904],{"class":98},"'https:\u002F\u002Ftelegram.org\u002Fjs\u002Ftelegram-web-app.js'",[81,906,907],{"class":91},", defer: ",[81,909,819],{"class":135},[81,911,912],{"class":91}," }\n",[81,914,915],{"class":83,"line":141},[81,916,917],{"class":91},"  ]\n",[81,919,920],{"class":83,"line":154},[81,921,922],{"class":91},"})\n",[864,924,926],{"id":925},"step-2-access-the-webapp-bridge-in-vue","Step 2: Access the WebApp Bridge in Vue",[11,928,929],{},"Create a Vue composable to access and synchronize Telegram styles:",[34,931,933],{"className":75,"code":932,"language":77,"meta":40,"style":40},"\u002F\u002F composables\u002FuseTelegram.ts\nimport { ref, onMounted } from 'vue';\n\nexport function useTelegram() {\n  const isReady = ref(false);\n  const user = ref\u003Cany>(null);\n\n  onMounted(() => {\n    const tg = (window as any).Telegram?.WebApp;\n    if (tg) {\n      tg.ready();\n      tg.expand(); \u002F\u002F Request the container to fill maximum vertical space\n      \n      user.value = tg.initDataUnsafe?.user;\n      isReady.value = true;\n\n      \u002F\u002F Apply Telegram theme colors to CSS custom properties\n      const root = document.documentElement;\n      root.style.setProperty('--color-tg-bg', tg.themeParams.bg_color);\n      root.style.setProperty('--color-tg-text', tg.themeParams.text_color);\n      root.style.setProperty('--color-tg-button', tg.themeParams.button_color);\n      root.style.setProperty('--color-tg-button-text', tg.themeParams.button_text_color);\n    }\n  });\n\n  return { isReady, user };\n}\n",[42,934,935,940,954,958,970,988,1012,1016,1029,1050,1058,1068,1081,1086,1097,1109,1113,1118,1131,1147,1161,1175,1189,1194,1199,1203,1211],{"__ignoreMap":40},[81,936,937],{"class":83,"line":84},[81,938,939],{"class":395},"\u002F\u002F composables\u002FuseTelegram.ts\n",[81,941,942,944,947,949,952],{"class":83,"line":105},[81,943,88],{"class":87},[81,945,946],{"class":91}," { ref, onMounted } ",[81,948,95],{"class":87},[81,950,951],{"class":98}," 'vue'",[81,953,102],{"class":91},[81,955,956],{"class":83,"line":112},[81,957,109],{"emptyLinePlaceholder":108},[81,959,960,962,964,967],{"class":83,"line":125},[81,961,262],{"class":87},[81,963,265],{"class":87},[81,965,966],{"class":118}," useTelegram",[81,968,969],{"class":91},"() {\n",[81,971,972,974,977,979,982,984,986],{"class":83,"line":141},[81,973,303],{"class":87},[81,975,976],{"class":135}," isReady",[81,978,309],{"class":87},[81,980,981],{"class":118}," ref",[81,983,271],{"class":91},[81,985,375],{"class":135},[81,987,342],{"class":91},[81,989,990,992,994,996,998,1001,1004,1007,1010],{"class":83,"line":154},[81,991,303],{"class":87},[81,993,779],{"class":135},[81,995,309],{"class":87},[81,997,981],{"class":118},[81,999,1000],{"class":91},"\u003C",[81,1002,1003],{"class":135},"any",[81,1005,1006],{"class":91},">(",[81,1008,1009],{"class":135},"null",[81,1011,342],{"class":91},[81,1013,1014],{"class":83,"line":167},[81,1015,109],{"emptyLinePlaceholder":108},[81,1017,1018,1021,1024,1027],{"class":83,"line":179},[81,1019,1020],{"class":118},"  onMounted",[81,1022,1023],{"class":91},"(() ",[81,1025,1026],{"class":87},"=>",[81,1028,122],{"class":91},[81,1030,1031,1033,1036,1038,1041,1044,1047],{"class":83,"line":191},[81,1032,755],{"class":87},[81,1034,1035],{"class":135}," tg",[81,1037,309],{"class":87},[81,1039,1040],{"class":91}," (window ",[81,1042,1043],{"class":87},"as",[81,1045,1046],{"class":135}," any",[81,1048,1049],{"class":91},").Telegram?.WebApp;\n",[81,1051,1052,1055],{"class":83,"line":204},[81,1053,1054],{"class":87},"    if",[81,1056,1057],{"class":91}," (tg) {\n",[81,1059,1060,1063,1066],{"class":83,"line":210},[81,1061,1062],{"class":91},"      tg.",[81,1064,1065],{"class":118},"ready",[81,1067,449],{"class":91},[81,1069,1070,1072,1075,1078],{"class":83,"line":215},[81,1071,1062],{"class":91},[81,1073,1074],{"class":118},"expand",[81,1076,1077],{"class":91},"(); ",[81,1079,1080],{"class":395},"\u002F\u002F Request the container to fill maximum vertical space\n",[81,1082,1083],{"class":83,"line":225},[81,1084,1085],{"class":91},"      \n",[81,1087,1088,1091,1094],{"class":83,"line":237},[81,1089,1090],{"class":91},"      user.value ",[81,1092,1093],{"class":87},"=",[81,1095,1096],{"class":91}," tg.initDataUnsafe?.user;\n",[81,1098,1099,1102,1104,1107],{"class":83,"line":249},[81,1100,1101],{"class":91},"      isReady.value ",[81,1103,1093],{"class":87},[81,1105,1106],{"class":135}," true",[81,1108,102],{"class":91},[81,1110,1111],{"class":83,"line":254},[81,1112,109],{"emptyLinePlaceholder":108},[81,1114,1115],{"class":83,"line":259},[81,1116,1117],{"class":395},"      \u002F\u002F Apply Telegram theme colors to CSS custom properties\n",[81,1119,1120,1123,1126,1128],{"class":83,"line":300},[81,1121,1122],{"class":87},"      const",[81,1124,1125],{"class":135}," root",[81,1127,309],{"class":87},[81,1129,1130],{"class":91}," document.documentElement;\n",[81,1132,1133,1136,1139,1141,1144],{"class":83,"line":321},[81,1134,1135],{"class":91},"      root.style.",[81,1137,1138],{"class":118},"setProperty",[81,1140,271],{"class":91},[81,1142,1143],{"class":98},"'--color-tg-bg'",[81,1145,1146],{"class":91},", tg.themeParams.bg_color);\n",[81,1148,1149,1151,1153,1155,1158],{"class":83,"line":345},[81,1150,1135],{"class":91},[81,1152,1138],{"class":118},[81,1154,271],{"class":91},[81,1156,1157],{"class":98},"'--color-tg-text'",[81,1159,1160],{"class":91},", tg.themeParams.text_color);\n",[81,1162,1163,1165,1167,1169,1172],{"class":83,"line":351},[81,1164,1135],{"class":91},[81,1166,1138],{"class":118},[81,1168,271],{"class":91},[81,1170,1171],{"class":98},"'--color-tg-button'",[81,1173,1174],{"class":91},", tg.themeParams.button_color);\n",[81,1176,1177,1179,1181,1183,1186],{"class":83,"line":366},[81,1178,1135],{"class":91},[81,1180,1138],{"class":118},[81,1182,271],{"class":91},[81,1184,1185],{"class":98},"'--color-tg-button-text'",[81,1187,1188],{"class":91},", tg.themeParams.button_text_color);\n",[81,1190,1191],{"class":83,"line":381},[81,1192,1193],{"class":91},"    }\n",[81,1195,1196],{"class":83,"line":387},[81,1197,1198],{"class":91},"  });\n",[81,1200,1201],{"class":83,"line":392},[81,1202,109],{"emptyLinePlaceholder":108},[81,1204,1205,1208],{"class":83,"line":399},[81,1206,1207],{"class":87},"  return",[81,1209,1210],{"class":91}," { isReady, user };\n",[81,1212,1213],{"class":83,"line":452},[81,1214,207],{"class":91},[26,1216,1218],{"id":1217},"_3-monetization-payments-via-telegram-stars","3. Monetization: Payments via Telegram Stars",[11,1220,1221,1222,357,1225,1228],{},"When operating inside Telegram, all digital services or content purchases must comply with Apple App Store and Google Play policies. Telegram enforces this by requiring the use of ",[15,1223,1224],{},"Telegram Stars",[42,1226,1227],{},"XTR",") for digital goods.",[11,1230,1231],{},"The flow for receiving Stars payments:",[1233,1234,1235,1242,1254,1260],"ol",{},[1236,1237,1238,1241],"li",{},[15,1239,1240],{},"Request Invoice",": The TWA requests the backend to generate an invoice.",[1236,1243,1244,1247,1248,1251,1252,62],{},[15,1245,1246],{},"Bot Sends Invoice",": The backend calls Bot API ",[42,1249,1250],{},"sendInvoice"," using the currency ",[42,1253,1227],{},[1236,1255,1256,1259],{},[15,1257,1258],{},"Client Checkout",": The Telegram client opens a native overlay allowing the user to pay using Stars purchased in-app.",[1236,1261,1262,1265,1266,1269],{},[15,1263,1264],{},"Verification",": Telegram sends a webhook ",[42,1267,1268],{},"successful_payment"," to your bot, which credits the user's account in your DB.",[11,1271,1272,1273,1278,1279,62],{},"In my Telegram bot hosting platform, ",[1274,1275,1277],"a",{"href":1276},"\u002Fprojects\u002Ftelego","TeleGo.io",", we provide exactly these billing options, allowing users to spin up their own TWAs and receive Stars instantly. Additionally, we support hybrid human-AI helpdesks, which you can learn to build in my guide on ",[1274,1280,1282],{"href":1281},"\u002Fblog\u002Ftelegram-bot-ai-rag-support","RAG AI Customer Support Bots",[11,1284,1285,1286,1290],{},"For physical goods and consulting services, traditional gateways can be used. Read my ",[1274,1287,1289],{"href":1288},"\u002Fblog\u002Fsaas-stripe-billing-integration","SaaS Stripe Billing Integration guide"," for Express\u002FTypeScript templates.",[26,1292,1294],{"id":1293},"sources-and-documentation","Sources and documentation",[1296,1297,1298,1307],"ul",{},[1236,1299,1300,1306],{},[1274,1301,1305],{"href":1302,"rel":1303},"https:\u002F\u002Fcore.telegram.org\u002Fbots\u002Fwebapps",[1304],"nofollow","Telegram Mini Apps"," — the official WebApp SDK and initData documentation",[1236,1308,1309,1314],{},[1274,1310,1313],{"href":1311,"rel":1312},"https:\u002F\u002Fcore.telegram.org\u002Fbots\u002Fpayments",[1304],"Bot Payments API"," — invoices, Telegram Stars, and payment webhooks",[11,1316,1317],{},"By combining native web frameworks with the Telegram WebApp API, developers can ship complex SaaS platforms and digital products directly into active messaging channels.",[11,1319,1320,1321,1325,1326,1330],{},"If you are planning to build a high-performance Telegram Mini App with secure billing integrations and dynamic frontend design, check out my ",[1274,1322,1324],{"href":1323},"\u002Ftelegram-bots","Telegram Bot Development Service"," or request a ",[1274,1327,1329],{"href":1328},"\u002Fconsultations","Technical Architecture Consultation"," to get a production blueprint.",[1332,1333,1334],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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);}",{"title":40,"searchDepth":105,"depth":105,"links":1336},[1337,1338,1340,1344,1345],{"id":28,"depth":105,"text":29},{"id":46,"depth":105,"text":1339},"1. Security First: Validating the initData Payload",{"id":858,"depth":105,"text":859,"children":1341},[1342,1343],{"id":866,"depth":112,"text":867},{"id":925,"depth":112,"text":926},{"id":1217,"depth":105,"text":1218},{"id":1293,"depth":105,"text":1294},"2026-07-05","A comprehensive developer's guide to using Telegram Mini Apps as a dynamic web frontend, implementing secure hash validation, and processing in-app purchases.","md",[1350,1353,1356],{"q":1351,"a":1352},"How is a Telegram Mini App different from a regular bot?","A bot communicates through messages and buttons, while a Mini App opens a full web interface inside Telegram: dashboards, catalogs, forms, charts. It's an SPA running in a WebView with its own frontend, and authorization happens automatically through the Telegram account.",{"q":1354,"a":1355},"How do I verify that user data in a Mini App isn't forged?","Through cryptographic initData validation: Telegram signs the user data with an HMAC-SHA256 signature derived from the bot token. The backend must recompute and compare the signature on every request — without it, anyone can impersonate any user.",{"q":1357,"a":1358},"When are Telegram Stars mandatory, and when can I use Stripe?","Digital goods and subscriptions inside a Mini App must be paid with Telegram Stars per Apple and Google policies. Physical goods, services, and consulting can be sold through classic gateways like Stripe.","\u002Fimages\u002Fblog\u002Fblog_telegram_twa.jpg","en",{},"\u002Fblog\u002Fen\u002Ftelegram-web-apps-sells",{"title":6,"description":1347},"blog\u002Fen\u002Ftelegram-web-apps-sells",[1366,1367,1368,1369,1370],"Telegram","Mini Apps","SaaS","Node.js","Frontend","4blcVbDR853MsonsChj72pfs0C4mqm-aL83XkBOZ0io",{"blog":1373,"snippetGroups":1416},[1374,1376,1379,1382,1385,1388,1391,1393,1396,1399,1401,1404,1407,1410,1413],{"title":6,"url":1375},"\u002Fblog\u002Ftelegram-web-apps-sells",{"title":1377,"url":1378},"Generative Engine Optimization (GEO): The New SEO for AI Search","\u002Fblog\u002Fgenerative-engine-optimization-geo",{"title":1380,"url":1381},"Optimizing Your Website for AI Crawlers: Robots.txt & JSON-LD","\u002Fblog\u002Fwebsite-optimization-ai-crawlers",{"title":1383,"url":1384},"How Much Does SaaS MVP Development Cost in 2026: A Stage-by-Stage Estimate","\u002Fblog\u002Fsaas-mvp-development-cost",{"title":1386,"url":1387},"How to Build a B2B SaaS MVP in 30 Days Without Technical Debt","\u002Fblog\u002Fb2b-saas-mvp-30-days",{"title":1389,"url":1390},"The Trap of Cheap Development: When to Rewrite Your SaaS MVP","\u002Fblog\u002Fsaas-development-cheap-code-trap",{"title":1392,"url":1288},"SaaS Monetization: Implementing Stripe, Billing, and Subscriptions Without Pain",{"title":1394,"url":1395},"SaaS Database Architecture: Single-Tenant vs. Multi-Tenant Patterns","\u002Fblog\u002Fsaas-multitenancy-databases",{"title":1397,"url":1398},"Codebase Audit Checklist: How to Assess Technical Debt Before Scaling","\u002Fblog\u002Fcode-audit-technical-debt",{"title":1400,"url":1281},"AI-Powered Customer Support: Building a RAG Bot with Live Agent Handover",{"title":1402,"url":1403},"Bot Builder vs Custom Development: How to Choose in 2026","\u002Fblog\u002Fbot-builder-vs-custom-development",{"title":1405,"url":1406},"How Much Does Telegram Bot Development Cost in 2026: A Real Price Breakdown","\u002Fblog\u002Ftelegram-bot-development-cost",{"title":1408,"url":1409},"What a Telegram Bot Specification Should Include: A Checklist with Examples","\u002Fblog\u002Ftelegram-bot-specification-checklist",{"title":1411,"url":1412},"Why Your CRM Needs a Custom Telegram Bot (HubSpot & AmoCRM Integration)","\u002Fblog\u002Ftelegram-bot-crm-integration",{"title":1414,"url":1415},"EzSwitch — Quick Keyboard Layout Switcher for macOS","\u002Fblog\u002Fezswitch-macos-switcher",{"JavaScript":1417,"PHP":1520,"CSS":1574,"Express.js":1599,"Python":1618,"TypeScript":1634,"Node.js":1650,"Go":1669,"Next.js":1690,"Docker":1706,"Git":1716,"GitHub Actions":1729,"Nginx":1742,"Prisma":1755,"React.js":1768,"Tailwind CSS":1787,"Vue":1800},[1418,1421,1424,1427,1430,1433,1436,1439,1442,1445,1448,1451,1454,1457,1460,1463,1466,1469,1472,1475,1478,1481,1484,1487,1490,1493,1496,1499,1502,1505,1508,1511,1514,1517],{"title":1419,"url":1420},"A JSON.parse that never throws","\u002Fsnippets\u002Fjavascript-safe-json-parse",{"title":1422,"url":1423},"Answer Telegram Callback Query","\u002Fsnippets\u002Fjavascript-telegram-answer-callback-query",{"title":1425,"url":1426},"Auto-arrange Telegram inline keyboard buttons","\u002Fsnippets\u002Fjavascript-smart-telegram-button-layout",{"title":1428,"url":1429},"Capitalize the first letter of a string","\u002Fsnippets\u002Fjavascript-capitalize-first-letter",{"title":1431,"url":1432},"Check if a hex color is light or dark","\u002Fsnippets\u002Fjavascript-is-color-light",{"title":1434,"url":1435},"Check if an event target is an editable field","\u002Fsnippets\u002Fjavascript-is-editable-target",{"title":1437,"url":1438},"Convert Date to\u002Ffrom \"YYYY-MM-DD\" without timezone shifts","\u002Fsnippets\u002Fjavascript-iso-date-without-timezone-shift",{"title":1440,"url":1441},"Cosine similarity between two vectors","\u002Fsnippets\u002Fjavascript-cosine-similarity",{"title":1443,"url":1444},"Debounce a function","\u002Fsnippets\u002Fjavascript-debounce-function",{"title":1446,"url":1447},"Decode JWT Payload Without Verification","\u002Fsnippets\u002Fjavascript-jwt-decode-payload",{"title":1449,"url":1450},"Deep Clone an Object","\u002Fsnippets\u002Fjavascript-deep-clone-object",{"title":1452,"url":1453},"Detect the visitor's timezone automatically","\u002Fsnippets\u002Fjavascript-detect-visitor-timezone",{"title":1455,"url":1456},"Display a timezone with its GMT offset","\u002Fsnippets\u002Fjavascript-timezone-gmt-offset-label",{"title":1458,"url":1459},"Format a date\u002Ftime in any IANA timezone","\u002Fsnippets\u002Fjavascript-format-date-in-timezone",{"title":1461,"url":1462},"Format a number compactly (1.2K, 3M)","\u002Fsnippets\u002Fjavascript-format-compact-number",{"title":1464,"url":1465},"Generate Slug from String","\u002Fsnippets\u002Fjavascript-generate-slug-from-string",{"title":1467,"url":1468},"Generate a UUID","\u002Fsnippets\u002Fjavascript-generate-uuid",{"title":1470,"url":1471},"Get IP geolocation info (ipapi.co)","\u002Fsnippets\u002Fjavascript-ip-geolocation-js",{"title":1473,"url":1474},"Get User ID and Data from Telegram Update","\u002Fsnippets\u002Fjavascript-telegram-get-user-from-update",{"title":1476,"url":1477},"Get seconds until the next UTC day\u002Fmonth rollover","\u002Fsnippets\u002Fjavascript-seconds-until-utc-rollover",{"title":1479,"url":1480},"Manage a deduplicated, sorted tag list","\u002Fsnippets\u002Fjavascript-dedupe-sort-tag-list",{"title":1482,"url":1483},"Merge Tailwind classes safely (cn helper)","\u002Fsnippets\u002Fjavascript-cn-classname-merge",{"title":1485,"url":1486},"Parse Telegram \u002Fstart deep-link parameters","\u002Fsnippets\u002Fjavascript-parse-telegram-start-params",{"title":1488,"url":1489},"Parse loose values into a real boolean","\u002Fsnippets\u002Fjavascript-to-boolean",{"title":1491,"url":1492},"Pick and omit object keys","\u002Fsnippets\u002Fjavascript-pick-omit-object-keys",{"title":1494,"url":1495},"Pluralize words with Intl.PluralRules","\u002Fsnippets\u002Fjavascript-pluralize-intl",{"title":1497,"url":1498},"Remove duplicates from an array","\u002Fsnippets\u002Fjavascript-remove-duplicate-array-items",{"title":1500,"url":1501},"Smooth Scroll to Anchor","\u002Fsnippets\u002Fjavascript-scroll-to-anchor",{"title":1503,"url":1504},"Telegram User Link and Mention","\u002Fsnippets\u002Fjavascript-telegram-user-link",{"title":1506,"url":1507},"Throttle a function","\u002Fsnippets\u002Fjavascript-throttle-function",{"title":1509,"url":1510},"Truncate Text by Word Count","\u002Fsnippets\u002Fjavascript-truncate-text-by-words",{"title":1512,"url":1513},"Validate Email Address","\u002Fsnippets\u002Fjavascript-validate-email",{"title":1515,"url":1516},"Validate a media URL","\u002Fsnippets\u002Fjavascript-validate-media-url",{"title":1518,"url":1519},"sleep() \u002F Async Delay","\u002Fsnippets\u002Fjavascript-sleep-delay-async",[1521,1524,1527,1530,1533,1536,1538,1541,1544,1547,1550,1553,1556,1559,1562,1565,1568,1571],{"title":1522,"url":1523},"Bcrypt Password Hashing (PHP)","\u002Fsnippets\u002Fphp-hash-password-bcrypt-php",{"title":1525,"url":1526},"Escape text for Telegram MarkdownV2","\u002Fsnippets\u002Fphp-escape-telegram-markdownv2",{"title":1528,"url":1529},"Format bytes as a human-readable size","\u002Fsnippets\u002Fphp-format-bytes-human-readable",{"title":1531,"url":1532},"Generate a colored initials avatar (HTML\u002FCSS)","\u002Fsnippets\u002Fphp-initials-avatar-php",{"title":1534,"url":1535},"Generate a secure random password","\u002Fsnippets\u002Fphp-secure-random-password-php",{"title":1470,"url":1537},"\u002Fsnippets\u002Fphp-ip-geolocation-php",{"title":1539,"url":1540},"Get Telegram User ID via PHP Webhook","\u002Fsnippets\u002Fphp-telegram-get-user-php",{"title":1542,"url":1543},"Get a timezone's current GMT offset in PHP","\u002Fsnippets\u002Fphp-timezone-gmt-offset",{"title":1545,"url":1546},"Get the real client IP behind a proxy\u002FCDN","\u002Fsnippets\u002Fphp-get-client-ip-behind-proxy",{"title":1548,"url":1549},"Make a GET request with cURL","\u002Fsnippets\u002Fphp-curl-get-request",{"title":1551,"url":1552},"Mask a token\u002FAPI key for safe display","\u002Fsnippets\u002Fphp-mask-token-preview",{"title":1554,"url":1555},"Parse a BCP-47 language tag","\u002Fsnippets\u002Fphp-parse-bcp47-language-tag",{"title":1557,"url":1558},"Redis Rate Limiter (PHP)","\u002Fsnippets\u002Fphp-rate-limiter-redis",{"title":1560,"url":1561},"Retry an operation with exponential backoff + jitter","\u002Fsnippets\u002Fphp-retry-with-backoff",{"title":1563,"url":1564},"Sanitize a string to valid UTF-8","\u002Fsnippets\u002Fphp-sanitize-utf8-php",{"title":1566,"url":1567},"Send Telegram Message via Bot API (PHP)","\u002Fsnippets\u002Fphp-telegram-send-message-php",{"title":1569,"url":1570},"Validate an email address","\u002Fsnippets\u002Fphp-validate-email",{"title":1572,"url":1573},"Verify Telegram Webhook Secret","\u002Fsnippets\u002Fphp-telegram-webhook-verify-secret",[1575,1578,1581,1584,1587,1590,1593,1596],{"title":1576,"url":1577},"CSS Gradient Text","\u002Fsnippets\u002Fcss-gradient-text",{"title":1579,"url":1580},"CSS Grid auto-fit — Responsive Grid Without Media Queries","\u002Fsnippets\u002Fcss-grid-auto-fit",{"title":1582,"url":1583},"Center anything with flexbox","\u002Fsnippets\u002Fcss-center-with-flexbox-css",{"title":1585,"url":1586},"Custom CSS Scrollbar","\u002Fsnippets\u002Fcss-custom-scrollbar",{"title":1588,"url":1589},"Fluid Typography with clamp()","\u002Fsnippets\u002Fcss-fluid-typography",{"title":1591,"url":1592},"Multiline text-overflow with Ellipsis","\u002Fsnippets\u002Fcss-line-clamp",{"title":1594,"url":1595},"Skeleton Loading Animation (CSS)","\u002Fsnippets\u002Fcss-skeleton-loading",{"title":1597,"url":1598},"Truncate single-line text with an ellipsis","\u002Fsnippets\u002Fcss-truncate-text-ellipsis",[1600,1603,1606,1609,1612,1615],{"title":1601,"url":1602},"Centralized error-handling middleware","\u002Fsnippets\u002Fexpress.js-error-handling-middleware",{"title":1604,"url":1605},"Enable CORS in Express","\u002Fsnippets\u002Fexpress.js-cors-middleware",{"title":1607,"url":1608},"JWT Auth Middleware (Express)","\u002Fsnippets\u002Fexpress.js-jwt-auth-middleware",{"title":1610,"url":1611},"JWT access and refresh token rotation in Express","\u002Fsnippets\u002Fexpress-jwt-refresh-tokens",{"title":1613,"url":1614},"Rate limit requests","\u002Fsnippets\u002Fexpress.js-rate-limiting-middleware",{"title":1616,"url":1617},"Sanitize an uploaded filename","\u002Fsnippets\u002Fexpress.js-sanitize-filename-express",[1619,1622,1625,1628,1631],{"title":1620,"url":1621},"Check if a file or directory exists","\u002Fsnippets\u002Fpython-check-file-exists",{"title":1623,"url":1624},"Flatten a nested list","\u002Fsnippets\u002Fpython-flatten-nested-list-python",{"title":1626,"url":1627},"Read a CSV file into a list of dictionaries","\u002Fsnippets\u002Fpython-read-csv-file",{"title":1629,"url":1630},"Retry Decorator with Backoff (Python)","\u002Fsnippets\u002Fpython-retry-decorator-python",{"title":1632,"url":1633},"asyncio.gather with Concurrency Limit","\u002Fsnippets\u002Fpython-async-gather-limited-python",[1635,1638,1641,1644,1647],{"title":1636,"url":1637},"Chunk Array into Groups","\u002Fsnippets\u002Ftypescript-chunk-array",{"title":1639,"url":1640},"Group Array by Key","\u002Fsnippets\u002Ftypescript-group-by-key",{"title":1642,"url":1643},"Pagination Utilities (Offset \u002F Limit)","\u002Fsnippets\u002Ftypescript-pagination-offset-limit",{"title":1645,"url":1646},"Zod API validation schemas","\u002Fsnippets\u002Fzod-api-validation-schema",{"title":1648,"url":1649},"useFetch with AbortController — TypeScript","\u002Fsnippets\u002Ftypescript-use-fetch-with-abort",[1651,1654,1657,1660,1663,1666],{"title":1652,"url":1653},"Constant-time string comparison for secrets","\u002Fsnippets\u002Fnode.js-timing-safe-string-compare",{"title":1655,"url":1656},"Create a basic HTTP server","\u002Fsnippets\u002Fnode.js-create-http-server",{"title":1658,"url":1659},"Hash and verify a password with bcrypt","\u002Fsnippets\u002Fnode.js-hash-password-bcrypt",{"title":1661,"url":1662},"Read a file asynchronously","\u002Fsnippets\u002Fnode.js-read-file-async",{"title":1664,"url":1665},"Read environment variables with defaults","\u002Fsnippets\u002Fnode.js-read-env-defaults-node",{"title":1667,"url":1668},"Stripe webhook handler with idempotency","\u002Fsnippets\u002Fstripe-webhook-idempotency",[1670,1673,1675,1678,1681,1684,1687],{"title":1671,"url":1672},"Create a simple HTTP server","\u002Fsnippets\u002Fgo-simple-http-server",{"title":1467,"url":1674},"\u002Fsnippets\u002Fgo-generate-uuid",{"title":1676,"url":1677},"HTTP Request with Timeout via Context (Go)","\u002Fsnippets\u002Fgo-context-timeout",{"title":1679,"url":1680},"HTTP Server with Graceful Shutdown (Go)","\u002Fsnippets\u002Fgo-graceful-shutdown",{"title":1682,"url":1683},"Make an HTTP GET request","\u002Fsnippets\u002Fgo-http-get-request",{"title":1685,"url":1686},"Read a file line by line","\u002Fsnippets\u002Fgo-read-file-lines-go",{"title":1688,"url":1689},"Run tasks concurrently with goroutines","\u002Fsnippets\u002Fgo-goroutines-waitgroup",[1691,1694,1697,1700,1703],{"title":1692,"url":1693},"Create an API route handler","\u002Fsnippets\u002Fnext.js-api-route-handler",{"title":1695,"url":1696},"Generate dynamic page metadata","\u002Fsnippets\u002Fnext.js-dynamic-metadata",{"title":1698,"url":1699},"Next.js App Router middleware — authentication and redirects","\u002Fsnippets\u002Fnextjs-middleware-auth",{"title":1701,"url":1702},"Read search params in a Server Component","\u002Fsnippets\u002Fnext.js-read-server-search-params-nextjs",{"title":1704,"url":1705},"Redirect from a Server Component or Route Handler","\u002Fsnippets\u002Fnext.js-redirect",[1707,1710,1713],{"title":1708,"url":1709},"Docker Compose for Node.js + PostgreSQL + Redis","\u002Fsnippets\u002Fdocker-compose-node-postgres-redis",{"title":1711,"url":1712},"Docker multi-stage builds for Node.js","\u002Fsnippets\u002Fdocker-multistage-build-nodejs",{"title":1714,"url":1715},"Docker security — non-root user and read-only filesystem","\u002Fsnippets\u002Fdocker-security-nonroot",[1717,1720,1723,1726],{"title":1718,"url":1719},"Git cherry-pick and bisect — target specific commits","\u002Fsnippets\u002Fgit-cherry-pick-bisect",{"title":1721,"url":1722},"Git log — filtering and formatting commit history","\u002Fsnippets\u002Fgit-log-filter-format",{"title":1724,"url":1725},"Git squash commits and interactive rebase","\u002Fsnippets\u002Fgit-squash-rebase-workflow",{"title":1727,"url":1728},"Git stash — save and restore work in progress","\u002Fsnippets\u002Fgit-stash-work-in-progress",[1730,1733,1736,1739],{"title":1731,"url":1732},"GitHub Actions — CI pipeline for PRs","\u002Fsnippets\u002Fgithub-actions-ci-pr",{"title":1734,"url":1735},"GitHub Actions — build and push Docker image to registry","\u002Fsnippets\u002Fgithub-actions-docker-build-push",{"title":1737,"url":1738},"GitHub Actions — deploy Node.js app to VPS via SSH","\u002Fsnippets\u002Fgithub-actions-deploy-vps",{"title":1740,"url":1741},"GitHub Actions — scheduled tasks with cron","\u002Fsnippets\u002Fgithub-actions-scheduled-cron",[1743,1746,1749,1752],{"title":1744,"url":1745},"Nginx WebSocket proxy for real-time apps","\u002Fsnippets\u002Fnginx-websocket-proxy",{"title":1747,"url":1748},"Nginx load balancing between Node.js instances","\u002Fsnippets\u002Fnginx-load-balancing-nodejs",{"title":1750,"url":1751},"Nginx reverse proxy with SSL termination","\u002Fsnippets\u002Fnginx-reverse-proxy-ssl",{"title":1753,"url":1754},"Nginx static file server with caching","\u002Fsnippets\u002Fnginx-static-cache-server",[1756,1759,1762,1765],{"title":1757,"url":1758},"Prisma CRUD — create, read, update, delete","\u002Fsnippets\u002Fprisma-crud-operations",{"title":1760,"url":1761},"Prisma migrations — create, apply, and roll back","\u002Fsnippets\u002Fprisma-migrations-workflow",{"title":1763,"url":1764},"Prisma pagination — cursor and offset","\u002Fsnippets\u002Fprisma-pagination-cursor-offset",{"title":1766,"url":1767},"Prisma schema — User, Post, and relations","\u002Fsnippets\u002Fprisma-schema-user-relations",[1769,1772,1775,1778,1781,1784],{"title":1770,"url":1771},"React context + useReducer for global state","\u002Fsnippets\u002Freact-context-usereducer",{"title":1773,"url":1774},"React useMediaQuery — responsive hook","\u002Fsnippets\u002Freact-usemediaquery-hook",{"title":1776,"url":1777},"useDebounce hook","\u002Fsnippets\u002Freact.js-use-debounce-hook",{"title":1779,"url":1780},"useFetch hook","\u002Fsnippets\u002Freact.js-use-fetch-hook",{"title":1782,"url":1783},"useOnClickOutside hook","\u002Fsnippets\u002Freact.js-use-click-outside-hook",{"title":1785,"url":1786},"usePersistedState hook","\u002Fsnippets\u002Freact.js-use-local-storage-react",[1788,1791,1794,1797],{"title":1789,"url":1790},"Tailwind CSS animations — keyframes and transition","\u002Fsnippets\u002Ftailwind-animations-keyframes",{"title":1792,"url":1793},"Tailwind CSS custom theme with CSS variables","\u002Fsnippets\u002Ftailwind-custom-theme-variables",{"title":1795,"url":1796},"Tailwind CSS dark mode with class strategy","\u002Fsnippets\u002Ftailwind-dark-mode-class",{"title":1798,"url":1799},"Tailwind CSS responsive card grid","\u002Fsnippets\u002Ftailwind-responsive-card-grid",[1801,1804,1807,1810,1813,1816,1819,1822,1825],{"title":1802,"url":1803},"useBreakpoints hook","\u002Fsnippets\u002Fvue-use-breakpoints",{"title":1805,"url":1806},"useClipboardCopy hook","\u002Fsnippets\u002Fvue-use-clipboard-copy",{"title":1808,"url":1809},"useDebouncedRef — Delayed Reactive Value","\u002Fsnippets\u002Fvue-use-debounced-ref",{"title":1811,"url":1812},"useDragAndDrop hook","\u002Fsnippets\u002Fvue-use-drag-and-drop",{"title":1814,"url":1815},"useIntersectionObserver — Vue Composable","\u002Fsnippets\u002Fvue-use-intersection-observer",{"title":1817,"url":1818},"useMediaQuery — Reactive Media Query","\u002Fsnippets\u002Fvue-use-media-query",{"title":1820,"url":1821},"usePlatform hook","\u002Fsnippets\u002Fvue-use-platform",{"title":1823,"url":1824},"useTheme hook","\u002Fsnippets\u002Fvue-use-theme",{"title":1826,"url":1827},"useViewMode hook","\u002Fsnippets\u002Fvue-use-view-mode",1784561414096]