[{"data":1,"prerenderedAt":3897},["ShallowReactive",2],{"blog-podtverzhdenie-nomera-na-sayte":3,"blog-related-podtverzhdenie-nomera-na-sayte":379,"mdc-bwvf3b-key":3829,"mdc-4w5rno-key":3841,"mdc--ef7l2t-key":3856,"mdc--3vn8o7-key":3865,"mdc--l960em-key":3888},{"id":4,"title":5,"body":6,"category":343,"cta":344,"description":349,"draft":350,"extension":351,"faq":352,"h1":365,"meta":366,"navigation":367,"order":368,"path":369,"published":370,"related":371,"seo":375,"stem":376,"summary":377,"updated":370,"__hash__":378},"blog\u002Fblog\u002Fpodtverzhdenie-nomera-na-sayte.md","Подтверждение номера телефона на сайте — как реализовать",{"type":7,"value":8,"toc":334},"minimark",[9,13,18,53,62,66,139,142,155,159,204,211,215,218,268,271,275,278,281],[10,11,12],"p",{},"Форма с подтверждением телефона нужна в трёх местах почти любого сайта: при регистрации, при оформлении заказа и при публикации объявления. Разбираем, какие есть варианты реализации, сколько они стоят и как собрать сценарий со звонком.",[14,15,17],"h2",{"id":16},"gde-na-sayte-nuzhno-podtverzhdat-nomer","Где на сайте нужно подтверждать номер",[19,20,21,29,35,41,47],"ul",{},[22,23,24,28],"li",{},[25,26,27],"strong",{},"Регистрация."," Отсечь фейковые аккаунты и получить рабочий контакт.",[22,30,31,34],{},[25,32,33],{},"Оформление заказа."," Убедиться, что курьер дозвонится, и отсечь ложные заказы — особенно при оплате при получении.",[22,36,37,40],{},[25,38,39],{},"Публикация объявления."," Подтверждённый контакт продавца снижает долю мошеннических размещений.",[22,42,43,46],{},[25,44,45],{},"Восстановление доступа."," Телефон как доказательство, что аккаунт принадлежит вам.",[22,48,49,52],{},[25,50,51],{},"Подтверждение операции."," Смена реквизитов, крупная покупка, изменение доставки.",[10,54,55,56,61],{},"Разбор задач по отраслям — в статье ",[57,58,60],"a",{"href":59},"\u002Fblog\u002Fgde-nuzhna-verifikaciya-telefona","где нужна верификация телефона",".",[14,63,65],{"id":64},"varianty-realizacii-i-chto-oni-stoyat","Варианты реализации и что они стоят",[67,68,71],"div",{"className":69},[70],"md-table",[72,73,74,93],"table",{},[75,76,77],"thead",{},[78,79,80,84,87,90],"tr",{},[81,82,83],"th",{},"Вариант",[81,85,86],{},"Срок внедрения",[81,88,89],{},"За что платите",[81,91,92],{},"Что ломается",[94,95,96,111,125],"tbody",{},[78,97,98,102,105,108],{},[99,100,101],"td",{},"Своя интеграция с SMS-агрегатором",[99,103,104],{},"от недели",[99,106,107],{},"за отправку, независимо от доставки",[99,109,110],{},"доставляемость, фильтры операторов, роуминг",[78,112,113,116,119,122],{},[99,114,115],{},"Готовый виджет с кодом",[99,117,118],{},"день-два",[99,120,121],{},"по тарифу виджета, обычно за попытку",[99,123,124],{},"чужой интерфейс на вашем сайте, ограниченная кастомизация",[78,126,127,130,133,136],{},[99,128,129],{},"Подтверждение звонком",[99,131,132],{},"день",[99,134,135],{},"только за состоявшийся звонок",[99,137,138],{},"не проходит при скрытом номере и у зарубежных SIM",[10,140,141],{},"У первых двух вариантов общая слабость: результат зависит от доставки сообщения, на которую вы не влияете. У третьего зависимость другая — от того, передаётся ли номер вызывающего абонента. Она затрагивает меньшую долю аудитории и, в отличие от доставляемости, предсказуема.",[10,143,144,145,149,150,154],{},"Как именно устроена проверка звонком — в статье про ",[57,146,148],{"href":147},"\u002Fblog\u002Fpodtverzhdenie-nomera-zvonkom","подтверждение номера телефона звонком",". Если ваш продукт живёт не только в вебе, посмотрите отличия мобильного сценария в материале про ",[57,151,153],{"href":152},"\u002Fblog\u002Fpodtverzhdenie-nomera-v-prilozhenii","подтверждение номера в приложении",": там набор открывается одним касанием, и экран ожидания строится иначе.",[14,156,158],{"id":157},"kak-vstroit-podtverzhdenie-zvonkom-5-shagov","Как встроить подтверждение звонком: 5 шагов",[160,161,162,168,174,184,190],"ol",{},[22,163,164,167],{},[25,165,166],{},"Зарегистрируйтесь и создайте проект."," Он получит собственный ключ. Ключ живёт в переменных окружения на сервере — не в репозитории и не во фронтенде.",[22,169,170,173],{},[25,171,172],{},"Добавьте эндпоинт «начать проверку» на своём бэкенде."," Он принимает номер от фронтенда, валидирует его, вызывает создание проверки и возвращает во фронтенд только идентификатор и выделенный номер.",[22,175,176,179,180,61],{},[25,177,178],{},"Покажите номер пользователю."," Крупно, с таймером на 60 секунд, на мобильных — ссылкой ",[181,182,183],"code",{},"tel:",[22,185,186,189],{},[25,187,188],{},"Добавьте эндпоинт «статус проверки»."," Фронтенд опрашивает его, а он проксирует запрос с ключом. Наружу отдавайте только статус — ни ключ, ни лишние поля клиенту не нужны.",[22,191,192,195,196,199,200,203],{},[25,193,194],{},"Обработайте результат на бэкенде."," По ",[181,197,198],{},"CONFIRMED"," — помечайте номер подтверждённым и продолжайте сценарий, по ",[181,201,202],{},"EXPIRED"," — предлагайте повторить.",[10,205,206,207,61],{},"Запросы, форматы ответов и примеры кода — в материале про ",[57,208,210],{"href":209},"\u002Fblog\u002Fapi-podtverzhdeniya-nomera","API подтверждения номера",[14,212,214],{"id":213},"chto-pokazat-polzovatelyu-v-forme","Что показать пользователю в форме",[10,216,217],{},"Экран ожидания звонка решает больше, чем кажется: именно на нём пользователь либо делает нужное действие, либо закрывает вкладку.",[19,219,220,230,236,244,250,256,262],{},[22,221,222,225,226,229],{},[25,223,224],{},"Номер — самый крупный элемент экрана."," С разделителями, в привычном виде: ",[181,227,228],{},"+7 495 000-00-00",". Его будут набирать руками с другого устройства.",[22,231,232,235],{},[25,233,234],{},"Кнопка копирования"," рядом с номером — на десктопе она нужнее, чем кажется.",[22,237,238,243],{},[25,239,240,241],{},"Ссылка ",[181,242,183],{}," на мобильных: одно касание вместо переписывания.",[22,245,246,249],{},[25,247,248],{},"Понятная инструкция."," «Позвоните на этот номер с телефона, который подтверждаете. Трубку брать не нужно — вызов оборвётся сам». Без слова «верификация».",[22,251,252,255],{},[25,253,254],{},"Таймер на 60 секунд."," Показывает, что процесс идёт, и задаёт рамку ожидания.",[22,257,258,261],{},[25,259,260],{},"Автопереход при подтверждении."," Пользователь не должен нажимать «Проверить» — экран меняется сам.",[22,263,264,267],{},[25,265,266],{},"После истечения таймера"," — «Позвонить ещё раз» и ссылка «Подтвердить другим способом».",[10,269,270],{},"Отдельно про звонок с того же номера: предупредите об этом заранее, одной строкой под номером. Иначе часть пользователей позвонит с рабочего телефона, проверка не подтвердится, и причина будет неочевидна.",[14,272,274],{"id":273},"kak-obrabotat-rezultat-na-bekende","Как обработать результат на бэкенде",[10,276,277],{},"Вебхуков в сервисе нет — результат забирается опросом статуса. Это упрощает интеграцию: не нужен публично доступный обработчик, не нужна проверка подписи, не нужно думать о повторных доставках.",[10,279,280],{},"Что учесть:",[19,282,283,289,301,307,316,322,328],{},[22,284,285,288],{},[25,286,287],{},"Интервал опроса."," Раз в одну-две секунды достаточно. Чаще — бессмысленно, реже — заметная задержка на экране.",[22,290,291,294,295,297,298,300],{},[25,292,293],{},"Условия остановки."," Три штуки: ",[181,296,198],{},", ",[181,299,202],{},", истёк локальный таймер. Без явных условий опрос продолжится после ухода пользователя со страницы.",[22,302,303,306],{},[25,304,305],{},"Доверять только своему серверу."," Решение «номер подтверждён» принимается на бэкенде по ответу API, а не по тому, что прислал фронтенд.",[22,308,309,312,313,315],{},[25,310,311],{},"Идемпотентность на своей стороне."," Один и тот же ",[181,314,198],{}," может быть увиден несколькими запросами подряд — привязка номера должна отрабатывать один раз.",[22,317,318,321],{},[25,319,320],{},"Проверка баланса."," Если средств не хватает, проверка не создастся и API вернёт ошибку. Обработайте её явно и заведите оповещение о низком балансе: иначе форма молча перестанет работать.",[22,323,324,327],{},[25,325,326],{},"Ограничение частоты на своём эндпоинте."," Создание проверок стоит денег, поэтому лимит на число попыток с одного адреса или сессии нужен обязательно.",[22,329,330,333],{},[25,331,332],{},"Валидация номера до отправки."," Приведите введённое к формату E.164 на своей стороне — так вы не потратите запрос на заведомо некорректный номер и покажете пользователю понятную ошибку сразу в форме.",{"title":335,"searchDepth":336,"depth":336,"links":337},"",2,[338,339,340,341,342],{"id":16,"depth":336,"text":17},{"id":64,"depth":336,"text":65},{"id":157,"depth":336,"text":158},{"id":213,"depth":336,"text":214},{"id":273,"depth":336,"text":274},"Реализация",{"title":345,"text":346,"label":347,"href":348},"Добавьте подтверждение на свой сайт","Два запроса к REST API, без SDK и лишних зависимостей. Ключ проекта выдаётся сразу после регистрации.","Начать бесплатно","\u002Fregister","Как добавить на сайт подтверждение номера телефона: варианты реализации и их цена, сценарий со звонком, что показывать в форме и как обработать результат.",false,"md",[353,356,359,362],{"q":354,"a":355},"Пользователь на десктопе — как он позвонит?","С телефона, глядя на экран компьютера. Поэтому номер нужно показывать крупно\nи с разделителями, чтобы его было легко набрать вручную, а не мелким шрифтом\nв одну строку. На мобильных браузерах номер должен быть ссылкой `tel:`.\n",{"q":357,"a":358},"Можно ли обойтись без бэкенда?","Нет. Ключ проекта нельзя размещать во фронтенде — из браузера его заберёт кто\nугодно и начнёт тратить ваш баланс. Создание проверки и опрос статуса должны\nидти через ваш собственный сервер.\n",{"q":360,"a":361},"Как часто опрашивать статус?","Достаточно раза в пару секунд: сценарий укладывается в 60 секунд, и более\nчастый опрос ничего не ускоряет. Прекращать опрос нужно по `CONFIRMED`,\nпо `EXPIRED` и по истечении таймера, иначе запросы будут идти вечно.\n",{"q":363,"a":364},"Что делать с пользователями, у которых звонок не проходит?","Оставить в форме ссылку «Подтвердить другим способом». Скрытый номер,\nзарубежная SIM и корпоративная АТС встречаются нечасто, но без запасного\nварианта такие пользователи не пройдут дальше.\n","Как сделать подтверждение номера телефона на сайте",{},true,7,"\u002Fblog\u002Fpodtverzhdenie-nomera-na-sayte","2026-08-08",[372,373,374],"podtverzhdenie-nomera-zvonkom","podtverzhdenie-nomera-v-prilozhenii","api-podtverzhdeniya-nomera",{"title":5,"description":349},"blog\u002Fpodtverzhdenie-nomera-na-sayte","Подтверждение номера на сайте делается тремя способами: своя интеграция с SMS-агрегатором,\nготовый виджет или проверка звонком. Последний вариант не требует доставки сообщения —\nпользователь сам звонит на выделенный номер, и вводить ему нечего. На бэкенде это два\nHTTP-запроса и три статуса, во фронтенде — кликабельный номер и таймер на 60 секунд.\nПодтверждение стоит 0,20 ₽, несостоявшееся не тарифицируется.\n","E4A1c-OYXZBIRTAoVrLxmuUT_fr8E9_Q658zLnHCN8E",[380,897,1193],{"id":381,"title":382,"body":383,"category":862,"cta":863,"description":866,"draft":350,"extension":351,"faq":867,"h1":886,"meta":887,"navigation":367,"order":888,"path":147,"published":370,"related":889,"seo":893,"stem":894,"summary":895,"updated":370,"__hash__":896},"blog\u002Fblog\u002Fpodtverzhdenie-nomera-zvonkom.md","Подтверждение номера телефона звонком — верификация без SMS",{"type":7,"value":384,"toc":853},[385,388,391,395,398,401,404,488,491,495,498,541,548,551,555,558,564,567,573,656,663,667,670,673,676,683,686,690,697,703,756,759,771,775,778,812,817,821,824,847],[10,386,387],{},"Способ выглядит непривычно ровно один раз — пока не увидишь его в деле. Пользователь не ждёт SMS, не переключается в приложение сообщений, не копирует шесть цифр и не возвращается обратно в форму. Он нажимает одну кнопку, телефон начинает набор, через мгновение вызов обрывается — и регистрация продолжается сама.",[10,389,390],{},"Ниже — что происходит под капотом, чем это отличается от обычного flash call, почему звонок доходит там, где SMS теряется, и сколько всё это стоит.",[14,392,394],{"id":393},"chto-takoe-proverka-telefona-zvonkom","Что такое проверка телефона звонком",[10,396,397],{},"Задача любой телефонной верификации одна: убедиться, что человек, который вводит номер в форму, действительно имеет к этому номеру доступ. Классическое решение — отправить на него одноразовый код и попросить ввести обратно. Раз код дошёл и был введён, значит, телефон у пользователя в руках.",[10,399,400],{},"Проверка номера звонком решает ту же задачу с другой стороны. Вместо того чтобы отправлять что-то абоненту и ждать, что он это получит и перепечатает, сервис просит его сделать исходящий вызов. Телефонная сеть передаёт номер вызывающего абонента вместе с вызовом — это тот же механизм, благодаря которому вы видите, кто вам звонит. Сервис сравнивает определившийся номер с тем, который пользователь ввёл в форму. Совпало — номер подтверждён.",[10,402,403],{},"Разница принципиальная. В SMS-сценарии доказательством служит доставленное сообщение, а его доставка зависит от цепочки посредников. В сценарии со звонком доказательством служит сам факт исходящего вызова с проверяемого номера, и никакого содержимого передавать не требуется.",[67,405,407],{"className":406},[70],[72,408,409,421],{},[75,410,411],{},[78,412,413,415,418],{},[81,414],{},[81,416,417],{},"SMS-код",[81,419,420],{},"Звонок на выделенный номер",[94,422,423,434,445,456,467,477],{},[78,424,425,428,431],{},[99,426,427],{},"Что делает пользователь",[99,429,430],{},"ждёт сообщение, открывает его, запоминает или копирует код, возвращается в форму, вводит",[99,432,433],{},"нажимает кнопку вызова и сбрасывает",[78,435,436,439,442],{},[99,437,438],{},"Число действий",[99,440,441],{},"пять",[99,443,444],{},"два",[78,446,447,450,453],{},[99,448,449],{},"Что подтверждает факт",[99,451,452],{},"что сообщение дошло и было прочитано",[99,454,455],{},"что вызов совершён с проверяемого номера",[78,457,458,461,464],{},[99,459,460],{},"Где ломается",[99,462,463],{},"сообщение не дошло, пришло с задержкой, код введён с ошибкой",[99,465,466],{},"номер вызывающего не определяется, звонок с другого телефона",[78,468,469,472,474],{},[99,470,471],{},"За что списываются деньги",[99,473,107],{},[99,475,476],{},"за состоявшийся звонок",[78,478,479,482,485],{},[99,480,481],{},"Ввод кода",[99,483,484],{},"обязателен",[99,486,487],{},"не нужен",[10,489,490],{},"У способа есть и обратная сторона, о ней честно в разделе про ограничения: он работает, только пока номер вызывающего абонента передаётся сетью, и только для российских номеров.",[14,492,494],{"id":493},"kak-prohodit-podtverzhdenie-chetyre-shaga","Как проходит подтверждение: четыре шага",[10,496,497],{},"Со стороны интеграции всё сводится к двум запросам к API — на создание проверки и на получение её результата.",[160,499,500,510,519,525],{},[22,501,502,505,506,509],{},[25,503,504],{},"Ваша система создаёт проверку."," Отправляет ключ проекта и номер пользователя в формате E.164 (",[181,507,508],{},"+79161234567","). В ответ приходит идентификатор проверки и выделенный номер, на который нужно позвонить.",[22,511,512,515,516,518],{},[25,513,514],{},"Вы показываете этот номер пользователю."," На мобильных — ссылкой ",[181,517,183],{},", чтобы набор открывался в одно касание и номер не пришлось переписывать руками. Рядом полезен таймер: у проверки есть срок.",[22,520,521,524],{},[25,522,523],{},"Пользователь звонит."," Отвечать на вызов не нужно и невозможно — сервис фиксирует входящий и сбрасывает соединение. Звонок сопоставляется с проверкой по паре «выделенный номер + номер вызывающего абонента».",[22,526,527,530,531,534,535,537,538,540],{},[25,528,529],{},"Ваша система забирает результат."," Опрашивает статус проверки по её идентификатору: ",[181,532,533],{},"PENDING"," — звонка ещё не было, ",[181,536,198],{}," — номер подтверждён, ",[181,539,202],{}," — 60 секунд истекли, звонка не поступило.",[10,542,543,544,547],{},"Важная деталь, которую часто понимают неправильно: ",[25,545,546],{},"выделенный номер не закрепляется за пользователем",". Один и тот же номер из пула одновременно обслуживает проверки разных людей — уникальной должна быть только пара «номер плюс звонящий». Поэтому пул не «заканчивается» при всплеске регистраций и не требует запаса номеров под пиковую нагрузку: сервис сам подбирает свободную комбинацию и распределяет вызовы по пулу.",[10,549,550],{},"Вторая деталь — про 60 секунд. Это срок жизни проверки, а не срок аренды номера. Если пользователь отвлёкся и позвонил через две минуты, вызов уже ни к чему не привяжется: проверка к тому моменту помечена как истёкшая. Правильная реакция интерфейса — не «ошибка», а кнопка «Позвонить ещё раз», создающая новую проверку.",[14,552,554],{"id":553},"chem-eto-otlichaetsya-ot-obychnogo-flash-call","Чем это отличается от обычного flash call",[10,556,557],{},"Термин flash call описывает два зеркальных сценария, и путаница между ними — главный источник недопонимания при выборе провайдера.",[10,559,560,563],{},[25,561,562],{},"Обычный flash call."," Звонит сервис. Пользователь видит входящий вызов, который тут же обрывается, а приложение читает номер входящего и подставляет его последние цифры как код подтверждения. Пользователю иногда даже не нужно ничего делать — но только если приложение получило доступ к журналу вызовов.",[10,565,566],{},"Здесь и находится слабое место. Доступ к журналу вызовов — чувствительное разрешение: в Google Play его выдача ограничена и требует обоснования, в iOS публичного API для чтения журнала нет вообще, а в браузере такой сценарий недоступен по определению. На практике обычный flash call — это история про Android-приложение с одобренным разрешением, и больше почти нигде.",[10,568,569,572],{},[25,570,571],{},"Обратный (reverse) flash call."," Звонит пользователь. Сервису не нужен доступ ни к журналу вызовов, ни к SMS — он видит вызов на своей стороне, потому что вызов пришёл к нему. Отсюда следует, что сценарий одинаково работает и в мобильном приложении, и на обычном сайте в браузере, и никаких особых разрешений не требует.",[67,574,576],{"className":575},[70],[72,577,578,590],{},[75,579,580],{},[78,581,582,584,587],{},[81,583],{},[81,585,586],{},"Обычный flash call",[81,588,589],{},"Обратный flash call",[94,591,592,603,614,625,634,645],{},[78,593,594,597,600],{},[99,595,596],{},"Кто звонит",[99,598,599],{},"сервис пользователю",[99,601,602],{},"пользователь сервису",[78,604,605,608,611],{},[99,606,607],{},"Разрешения в приложении",[99,609,610],{},"доступ к журналу вызовов",[99,612,613],{},"не нужны",[78,615,616,619,622],{},[99,617,618],{},"Работает на сайте",[99,620,621],{},"нет",[99,623,624],{},"да",[78,626,627,630,632],{},[99,628,629],{},"Работает на iOS",[99,631,621],{},[99,633,624],{},[78,635,636,639,642],{},[99,637,638],{},"Что вводит пользователь",[99,640,641],{},"цифры из номера (или автоподстановка)",[99,643,644],{},"ничего",[78,646,647,650,653],{},[99,648,649],{},"Что может помешать",[99,651,652],{},"отказ в разрешении, ограничения магазинов приложений",[99,654,655],{},"номер вызывающего не определяется",[10,657,658,659,61],{},"FlashCall работает по второй схеме. Подробный разбор терминологии — включая написания «флеш колл» и синоним call to auth — в отдельном материале ",[57,660,662],{"href":661},"\u002Fblog\u002Fchto-takoe-flash-call","что такое flash call и обратный flash call",[14,664,666],{"id":665},"pochemu-zvonok-dohodit-tam-gde-sms-ne-doshla","Почему звонок доходит там, где SMS не дошла",[10,668,669],{},"Чтобы SMS с кодом оказалась на экране, она проходит цепочку: ваш сервис → агрегатор → оператор получателя → устройство. На каждом переходе есть очередь, фильтр и возможность потерять сообщение. Массовые рассылки фильтруются как спам, роуминг и запрет на приём международных сообщений отсекают часть абонентов, переполненная память устройства не даёт сообщение принять, а задержка на стороне агрегатора превращает код в бесполезный — к моменту доставки он уже истёк.",[10,671,672],{},"Для бизнеса это не абстрактная проблема, а прямые потери: недоставленная SMS — это не отправленный впустую рубль, а несостоявшаяся регистрация. Причём отправка тарифицируется независимо от того, дошло сообщение или нет.",[10,674,675],{},"Исходящий вызов идёт по голосовой сети напрямую. Там нет ни фильтрации массовых рассылок, ни очереди на доставку, ни спам-папки — соединение либо устанавливается, либо нет, и результат виден мгновенно. Отдельно важно, что инициатива на стороне пользователя: он не ждёт, пока до него что-то доедет, а сам совершает действие и сразу видит результат.",[10,677,678,679,61],{},"Подробный разбор причин, по которым коды не доходят, и что с этим делать пользователю — в материале ",[57,680,682],{"href":681},"\u002Fblog\u002Fne-prihodit-kod-podtverzhdeniya","почему не приходит код подтверждения",[10,684,685],{},"Обещать стопроцентную доставляемость было бы нечестно, поэтому назовём и границы. Верификация телефона звонком не сработает в трёх случаях: у абонента скрыт номер, звонок идёт через корпоративную АТС с подменой исходящего номера, или пользователь находится за пределами России — пул состоит из российских номеров. Разумная архитектура оставляет запасной способ подтверждения: звонок как основной сценарий, SMS как резервный для тех, у кого он не прошёл.",[14,687,689],{"id":688},"skolko-stoit-podtverzhdenie-nomera","Сколько стоит подтверждение номера",[10,691,692,693,696],{},"Одно подтверждение — ",[25,694,695],{},"0,20 ₽",". Абонентской платы нет, минимального ежемесячного объёма нет, за подключение платить не нужно.",[10,698,699,700,702],{},"Ключевое отличие от SMS — в модели списания. Деньги списываются в момент, когда звонок зафиксирован, то есть за результат. Проверка, по которой звонка не было и которая ушла в ",[181,701,202],{},", не тарифицируется вообще. Вы не платите за попытки, за недоставку и за пользователей, которые передумали на середине формы.",[67,704,706],{"className":705},[70],[72,707,708,721],{},[75,709,710],{},[78,711,712,715,718],{},[81,713,714],{},"Подтверждений в месяц",[81,716,717],{},"Стоимость при 100 % дозвона",[81,719,720],{},"При 80 % дозвона",[94,722,723,734,745],{},[78,724,725,728,731],{},[99,726,727],{},"1 000",[99,729,730],{},"200 ₽",[99,732,733],{},"160 ₽",[78,735,736,739,742],{},[99,737,738],{},"10 000",[99,740,741],{},"2 000 ₽",[99,743,744],{},"1 600 ₽",[78,746,747,750,753],{},[99,748,749],{},"100 000",[99,751,752],{},"20 000 ₽",[99,754,755],{},"16 000 ₽",[10,757,758],{},"Второй столбец — не скидка, а прямое следствие модели: если из десяти тысяч запущенных проверок звонком завершились восемь тысяч, платить вы будете за восемь. В SMS-сценарии заплатить пришлось бы за все десять тысяч отправок.",[10,760,761,762,766,767,61],{},"Проверка не создастся, если на балансе меньше стоимости одного подтверждения — API вернёт ошибку о недостатке средств. Это стоит обработать в коде заранее и завести оповещение о низком балансе. Актуальные цены и условия — на странице ",[57,763,765],{"href":764},"\u002Fpricing","тарифов","; подробнее о том, как вообще выбирать провайдера и на что смотреть кроме цены, — в разборе ",[57,768,770],{"href":769},"\u002Fblog\u002Fservis-podtverzhdeniya-nomera","сервисов подтверждения номера",[14,772,774],{"id":773},"gde-primenyayut-verifikaciyu-nomera-zvonkom","Где применяют верификацию номера звонком",[10,776,777],{},"Сценариев ровно столько же, сколько у SMS-кода, — способ подтверждения меняется, задача остаётся.",[19,779,780,785,795,800,806],{},[22,781,782,784],{},[25,783,27],{}," Отсечь фейковые аккаунты и получить рабочий контакт. Здесь короткий сценарий даёт больше всего: чем меньше шагов между «ввёл номер» и «зарегистрирован», тем меньше отвалов.",[22,786,787,790,791,61],{},[25,788,789],{},"Вход в аккаунт."," Замена SMS-кода при авторизации, в том числе полностью без пароля. Как это устроено — в материале про ",[57,792,794],{"href":793},"\u002Fblog\u002Favtorizaciya-po-zvonku","авторизацию по звонку",[22,796,797,799],{},[25,798,51],{}," Смена пароля, изменение реквизитов, подтверждение заказа — там, где нужно доказать, что действие совершает владелец аккаунта.",[22,801,802,805],{},[25,803,804],{},"Оформление заказа и доставка."," Проверка контактного номера до того, как заказ уйдёт в сборку, а курьер — по адресу с неверным телефоном.",[22,807,808,811],{},[25,809,810],{},"Объявления и маркетплейсы."," Верификация продавца, борьба с мультиаккаунтами и фейковыми регистрациями.",[10,813,814,815,61],{},"Разбор по отраслям с конкретными задачами — в статье ",[57,816,60],{"href":59},[14,818,820],{"id":819},"kak-podklyuchit","Как подключить",[10,822,823],{},"Интеграция занимает несколько часов и не требует SDK: это два HTTP-запроса к REST API.",[160,825,826,829,832,838],{},[22,827,828],{},"Зарегистрируйтесь и создайте проект — он получит собственный ключ.",[22,830,831],{},"На бэкенде вызовите создание проверки, передав ключ проекта и номер пользователя. Ключ хранится только на сервере: во фронтенде его размещать нельзя.",[22,833,834,835,837],{},"Отдайте выделенный номер во фронтенд и покажите его пользователю ссылкой ",[181,836,183],{}," с таймером на 60 секунд.",[22,839,840,841,843,844,846],{},"Опрашивайте статус проверки, пока он не станет ",[181,842,198],{}," или ",[181,845,202],{},", и обработайте оба исхода.",[10,848,849,850,852],{},"Пошаговое описание запросов, форматов и типичных ошибок интеграции — в материале про ",[57,851,210],{"href":209},". Инструкция с вашим собственным ключом открывается в кабинете на странице проекта сразу после регистрации.",{"title":335,"searchDepth":336,"depth":336,"links":854},[855,856,857,858,859,860,861],{"id":393,"depth":336,"text":394},{"id":493,"depth":336,"text":494},{"id":553,"depth":336,"text":554},{"id":665,"depth":336,"text":666},{"id":688,"depth":336,"text":689},{"id":773,"depth":336,"text":774},{"id":819,"depth":336,"text":820},"Как это работает",{"title":864,"text":865,"label":347,"href":348},"Попробуйте на своём проекте","Регистрация занимает минуту, ключ проекта выдаётся сразу. Тестовую проверку можно запустить с первого же запроса.","Разбираем, как подтвердить номер телефона звонком вместо SMS-кода: схема проверки, отличие от обычного flash call, стоимость и порядок подключения.",[868,871,874,877,880,883],{"q":869,"a":870},"Что будет, если пользователь позвонит с другого номера?","Проверка не подтвердится. Совпадение ищется по паре «выделенный номер + номер\nвызывающего абонента», и звонок с телефона, который не был указан при создании\nпроверки, к ней не привяжется. Через 60 секунд она перейдёт в статус `EXPIRED`,\nи деньги за неё списаны не будут.\n",{"q":872,"a":873},"Звонок платный для пользователя?","Оператор тарифицирует его как обычный вызов на российский номер, по тарифу абонента.\nНо соединение сбрасывается до ответа, а неотвеченные вызовы операторы не тарифицируют —\nна практике подтверждение обходится пользователю бесплатно.\n",{"q":875,"a":876},"Сработает ли проверка, если у вызывающего скрыт номер?","Нет. Определение номера вызывающего абонента — основа всего способа: если номер\nне передаётся, сопоставить звонок с проверкой не с чем. Так бывает при включённом\nантиопределителе и на корпоративных АТС, которые подменяют исходящий номер.\nДля таких пользователей нужен запасной способ подтверждения.\n",{"q":878,"a":879},"Что делать, если звонка так и не было?","Через 60 секунд после создания проверка сама переходит в `EXPIRED` — опрос статуса\nвернёт его вместо `PENDING`. Это штатная ситуация: покажите пользователю кнопку\n«Позвонить ещё раз» и создайте новую проверку. Тарифицируется только состоявшийся\nзвонок, поэтому повторные попытки ничего не стоят.\n",{"q":881,"a":882},"Сколько проверок можно запускать одновременно?","Ограничения на общее число параллельных проверок в проекте нет. Единственное\nтребование — уникальность пары «выделенный номер + номер звонящего» среди\nожидающих. Сервис сам подбирает подходящий номер из пула и распределяет по нему\nнагрузку, так что со стороны интеграции об этом думать не нужно.\n",{"q":884,"a":885},"Что произойдёт, если на балансе закончатся деньги?","Проверка просто не создастся: запрос на выдачу номера вернёт ошибку о недостатке\nсредств. Уже созданные проверки при этом дорабатывают штатно. Обработайте эту\nошибку в коде заранее — иначе форма регистрации молча перестанет работать,\nкогда баланс дойдёт до нуля.\n","Подтверждение номера телефона звонком: как это работает",{},1,[890,891,892],"avtorizaciya-po-zvonku","servis-podtverzhdeniya-nomera","chto-takoe-flash-call",{"title":382,"description":866},"blog\u002Fpodtverzhdenie-nomera-zvonkom","Подтверждение номера звонком — способ проверить телефон без SMS и без одноразового кода.\nСервис выдаёт выделенный номер, пользователь звонит на него со своего телефона, вызов\nсбрасывается, а система по номеру вызывающего абонента понимает, что телефон принадлежит\nименно ему. Вводить ничего не нужно, отвечать на звонок тоже. Проверка живёт 60 секунд\nи стоит 0,20 ₽, причём списание происходит только за состоявшийся звонок.\n","Vs3hHENdsOH5tdu4ypInD_PfVG5qPC2vwutGGv1h_7s",{"id":898,"title":899,"body":900,"category":343,"cta":1167,"description":1170,"draft":350,"extension":351,"faq":1171,"h1":1184,"meta":1185,"navigation":367,"order":1186,"path":152,"published":370,"related":1187,"seo":1189,"stem":1190,"summary":1191,"updated":370,"__hash__":1192},"blog\u002Fblog\u002Fpodtverzhdenie-nomera-v-prilozhenii.md","Подтверждение номера в приложении — как сделать без SMS",{"type":7,"value":901,"toc":1160},[902,905,909,916,919,922,925,929,961,964,968,971,983,992,1001,1015,1019,1092,1096,1099,1154],[10,903,904],{},"На мобильных сценарий подтверждения звонком раскрывается лучше всего: телефон, с которого нужно позвонить, — это то же устройство, где открыто приложение. Пользователю не приходится ничего переносить с экрана на экран.",[14,906,908],{"id":907},"chem-podtverzhdenie-v-prilozhenii-otlichaetsya-ot-sayta","Чем подтверждение в приложении отличается от сайта",[10,910,911,912,915],{},"Главное отличие одно, и оно решающее: ",[25,913,914],{},"приложение может открыть набор номера в одно касание",". Пользователь нажимает кнопку — открывается системный диалер с уже подставленным номером, остаётся нажать вызов.",[10,917,918],{},"На сайте с десктопа так не получится: номер придётся переносить на телефон руками, и это худшая точка сценария. В приложении её просто нет.",[10,920,921],{},"Второе отличие — контроль над экраном. Приложение точно знает, когда пользователь вернулся из диалера, и может проверить статус именно в этот момент, а не по таймеру. Переход к следующему шагу получается мгновенным.",[10,923,924],{},"Третье — обратная сторона: приложение уходит на задний план во время вызова, и это надо учитывать в логике, а не считать сбоем.",[14,926,928],{"id":927},"scenariy-so-zvonkom-chto-vidit-polzovatel","Сценарий со звонком: что видит пользователь",[160,930,931,937,943,949,955],{},[22,932,933,936],{},[25,934,935],{},"Экран ввода номера."," Поле в международном формате, подсказка про код страны, кнопка «Продолжить».",[22,938,939,942],{},[25,940,941],{},"Экран подтверждения."," Крупная кнопка «Позвонить», под ней — номер текстом на случай, если диалер не откроется. Пояснение: трубку брать не нужно, вызов оборвётся сам. Таймер на 60 секунд.",[22,944,945,948],{},[25,946,947],{},"Системный диалер."," Открывается с подставленным номером. Пользователь нажимает вызов, соединение сбрасывается через мгновение.",[22,950,951,954],{},[25,952,953],{},"Возврат в приложение."," Статус уже подтверждён — экран меняется сам, нажимать ничего не нужно.",[22,956,957,960],{},[25,958,959],{},"Если не сложилось."," По истечении таймера — кнопка «Позвонить ещё раз» и ссылка на запасной способ.",[10,962,963],{},"Формулировки на втором экране важнее, чем кажется. «Позвоните на этот номер, трубку брать не нужно» пользователь понимает сразу. «Пройдите верификацию» — нет.",[14,965,967],{"id":966},"realizaciya-na-ios-i-android","Реализация на iOS и Android",[10,969,970],{},"Логика одинаковая, различается только открытие диалера.",[10,972,973,976,977,979,980,61],{},[25,974,975],{},"Общая часть."," Бэкенд создаёт проверку и получает выделенный номер; приложение показывает его и опрашивает статус. Сам механизм проверки разобран в статье про ",[57,978,148],{"href":147}," — он одинаков для всех платформ. Ключ проекта в приложении хранить нельзя: его место на сервере, а мобильный клиент ходит в ваш собственный API. Веб-версия того же сценария описана в материале про ",[57,981,982],{"href":369},"подтверждение номера на сайте",[10,984,985,988,989,991],{},[25,986,987],{},"Открытие набора."," На iOS — системная ссылка вида ",[181,990,183],{},", на Android — намерение набора номера. В обоих случаях открывается штатный диалер с подставленным номером; сам вызов инициирует пользователь. Автоматически совершать звонок за него не нужно и не следует: это требует отдельного разрешения и выглядит подозрительно.",[10,993,994,997,998,61],{},[25,995,996],{},"Разрешения не нужны."," Здесь ключевое отличие от обычного flash call, при котором приложение считывает номер входящего вызова из журнала. Такой доступ ограничен политиками магазинов: в Google Play выдача разрешения на журнал вызовов требует обоснования, а в iOS публичного API для чтения журнала нет вовсе. При обратном сценарии читать нечего — звонок видит сервис на своей стороне. Разбор разницы — в статье ",[57,999,1000],{"href":661},"что такое flash call",[10,1002,1003,1006,1007,1009,1010,1012,1013,61],{},[25,1004,1005],{},"Опрос статуса."," Периодический запрос с разумным интервалом плюс обязательная внеочередная проверка при возврате приложения на передний план. Опрос прекращается по ",[181,1008,198],{},", по ",[181,1011,202],{}," или по истечении таймера. Детали запросов — в материале про ",[57,1014,210],{"href":209},[14,1016,1018],{"id":1017},"obrabotka-granichnyh-sluchaev","Обработка граничных случаев",[67,1020,1022],{"className":1021},[70],[72,1023,1024,1034],{},[75,1025,1026],{},[78,1027,1028,1031],{},[81,1029,1030],{},"Ситуация",[81,1032,1033],{},"Что делать",[94,1035,1036,1044,1052,1060,1068,1076,1084],{},[78,1037,1038,1041],{},[99,1039,1040],{},"Приложение свёрнуто во время вызова",[99,1042,1043],{},"проверять статус при возврате на передний план, не только по таймеру",[78,1045,1046,1049],{},[99,1047,1048],{},"Пользователь отменил вызов в диалере",[99,1050,1051],{},"оставить экран активным, таймер продолжает идти — он может позвонить снова",[78,1053,1054,1057],{},[99,1055,1056],{},"Звонок с другого номера",[99,1058,1059],{},"проверка не подтвердится; в подсказке заранее написать, что звонить нужно с указанного номера",[78,1061,1062,1065],{},[99,1063,1064],{},"Нет сигнала сотовой сети",[99,1066,1067],{},"предложить запасной способ подтверждения",[78,1069,1070,1073],{},[99,1071,1072],{},"Диалер не открылся",[99,1074,1075],{},"номер показан текстом, дать кнопку копирования",[78,1077,1078,1081],{},[99,1079,1080],{},"Истекли 60 секунд",[99,1082,1083],{},"показать «Позвонить ещё раз», создать новую проверку",[78,1085,1086,1089],{},[99,1087,1088],{},"Приложение закрыто до подтверждения",[99,1090,1091],{},"при следующем запуске начинать сценарий заново",[14,1093,1095],{"id":1094},"chto-proverit-pered-otpravkoy-v-magazin","Что проверить перед отправкой в магазин",[10,1097,1098],{},"Короткий чек-лист по местам, где мобильная реализация обычно ломается.",[19,1100,1101,1107,1113,1119,1130,1136,1142,1148],{},[22,1102,1103,1106],{},[25,1104,1105],{},"Диалер открывается на реальном устройстве",", а не только в эмуляторе. На части прошивок поведение отличается.",[22,1108,1109,1112],{},[25,1110,1111],{},"Номер показан текстом рядом с кнопкой."," Если системный набор не открылся, пользователю нужен запасной путь — скопировать и набрать вручную.",[22,1114,1115,1118],{},[25,1116,1117],{},"Статус проверяется при возврате на передний план."," Без этого пользователь возвращается на экран ожидания, который уже неактуален.",[22,1120,1121,1124,1125,297,1127,1129],{},[25,1122,1123],{},"Опрос останавливается."," Три условия: ",[181,1126,198],{},[181,1128,202],{},", истёкший таймер. Иначе запросы продолжатся в фоне и посадят батарею.",[22,1131,1132,1135],{},[25,1133,1134],{},"Ключ проекта не лежит в приложении."," Даже в обфусцированном виде: мобильный клиент разбирается, а ключ тратит ваш баланс. Приложение ходит в ваш собственный API.",[22,1137,1138,1141],{},[25,1139,1140],{},"Экран переживает поворот и сворачивание."," Идентификатор проверки и остаток таймера должны сохраняться.",[22,1143,1144,1147],{},[25,1145,1146],{},"Есть запасной способ подтверждения"," для тех, у кого номер не определяется.",[22,1149,1150,1153],{},[25,1151,1152],{},"Тексты понятны без терминов."," «Позвоните на этот номер, трубку брать не нужно» вместо «пройдите верификацию».",[10,1155,1156,1157,1159],{},"Отдельно про повторные попытки: они ничего не стоят. Тарифицируется только состоявшийся звонок, проверка в статусе ",[181,1158,202],{}," не списывает баланс — поэтому предлагать позвонить ещё раз можно без оглядки на расходы.",{"title":335,"searchDepth":336,"depth":336,"links":1161},[1162,1163,1164,1165,1166],{"id":907,"depth":336,"text":908},{"id":927,"depth":336,"text":928},{"id":966,"depth":336,"text":967},{"id":1017,"depth":336,"text":1018},{"id":1094,"depth":336,"text":1095},{"title":1168,"text":1169,"label":347,"href":348},"Встройте подтверждение в своё приложение","Два HTTP-запроса, никаких SDK и разрешений. Ключ проекта выдаётся сразу после регистрации.","Как реализовать подтверждение номера в мобильном приложении: сценарий со звонком вместо SMS, работа без разрешений, экраны, обработка граничных случаев.",[1172,1175,1178,1181],{"q":1173,"a":1174},"Нужны ли разрешения на чтение SMS или журнала вызовов?","Нет, и это ключевое отличие сценария. Приложение только открывает системный\nдиалер и опрашивает статус проверки по сети. Читать сообщения и журнал\nвызовов не требуется, поэтому и запрашивать соответствующие разрешения\nне нужно.\n",{"q":1176,"a":1177},"Пройдёт ли такое приложение модерацию в магазинах?","С этой стороны придираться не к чему: чувствительные разрешения не запрашиваются,\nфоновой активности нет, всё общение с сервисом идёт обычными HTTP-запросами.\nИменно из-за разрешений обычный flash call часто и не проходит.\n",{"q":1179,"a":1180},"Что если пользователь свернул приложение во время звонка?","Так и будет — набор номера сворачивает приложение по определению. Поэтому\nстатус нужно проверять и при возврате на передний план, а не только по таймеру:\nк этому моменту проверка обычно уже подтверждена.\n",{"q":1182,"a":1183},"Нужен ли отдельный код для iOS и Android?","Отличается только способ открыть диалер — системная ссылка на iOS и намерение\nнабора на Android. Вся логика проверки одинаковая: создать, показать номер,\nопросить статус.\n","Подтверждение номера телефона в мобильном приложении",{},8,[372,1188,892],"podtverzhdenie-nomera-na-sayte",{"title":899,"description":1170},"blog\u002Fpodtverzhdenie-nomera-v-prilozhenii","В мобильном приложении подтверждение номера звонком удобнее, чем где-либо ещё: набор\nоткрывается одним касанием через системный диалер, пользователю не нужно ничего\nпереписывать и вводить. Главное преимущество перед обычным flash call — приложению\nне требуются разрешения на чтение SMS и журнала вызовов, а их выдачу ограничивают\nи Google Play, и App Store. Проверка живёт 60 секунд, стоит 0,20 ₽ за состоявшийся звонок.\n","SzYBtnQ2PPpUxp6ImUb7w9yWpfYtOF4vzcvV-g9wfsk",{"id":1194,"title":1195,"body":1196,"category":343,"cta":3801,"description":3805,"draft":350,"extension":351,"faq":3806,"h1":3822,"meta":3823,"navigation":367,"order":1851,"path":209,"published":370,"related":3824,"seo":3825,"stem":3826,"summary":3827,"updated":370,"__hash__":3828},"blog\u002Fblog\u002Fapi-podtverzhdeniya-nomera.md","API подтверждения номера телефона — REST с примерами",{"type":7,"value":1197,"toc":3784},[1198,1201,1205,1261,1268,1277,1281,1330,1333,1368,1381,1384,1446,1450,1456,1469,1473,1485,1561,1567,1570,1592,1596,1601,2185,2189,2352,2356,2504,2508,2515,2519,3047,3051,3655,3659,3711,3715,3772,3775,3780],[10,1199,1200],{},"Интеграция подтверждения номера обычно занимает не больше рабочего дня. Ниже — весь путь: от первого запроса до обработки граничных случаев, с примерами, которые можно копировать.",[14,1202,1204],{"id":1203},"kak-ustroen-api","Как устроен API",[19,1206,1207,1216,1222,1240,1248],{},[22,1208,1209,1212,1213],{},[25,1210,1211],{},"Базовый адрес:"," ",[181,1214,1215],{},"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1",[22,1217,1218,1221],{},[25,1219,1220],{},"Формат:"," JSON, обычный REST.",[22,1223,1224,1227,1228,1231,1232,1235,1236,1239],{},[25,1225,1226],{},"Авторизация:"," ключ проекта передаётся полем ",[181,1229,1230],{},"token"," — в теле ",[181,1233,1234],{},"POST \u002Fget-phone"," или в query-строке ",[181,1237,1238],{},"GET \u002Fstatus",". Отдельных заголовков авторизации нет.",[22,1241,1242,1245,1246,61],{},[25,1243,1244],{},"Формат номера:"," E.164 — знак плюс, код страны и номер без разделителей: ",[181,1247,508],{},[22,1249,1250,1212,1253,1255,1256,537,1258,1260],{},[25,1251,1252],{},"Статусы проверки:",[181,1254,533],{}," — ждём звонок, ",[181,1257,198],{},[181,1259,202],{}," — за 60 секунд звонка не было.",[10,1262,1263,1264,1267],{},"Всего два запроса. Третий эндпоинт, ",[181,1265,1266],{},"POST \u002Finbound-call",", вызывает оператор телефонии при поступлении звонка — со стороны интеграции он не используется.",[10,1269,1270,1271,1273,1274,61],{},"Если механизм в целом ещё не знаком, начните с разбора в статье про ",[57,1272,148],{"href":147},": дальше по тексту предполагается, что схема «выдали номер — пользователь позвонил — забрали статус» уже понятна. Модель тарификации и критерии выбора провайдера — в материале про ",[57,1275,1276],{"href":769},"сервисы подтверждения номера",[14,1278,1280],{"id":1279},"shag-1-sozdat-proverku","Шаг 1. Создать проверку",[1282,1283,1287],"pre",{"className":1284,"code":1285,"language":1286,"meta":335,"style":335},"language-bash shiki shiki-themes github-light github-dark","curl -X POST \"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1\u002Fget-phone\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{ \"token\": \"YOUR_API_KEY\", \"phone\": \"+79161234567\" }'\n","bash",[181,1288,1289,1311,1321],{"__ignoreMap":335},[1290,1291,1293,1297,1301,1305,1308],"span",{"class":1292,"line":888},"line",[1290,1294,1296],{"class":1295},"sScJk","curl",[1290,1298,1300],{"class":1299},"sj4cs"," -X",[1290,1302,1304],{"class":1303},"sZZnC"," POST",[1290,1306,1307],{"class":1303}," \"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1\u002Fget-phone\"",[1290,1309,1310],{"class":1299}," \\\n",[1290,1312,1313,1316,1319],{"class":1292,"line":336},[1290,1314,1315],{"class":1299},"  -H",[1290,1317,1318],{"class":1303}," \"Content-Type: application\u002Fjson\"",[1290,1320,1310],{"class":1299},[1290,1322,1324,1327],{"class":1292,"line":1323},3,[1290,1325,1326],{"class":1299},"  -d",[1290,1328,1329],{"class":1303}," '{ \"token\": \"YOUR_API_KEY\", \"phone\": \"+79161234567\" }'\n",[10,1331,1332],{},"Ответ:",[1282,1334,1338],{"className":1335,"code":1336,"language":1337,"meta":335,"style":335},"language-json shiki shiki-themes github-light github-dark","{ \"id\": \"c1x8f2k9b0001\", \"phone\": \"+74950000000\" }\n","json",[181,1339,1340],{"__ignoreMap":335},[1290,1341,1342,1346,1349,1352,1355,1357,1360,1362,1365],{"class":1292,"line":888},[1290,1343,1345],{"class":1344},"sVt8B","{ ",[1290,1347,1348],{"class":1299},"\"id\"",[1290,1350,1351],{"class":1344},": ",[1290,1353,1354],{"class":1303},"\"c1x8f2k9b0001\"",[1290,1356,297],{"class":1344},[1290,1358,1359],{"class":1299},"\"phone\"",[1290,1361,1351],{"class":1344},[1290,1363,1364],{"class":1303},"\"+74950000000\"",[1290,1366,1367],{"class":1344}," }\n",[10,1369,1370,1373,1374,1377,1378,1380],{},[181,1371,1372],{},"id"," — идентификатор проверки, по нему запрашивается статус. ",[181,1375,1376],{},"phone"," — выделенный номер, на который пользователь должен позвонить. Проверка создаётся в статусе ",[181,1379,533],{}," и живёт 60 секунд.",[10,1382,1383],{},"Возможные ошибки:",[67,1385,1387],{"className":1386},[70],[72,1388,1389,1401],{},[75,1390,1391],{},[78,1392,1393,1396,1399],{},[81,1394,1395],{},"Код",[81,1397,1398],{},"Причина",[81,1400,1033],{},[94,1402,1403,1414,1424,1435],{},[78,1404,1405,1408,1411],{},[99,1406,1407],{},"400",[99,1409,1410],{},"неверный формат номера",[99,1412,1413],{},"привести к E.164 до отправки",[78,1415,1416,1418,1421],{},[99,1417,1407],{},[99,1419,1420],{},"недостаточно средств на балансе",[99,1422,1423],{},"пополнить, заранее завести оповещение",[78,1425,1426,1429,1432],{},[99,1427,1428],{},"404",[99,1430,1431],{},"ключ проекта не найден",[99,1433,1434],{},"проверить ключ и окружение",[78,1436,1437,1440,1443],{},[99,1438,1439],{},"503",[99,1441,1442],{},"нет доступных номеров в пуле",[99,1444,1445],{},"повторить попытку, показать запасной способ",[14,1447,1449],{"id":1448},"shag-2-pokazat-nomer-polzovatelyu","Шаг 2. Показать номер пользователю",[10,1451,1452,1453,1455],{},"Во фронтенд отдаются только ",[181,1454,1372],{}," и выделенный номер — ключ проекта наружу не уходит.",[10,1457,1458,1459,1461,1462,1465,1466,61],{},"Что должно быть на экране: номер крупно и с разделителями, на мобильных — ссылкой ",[181,1460,183],{},"; таймер на 60 секунд; пояснение, что трубку брать не нужно; подсказка, что звонить надо именно с подтверждаемого номера. Разбор интерфейса — в материалах про ",[57,1463,1464],{"href":369},"подтверждение на сайте"," и ",[57,1467,1468],{"href":152},"в мобильном приложении",[14,1470,1472],{"id":1471},"shag-3-poluchit-rezultat","Шаг 3. Получить результат",[1282,1474,1476],{"className":1284,"code":1475,"language":1286,"meta":335,"style":335},"curl \"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1\u002Fstatus?token=YOUR_API_KEY&id=c1x8f2k9b0001\"\n",[181,1477,1478],{"__ignoreMap":335},[1290,1479,1480,1482],{"class":1292,"line":888},[1290,1481,1296],{"class":1295},[1290,1483,1484],{"class":1303}," \"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1\u002Fstatus?token=YOUR_API_KEY&id=c1x8f2k9b0001\"\n",[1282,1486,1488],{"className":1335,"code":1487,"language":1337,"meta":335,"style":335},"{\n  \"id\": \"c1x8f2k9b0001\",\n  \"status\": \"PENDING\",\n  \"callerPhone\": \"+79161234567\",\n  \"cost\": \"0.20\",\n  \"createdAt\": \"2026-08-08T18:00:00.000Z\"\n}\n",[181,1489,1490,1495,1507,1519,1532,1545,1556],{"__ignoreMap":335},[1290,1491,1492],{"class":1292,"line":888},[1290,1493,1494],{"class":1344},"{\n",[1290,1496,1497,1500,1502,1504],{"class":1292,"line":336},[1290,1498,1499],{"class":1299},"  \"id\"",[1290,1501,1351],{"class":1344},[1290,1503,1354],{"class":1303},[1290,1505,1506],{"class":1344},",\n",[1290,1508,1509,1512,1514,1517],{"class":1292,"line":1323},[1290,1510,1511],{"class":1299},"  \"status\"",[1290,1513,1351],{"class":1344},[1290,1515,1516],{"class":1303},"\"PENDING\"",[1290,1518,1506],{"class":1344},[1290,1520,1522,1525,1527,1530],{"class":1292,"line":1521},4,[1290,1523,1524],{"class":1299},"  \"callerPhone\"",[1290,1526,1351],{"class":1344},[1290,1528,1529],{"class":1303},"\"+79161234567\"",[1290,1531,1506],{"class":1344},[1290,1533,1535,1538,1540,1543],{"class":1292,"line":1534},5,[1290,1536,1537],{"class":1299},"  \"cost\"",[1290,1539,1351],{"class":1344},[1290,1541,1542],{"class":1303},"\"0.20\"",[1290,1544,1506],{"class":1344},[1290,1546,1548,1551,1553],{"class":1292,"line":1547},6,[1290,1549,1550],{"class":1299},"  \"createdAt\"",[1290,1552,1351],{"class":1344},[1290,1554,1555],{"class":1303},"\"2026-08-08T18:00:00.000Z\"\n",[1290,1557,1558],{"class":1292,"line":368},[1290,1559,1560],{"class":1344},"}\n",[10,1562,1563,1566],{},[25,1564,1565],{},"Вебхуков нет"," — результат забирается опросом. Это заметно упрощает интеграцию: не нужен публично доступный обработчик, не нужна проверка подписи, не нужна защита от повторной доставки.",[10,1568,1569],{},"Правила опроса:",[19,1571,1572,1575,1583,1586],{},[22,1573,1574],{},"Интервал одна-две секунды. Чаще бессмысленно, реже — заметная задержка на экране.",[22,1576,1577,1578,297,1580,1582],{},"Три условия остановки: ",[181,1579,198],{},[181,1581,202],{}," и истечение локального таймера. Без последнего цикл может не завершиться никогда.",[22,1584,1585],{},"Решение «номер подтверждён» принимается на сервере по ответу API, а не по тому, что прислал браузер.",[22,1587,1588,1589,1591],{},"Привязка номера должна отрабатывать один раз, даже если ",[181,1590,198],{}," увиден несколькими запросами подряд.",[14,1593,1595],{"id":1594},"primery-integracii","Примеры интеграции",[1597,1598,1600],"h3",{"id":1599},"node-js","Node.js",[1282,1602,1606],{"className":1603,"code":1604,"language":1605,"meta":335,"style":335},"language-js shiki shiki-themes github-light github-dark","const API = 'https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1'\nconst TOKEN = process.env.FLASHCALL_TOKEN\n\nexport async function createVerification(phone) {\n  const res = await fetch(`${API}\u002Fget-phone`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ token: TOKEN, phone }),\n  })\n  if (!res.ok) throw new Error(`get-phone: ${res.status}`)\n\n  \u002F\u002F callTo показываем пользователю — на него нужно позвонить\n  const { id, phone: callTo } = await res.json()\n  return { id, callTo }\n}\n\nexport async function getStatus(id) {\n  const url = `${API}\u002Fstatus?token=${encodeURIComponent(TOKEN)}&id=${encodeURIComponent(id)}`\n  const res = await fetch(url)\n  if (!res.ok) throw new Error(`status: ${res.status}`)\n  return (await res.json()).status\n}\n\n\u002F** Ждём результат, пока проверка жива: TTL 60 секунд. *\u002F\nexport async function waitForCall(id, timeoutMs = 60_000) {\n  const deadline = Date.now() + timeoutMs\n  while (Date.now() \u003C deadline) {\n    const status = await getStatus(id)\n    if (status === 'CONFIRMED') return true\n    if (status === 'EXPIRED') return false\n    await new Promise((r) => setTimeout(r, 2000))\n  }\n  return false\n}\n","js",[181,1607,1608,1623,1638,1643,1666,1696,1706,1722,1744,1750,1793,1798,1805,1840,1849,1854,1859,1877,1919,1935,1967,1984,1989,1994,2000,2028,2053,2072,2090,2114,2133,2167,2173,2180],{"__ignoreMap":335},[1290,1609,1610,1614,1617,1620],{"class":1292,"line":888},[1290,1611,1613],{"class":1612},"szBVR","const",[1290,1615,1616],{"class":1299}," API",[1290,1618,1619],{"class":1612}," =",[1290,1621,1622],{"class":1303}," 'https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1'\n",[1290,1624,1625,1627,1630,1632,1635],{"class":1292,"line":336},[1290,1626,1613],{"class":1612},[1290,1628,1629],{"class":1299}," TOKEN",[1290,1631,1619],{"class":1612},[1290,1633,1634],{"class":1344}," process.env.",[1290,1636,1637],{"class":1299},"FLASHCALL_TOKEN\n",[1290,1639,1640],{"class":1292,"line":1323},[1290,1641,1642],{"emptyLinePlaceholder":367},"\n",[1290,1644,1645,1648,1651,1654,1657,1660,1663],{"class":1292,"line":1521},[1290,1646,1647],{"class":1612},"export",[1290,1649,1650],{"class":1612}," async",[1290,1652,1653],{"class":1612}," function",[1290,1655,1656],{"class":1295}," createVerification",[1290,1658,1659],{"class":1344},"(",[1290,1661,1376],{"class":1662},"s4XuR",[1290,1664,1665],{"class":1344},") {\n",[1290,1667,1668,1671,1674,1676,1679,1682,1684,1687,1690,1693],{"class":1292,"line":1534},[1290,1669,1670],{"class":1612},"  const",[1290,1672,1673],{"class":1299}," res",[1290,1675,1619],{"class":1612},[1290,1677,1678],{"class":1612}," await",[1290,1680,1681],{"class":1295}," fetch",[1290,1683,1659],{"class":1344},[1290,1685,1686],{"class":1303},"`${",[1290,1688,1689],{"class":1299},"API",[1290,1691,1692],{"class":1303},"}\u002Fget-phone`",[1290,1694,1695],{"class":1344},", {\n",[1290,1697,1698,1701,1704],{"class":1292,"line":1547},[1290,1699,1700],{"class":1344},"    method: ",[1290,1702,1703],{"class":1303},"'POST'",[1290,1705,1506],{"class":1344},[1290,1707,1708,1711,1714,1716,1719],{"class":1292,"line":368},[1290,1709,1710],{"class":1344},"    headers: { ",[1290,1712,1713],{"class":1303},"'Content-Type'",[1290,1715,1351],{"class":1344},[1290,1717,1718],{"class":1303},"'application\u002Fjson'",[1290,1720,1721],{"class":1344}," },\n",[1290,1723,1724,1727,1730,1732,1735,1738,1741],{"class":1292,"line":1186},[1290,1725,1726],{"class":1344},"    body: ",[1290,1728,1729],{"class":1299},"JSON",[1290,1731,61],{"class":1344},[1290,1733,1734],{"class":1295},"stringify",[1290,1736,1737],{"class":1344},"({ token: ",[1290,1739,1740],{"class":1299},"TOKEN",[1290,1742,1743],{"class":1344},", phone }),\n",[1290,1745,1747],{"class":1292,"line":1746},9,[1290,1748,1749],{"class":1344},"  })\n",[1290,1751,1753,1756,1759,1762,1765,1768,1771,1774,1776,1779,1782,1784,1787,1790],{"class":1292,"line":1752},10,[1290,1754,1755],{"class":1612},"  if",[1290,1757,1758],{"class":1344}," (",[1290,1760,1761],{"class":1612},"!",[1290,1763,1764],{"class":1344},"res.ok) ",[1290,1766,1767],{"class":1612},"throw",[1290,1769,1770],{"class":1612}," new",[1290,1772,1773],{"class":1295}," Error",[1290,1775,1659],{"class":1344},[1290,1777,1778],{"class":1303},"`get-phone: ${",[1290,1780,1781],{"class":1344},"res",[1290,1783,61],{"class":1303},[1290,1785,1786],{"class":1344},"status",[1290,1788,1789],{"class":1303},"}`",[1290,1791,1792],{"class":1344},")\n",[1290,1794,1796],{"class":1292,"line":1795},11,[1290,1797,1642],{"emptyLinePlaceholder":367},[1290,1799,1801],{"class":1292,"line":1800},12,[1290,1802,1804],{"class":1803},"sJ8bj","  \u002F\u002F callTo показываем пользователю — на него нужно позвонить\n",[1290,1806,1808,1810,1813,1815,1817,1819,1821,1824,1827,1830,1832,1835,1837],{"class":1292,"line":1807},13,[1290,1809,1670],{"class":1612},[1290,1811,1812],{"class":1344}," { ",[1290,1814,1372],{"class":1299},[1290,1816,297],{"class":1344},[1290,1818,1376],{"class":1662},[1290,1820,1351],{"class":1344},[1290,1822,1823],{"class":1299},"callTo",[1290,1825,1826],{"class":1344}," } ",[1290,1828,1829],{"class":1612},"=",[1290,1831,1678],{"class":1612},[1290,1833,1834],{"class":1344}," res.",[1290,1836,1337],{"class":1295},[1290,1838,1839],{"class":1344},"()\n",[1290,1841,1843,1846],{"class":1292,"line":1842},14,[1290,1844,1845],{"class":1612},"  return",[1290,1847,1848],{"class":1344}," { id, callTo }\n",[1290,1850,1852],{"class":1292,"line":1851},15,[1290,1853,1560],{"class":1344},[1290,1855,1857],{"class":1292,"line":1856},16,[1290,1858,1642],{"emptyLinePlaceholder":367},[1290,1860,1862,1864,1866,1868,1871,1873,1875],{"class":1292,"line":1861},17,[1290,1863,1647],{"class":1612},[1290,1865,1650],{"class":1612},[1290,1867,1653],{"class":1612},[1290,1869,1870],{"class":1295}," getStatus",[1290,1872,1659],{"class":1344},[1290,1874,1372],{"class":1662},[1290,1876,1665],{"class":1344},[1290,1878,1880,1882,1885,1887,1890,1892,1895,1898,1900,1902,1905,1908,1910,1912,1914,1916],{"class":1292,"line":1879},18,[1290,1881,1670],{"class":1612},[1290,1883,1884],{"class":1299}," url",[1290,1886,1619],{"class":1612},[1290,1888,1889],{"class":1303}," `${",[1290,1891,1689],{"class":1299},[1290,1893,1894],{"class":1303},"}\u002Fstatus?token=${",[1290,1896,1897],{"class":1295},"encodeURIComponent",[1290,1899,1659],{"class":1303},[1290,1901,1740],{"class":1299},[1290,1903,1904],{"class":1303},")",[1290,1906,1907],{"class":1303},"}&id=${",[1290,1909,1897],{"class":1295},[1290,1911,1659],{"class":1303},[1290,1913,1372],{"class":1344},[1290,1915,1904],{"class":1303},[1290,1917,1918],{"class":1303},"}`\n",[1290,1920,1922,1924,1926,1928,1930,1932],{"class":1292,"line":1921},19,[1290,1923,1670],{"class":1612},[1290,1925,1673],{"class":1299},[1290,1927,1619],{"class":1612},[1290,1929,1678],{"class":1612},[1290,1931,1681],{"class":1295},[1290,1933,1934],{"class":1344},"(url)\n",[1290,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954,1957,1959,1961,1963,1965],{"class":1292,"line":1937},20,[1290,1939,1755],{"class":1612},[1290,1941,1758],{"class":1344},[1290,1943,1761],{"class":1612},[1290,1945,1764],{"class":1344},[1290,1947,1767],{"class":1612},[1290,1949,1770],{"class":1612},[1290,1951,1773],{"class":1295},[1290,1953,1659],{"class":1344},[1290,1955,1956],{"class":1303},"`status: ${",[1290,1958,1781],{"class":1344},[1290,1960,61],{"class":1303},[1290,1962,1786],{"class":1344},[1290,1964,1789],{"class":1303},[1290,1966,1792],{"class":1344},[1290,1968,1970,1972,1974,1977,1979,1981],{"class":1292,"line":1969},21,[1290,1971,1845],{"class":1612},[1290,1973,1758],{"class":1344},[1290,1975,1976],{"class":1612},"await",[1290,1978,1834],{"class":1344},[1290,1980,1337],{"class":1295},[1290,1982,1983],{"class":1344},"()).status\n",[1290,1985,1987],{"class":1292,"line":1986},22,[1290,1988,1560],{"class":1344},[1290,1990,1992],{"class":1292,"line":1991},23,[1290,1993,1642],{"emptyLinePlaceholder":367},[1290,1995,1997],{"class":1292,"line":1996},24,[1290,1998,1999],{"class":1803},"\u002F** Ждём результат, пока проверка жива: TTL 60 секунд. *\u002F\n",[1290,2001,2003,2005,2007,2009,2012,2014,2016,2018,2021,2023,2026],{"class":1292,"line":2002},25,[1290,2004,1647],{"class":1612},[1290,2006,1650],{"class":1612},[1290,2008,1653],{"class":1612},[1290,2010,2011],{"class":1295}," waitForCall",[1290,2013,1659],{"class":1344},[1290,2015,1372],{"class":1662},[1290,2017,297],{"class":1344},[1290,2019,2020],{"class":1662},"timeoutMs",[1290,2022,1619],{"class":1612},[1290,2024,2025],{"class":1299}," 60_000",[1290,2027,1665],{"class":1344},[1290,2029,2031,2033,2036,2038,2041,2044,2047,2050],{"class":1292,"line":2030},26,[1290,2032,1670],{"class":1612},[1290,2034,2035],{"class":1299}," deadline",[1290,2037,1619],{"class":1612},[1290,2039,2040],{"class":1344}," Date.",[1290,2042,2043],{"class":1295},"now",[1290,2045,2046],{"class":1344},"() ",[1290,2048,2049],{"class":1612},"+",[1290,2051,2052],{"class":1344}," timeoutMs\n",[1290,2054,2056,2059,2062,2064,2066,2069],{"class":1292,"line":2055},27,[1290,2057,2058],{"class":1612},"  while",[1290,2060,2061],{"class":1344}," (Date.",[1290,2063,2043],{"class":1295},[1290,2065,2046],{"class":1344},[1290,2067,2068],{"class":1612},"\u003C",[1290,2070,2071],{"class":1344}," deadline) {\n",[1290,2073,2075,2078,2081,2083,2085,2087],{"class":1292,"line":2074},28,[1290,2076,2077],{"class":1612},"    const",[1290,2079,2080],{"class":1299}," status",[1290,2082,1619],{"class":1612},[1290,2084,1678],{"class":1612},[1290,2086,1870],{"class":1295},[1290,2088,2089],{"class":1344},"(id)\n",[1290,2091,2093,2096,2099,2102,2105,2108,2111],{"class":1292,"line":2092},29,[1290,2094,2095],{"class":1612},"    if",[1290,2097,2098],{"class":1344}," (status ",[1290,2100,2101],{"class":1612},"===",[1290,2103,2104],{"class":1303}," 'CONFIRMED'",[1290,2106,2107],{"class":1344},") ",[1290,2109,2110],{"class":1612},"return",[1290,2112,2113],{"class":1299}," true\n",[1290,2115,2117,2119,2121,2123,2126,2128,2130],{"class":1292,"line":2116},30,[1290,2118,2095],{"class":1612},[1290,2120,2098],{"class":1344},[1290,2122,2101],{"class":1612},[1290,2124,2125],{"class":1303}," 'EXPIRED'",[1290,2127,2107],{"class":1344},[1290,2129,2110],{"class":1612},[1290,2131,2132],{"class":1299}," false\n",[1290,2134,2136,2139,2141,2144,2147,2150,2152,2155,2158,2161,2164],{"class":1292,"line":2135},31,[1290,2137,2138],{"class":1612},"    await",[1290,2140,1770],{"class":1612},[1290,2142,2143],{"class":1299}," Promise",[1290,2145,2146],{"class":1344},"((",[1290,2148,2149],{"class":1662},"r",[1290,2151,2107],{"class":1344},[1290,2153,2154],{"class":1612},"=>",[1290,2156,2157],{"class":1295}," setTimeout",[1290,2159,2160],{"class":1344},"(r, ",[1290,2162,2163],{"class":1299},"2000",[1290,2165,2166],{"class":1344},"))\n",[1290,2168,2170],{"class":1292,"line":2169},32,[1290,2171,2172],{"class":1344},"  }\n",[1290,2174,2176,2178],{"class":1292,"line":2175},33,[1290,2177,1845],{"class":1612},[1290,2179,2132],{"class":1299},[1290,2181,2183],{"class":1292,"line":2182},34,[1290,2184,1560],{"class":1344},[1597,2186,2188],{"id":2187},"php","PHP",[1282,2190,2193],{"className":2191,"code":2192,"language":2187,"meta":335,"style":335},"language-php shiki shiki-themes github-light github-dark","\u003C?php\nconst FLASHCALL_API = 'https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1';\n\nfunction flashcallRequest(string $url, ?array $body = null): array {\n    $ch = curl_init($url);\n    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n    curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n    if ($body !== null) {\n        curl_setopt($ch, CURLOPT_POST, true);\n        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));\n        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application\u002Fjson']);\n    }\n    $response = curl_exec($ch);\n    $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\n    curl_close($ch);\n\n    if ($code >= 400) {\n        throw new RuntimeException(\"FlashCall API вернул $code: $response\");\n    }\n    return json_decode($response, true);\n}\n\nfunction createVerification(string $phone): array {\n    return flashcallRequest(FLASHCALL_API . '\u002Fget-phone', [\n        'token' => getenv('FLASHCALL_TOKEN'),\n        'phone' => $phone,\n    ]);\n}\n\nfunction getStatus(string $id): string {\n    $query = http_build_query(['token' => getenv('FLASHCALL_TOKEN'), 'id' => $id]);\n    return flashcallRequest(FLASHCALL_API . '\u002Fstatus?' . $query)['status'];\n}\n",[181,2194,2195,2200,2205,2209,2214,2219,2224,2229,2234,2239,2244,2249,2254,2259,2264,2269,2273,2278,2283,2287,2292,2296,2300,2305,2310,2315,2320,2325,2329,2333,2338,2343,2348],{"__ignoreMap":335},[1290,2196,2197],{"class":1292,"line":888},[1290,2198,2199],{},"\u003C?php\n",[1290,2201,2202],{"class":1292,"line":336},[1290,2203,2204],{},"const FLASHCALL_API = 'https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1';\n",[1290,2206,2207],{"class":1292,"line":1323},[1290,2208,1642],{"emptyLinePlaceholder":367},[1290,2210,2211],{"class":1292,"line":1521},[1290,2212,2213],{},"function flashcallRequest(string $url, ?array $body = null): array {\n",[1290,2215,2216],{"class":1292,"line":1534},[1290,2217,2218],{},"    $ch = curl_init($url);\n",[1290,2220,2221],{"class":1292,"line":1547},[1290,2222,2223],{},"    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n",[1290,2225,2226],{"class":1292,"line":368},[1290,2227,2228],{},"    curl_setopt($ch, CURLOPT_TIMEOUT, 10);\n",[1290,2230,2231],{"class":1292,"line":1186},[1290,2232,2233],{},"    if ($body !== null) {\n",[1290,2235,2236],{"class":1292,"line":1746},[1290,2237,2238],{},"        curl_setopt($ch, CURLOPT_POST, true);\n",[1290,2240,2241],{"class":1292,"line":1752},[1290,2242,2243],{},"        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));\n",[1290,2245,2246],{"class":1292,"line":1795},[1290,2247,2248],{},"        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application\u002Fjson']);\n",[1290,2250,2251],{"class":1292,"line":1800},[1290,2252,2253],{},"    }\n",[1290,2255,2256],{"class":1292,"line":1807},[1290,2257,2258],{},"    $response = curl_exec($ch);\n",[1290,2260,2261],{"class":1292,"line":1842},[1290,2262,2263],{},"    $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\n",[1290,2265,2266],{"class":1292,"line":1851},[1290,2267,2268],{},"    curl_close($ch);\n",[1290,2270,2271],{"class":1292,"line":1856},[1290,2272,1642],{"emptyLinePlaceholder":367},[1290,2274,2275],{"class":1292,"line":1861},[1290,2276,2277],{},"    if ($code >= 400) {\n",[1290,2279,2280],{"class":1292,"line":1879},[1290,2281,2282],{},"        throw new RuntimeException(\"FlashCall API вернул $code: $response\");\n",[1290,2284,2285],{"class":1292,"line":1921},[1290,2286,2253],{},[1290,2288,2289],{"class":1292,"line":1937},[1290,2290,2291],{},"    return json_decode($response, true);\n",[1290,2293,2294],{"class":1292,"line":1969},[1290,2295,1560],{},[1290,2297,2298],{"class":1292,"line":1986},[1290,2299,1642],{"emptyLinePlaceholder":367},[1290,2301,2302],{"class":1292,"line":1991},[1290,2303,2304],{},"function createVerification(string $phone): array {\n",[1290,2306,2307],{"class":1292,"line":1996},[1290,2308,2309],{},"    return flashcallRequest(FLASHCALL_API . '\u002Fget-phone', [\n",[1290,2311,2312],{"class":1292,"line":2002},[1290,2313,2314],{},"        'token' => getenv('FLASHCALL_TOKEN'),\n",[1290,2316,2317],{"class":1292,"line":2030},[1290,2318,2319],{},"        'phone' => $phone,\n",[1290,2321,2322],{"class":1292,"line":2055},[1290,2323,2324],{},"    ]);\n",[1290,2326,2327],{"class":1292,"line":2074},[1290,2328,1560],{},[1290,2330,2331],{"class":1292,"line":2092},[1290,2332,1642],{"emptyLinePlaceholder":367},[1290,2334,2335],{"class":1292,"line":2116},[1290,2336,2337],{},"function getStatus(string $id): string {\n",[1290,2339,2340],{"class":1292,"line":2135},[1290,2341,2342],{},"    $query = http_build_query(['token' => getenv('FLASHCALL_TOKEN'), 'id' => $id]);\n",[1290,2344,2345],{"class":1292,"line":2169},[1290,2346,2347],{},"    return flashcallRequest(FLASHCALL_API . '\u002Fstatus?' . $query)['status'];\n",[1290,2349,2350],{"class":1292,"line":2175},[1290,2351,1560],{},[1597,2353,2355],{"id":2354},"python","Python",[1282,2357,2360],{"className":2358,"code":2359,"language":2354,"meta":335,"style":335},"language-python shiki shiki-themes github-light github-dark","import os, time, requests\n\nAPI = \"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1\"\nTOKEN = os.environ[\"FLASHCALL_TOKEN\"]\n\n\ndef create_verification(phone: str) -> tuple[str, str]:\n    r = requests.post(f\"{API}\u002Fget-phone\", json={\"token\": TOKEN, \"phone\": phone}, timeout=10)\n    r.raise_for_status()\n    data = r.json()\n    # второй элемент — номер, на который звонит пользователь\n    return data[\"id\"], data[\"phone\"]\n\n\ndef get_status(check_id: str) -> str:\n    r = requests.get(f\"{API}\u002Fstatus\", params={\"token\": TOKEN, \"id\": check_id}, timeout=10)\n    r.raise_for_status()\n    return r.json()[\"status\"]\n\n\ndef wait_for_call(check_id: str, timeout: float = 60.0) -> bool:\n    deadline = time.monotonic() + timeout\n    while time.monotonic() \u003C deadline:\n        status = get_status(check_id)\n        if status == \"CONFIRMED\":\n            return True\n        if status == \"EXPIRED\":\n            return False\n        time.sleep(2)\n    return False\n",[181,2361,2362,2367,2371,2376,2381,2385,2389,2394,2399,2404,2409,2414,2419,2423,2427,2432,2437,2441,2446,2450,2454,2459,2464,2469,2474,2479,2484,2489,2494,2499],{"__ignoreMap":335},[1290,2363,2364],{"class":1292,"line":888},[1290,2365,2366],{},"import os, time, requests\n",[1290,2368,2369],{"class":1292,"line":336},[1290,2370,1642],{"emptyLinePlaceholder":367},[1290,2372,2373],{"class":1292,"line":1323},[1290,2374,2375],{},"API = \"https:\u002F\u002Fflashcall.ru\u002Fapi\u002Fv1\"\n",[1290,2377,2378],{"class":1292,"line":1521},[1290,2379,2380],{},"TOKEN = os.environ[\"FLASHCALL_TOKEN\"]\n",[1290,2382,2383],{"class":1292,"line":1534},[1290,2384,1642],{"emptyLinePlaceholder":367},[1290,2386,2387],{"class":1292,"line":1547},[1290,2388,1642],{"emptyLinePlaceholder":367},[1290,2390,2391],{"class":1292,"line":368},[1290,2392,2393],{},"def create_verification(phone: str) -> tuple[str, str]:\n",[1290,2395,2396],{"class":1292,"line":1186},[1290,2397,2398],{},"    r = requests.post(f\"{API}\u002Fget-phone\", json={\"token\": TOKEN, \"phone\": phone}, timeout=10)\n",[1290,2400,2401],{"class":1292,"line":1746},[1290,2402,2403],{},"    r.raise_for_status()\n",[1290,2405,2406],{"class":1292,"line":1752},[1290,2407,2408],{},"    data = r.json()\n",[1290,2410,2411],{"class":1292,"line":1795},[1290,2412,2413],{},"    # второй элемент — номер, на который звонит пользователь\n",[1290,2415,2416],{"class":1292,"line":1800},[1290,2417,2418],{},"    return data[\"id\"], data[\"phone\"]\n",[1290,2420,2421],{"class":1292,"line":1807},[1290,2422,1642],{"emptyLinePlaceholder":367},[1290,2424,2425],{"class":1292,"line":1842},[1290,2426,1642],{"emptyLinePlaceholder":367},[1290,2428,2429],{"class":1292,"line":1851},[1290,2430,2431],{},"def get_status(check_id: str) -> str:\n",[1290,2433,2434],{"class":1292,"line":1856},[1290,2435,2436],{},"    r = requests.get(f\"{API}\u002Fstatus\", params={\"token\": TOKEN, \"id\": check_id}, timeout=10)\n",[1290,2438,2439],{"class":1292,"line":1861},[1290,2440,2403],{},[1290,2442,2443],{"class":1292,"line":1879},[1290,2444,2445],{},"    return r.json()[\"status\"]\n",[1290,2447,2448],{"class":1292,"line":1921},[1290,2449,1642],{"emptyLinePlaceholder":367},[1290,2451,2452],{"class":1292,"line":1937},[1290,2453,1642],{"emptyLinePlaceholder":367},[1290,2455,2456],{"class":1292,"line":1969},[1290,2457,2458],{},"def wait_for_call(check_id: str, timeout: float = 60.0) -> bool:\n",[1290,2460,2461],{"class":1292,"line":1986},[1290,2462,2463],{},"    deadline = time.monotonic() + timeout\n",[1290,2465,2466],{"class":1292,"line":1991},[1290,2467,2468],{},"    while time.monotonic() \u003C deadline:\n",[1290,2470,2471],{"class":1292,"line":1996},[1290,2472,2473],{},"        status = get_status(check_id)\n",[1290,2475,2476],{"class":1292,"line":2002},[1290,2477,2478],{},"        if status == \"CONFIRMED\":\n",[1290,2480,2481],{"class":1292,"line":2030},[1290,2482,2483],{},"            return True\n",[1290,2485,2486],{"class":1292,"line":2055},[1290,2487,2488],{},"        if status == \"EXPIRED\":\n",[1290,2490,2491],{"class":1292,"line":2074},[1290,2492,2493],{},"            return False\n",[1290,2495,2496],{"class":1292,"line":2092},[1290,2497,2498],{},"        time.sleep(2)\n",[1290,2500,2501],{"class":1292,"line":2116},[1290,2502,2503],{},"    return False\n",[14,2505,2507],{"id":2506},"frontend","Фронтенд",[10,2509,2510,2511,2514],{},"Оба примера ходят в ",[25,2512,2513],{},"ваш"," эндпоинт, а не в API напрямую: ключ проекта в браузере размещать нельзя.",[1597,2516,2518],{"id":2517},"vue","Vue",[1282,2520,2523],{"className":2521,"code":2522,"language":2517,"meta":335,"style":335},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\nimport { ref, onUnmounted } from 'vue'\n\nconst callTo = ref('')\nconst secondsLeft = ref(0)\nconst done = ref(false)\nlet timer = null\n\nasync function start(phone) {\n  const res = await fetch('\u002Fapi\u002Fverification\u002Fstart', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ phone }),\n  })\n  const { id, callTo: number } = await res.json()\n\n  callTo.value = number\n  secondsLeft.value = 60\n\n  timer = setInterval(async () => {\n    secondsLeft.value--\n    const { status } = await (await fetch(`\u002Fapi\u002Fverification\u002F${id}`)).json()\n\n    if (status === 'CONFIRMED') {\n      done.value = true\n      stop()\n    } else if (status === 'EXPIRED' || secondsLeft.value \u003C= 0) {\n      stop()\n    }\n  }, 2000)\n}\n\nfunction stop() {\n  clearInterval(timer)\n  timer = null\n}\n\nonUnmounted(stop)\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Ca v-if=\"callTo && !done\" :href=\"`tel:${callTo}`\">Позвонить на {{ callTo }}\u003C\u002Fa>\n  \u003Cp v-if=\"secondsLeft > 0\">Ждём звонок: {{ secondsLeft }} с\u003C\u002Fp>\n\u003C\u002Ftemplate>\n",[181,2524,2525,2539,2553,2557,2576,2594,2612,2625,2629,2645,2664,2672,2684,2697,2701,2730,2734,2744,2754,2758,2780,2788,2824,2828,2840,2849,2856,2887,2893,2897,2906,2910,2914,2925,2933,2942,2947,2952,2961,2971,2976,2986,3017,3038],{"__ignoreMap":335},[1290,2526,2527,2529,2533,2536],{"class":1292,"line":888},[1290,2528,2068],{"class":1344},[1290,2530,2532],{"class":2531},"s9eBZ","script",[1290,2534,2535],{"class":1295}," setup",[1290,2537,2538],{"class":1344},">\n",[1290,2540,2541,2544,2547,2550],{"class":1292,"line":336},[1290,2542,2543],{"class":1612},"import",[1290,2545,2546],{"class":1344}," { ref, onUnmounted } ",[1290,2548,2549],{"class":1612},"from",[1290,2551,2552],{"class":1303}," 'vue'\n",[1290,2554,2555],{"class":1292,"line":1323},[1290,2556,1642],{"emptyLinePlaceholder":367},[1290,2558,2559,2561,2564,2566,2569,2571,2574],{"class":1292,"line":1521},[1290,2560,1613],{"class":1612},[1290,2562,2563],{"class":1299}," callTo",[1290,2565,1619],{"class":1612},[1290,2567,2568],{"class":1295}," ref",[1290,2570,1659],{"class":1344},[1290,2572,2573],{"class":1303},"''",[1290,2575,1792],{"class":1344},[1290,2577,2578,2580,2583,2585,2587,2589,2592],{"class":1292,"line":1534},[1290,2579,1613],{"class":1612},[1290,2581,2582],{"class":1299}," secondsLeft",[1290,2584,1619],{"class":1612},[1290,2586,2568],{"class":1295},[1290,2588,1659],{"class":1344},[1290,2590,2591],{"class":1299},"0",[1290,2593,1792],{"class":1344},[1290,2595,2596,2598,2601,2603,2605,2607,2610],{"class":1292,"line":1547},[1290,2597,1613],{"class":1612},[1290,2599,2600],{"class":1299}," done",[1290,2602,1619],{"class":1612},[1290,2604,2568],{"class":1295},[1290,2606,1659],{"class":1344},[1290,2608,2609],{"class":1299},"false",[1290,2611,1792],{"class":1344},[1290,2613,2614,2617,2620,2622],{"class":1292,"line":368},[1290,2615,2616],{"class":1612},"let",[1290,2618,2619],{"class":1344}," timer ",[1290,2621,1829],{"class":1612},[1290,2623,2624],{"class":1299}," null\n",[1290,2626,2627],{"class":1292,"line":1186},[1290,2628,1642],{"emptyLinePlaceholder":367},[1290,2630,2631,2634,2636,2639,2641,2643],{"class":1292,"line":1746},[1290,2632,2633],{"class":1612},"async",[1290,2635,1653],{"class":1612},[1290,2637,2638],{"class":1295}," start",[1290,2640,1659],{"class":1344},[1290,2642,1376],{"class":1662},[1290,2644,1665],{"class":1344},[1290,2646,2647,2649,2651,2653,2655,2657,2659,2662],{"class":1292,"line":1752},[1290,2648,1670],{"class":1612},[1290,2650,1673],{"class":1299},[1290,2652,1619],{"class":1612},[1290,2654,1678],{"class":1612},[1290,2656,1681],{"class":1295},[1290,2658,1659],{"class":1344},[1290,2660,2661],{"class":1303},"'\u002Fapi\u002Fverification\u002Fstart'",[1290,2663,1695],{"class":1344},[1290,2665,2666,2668,2670],{"class":1292,"line":1795},[1290,2667,1700],{"class":1344},[1290,2669,1703],{"class":1303},[1290,2671,1506],{"class":1344},[1290,2673,2674,2676,2678,2680,2682],{"class":1292,"line":1800},[1290,2675,1710],{"class":1344},[1290,2677,1713],{"class":1303},[1290,2679,1351],{"class":1344},[1290,2681,1718],{"class":1303},[1290,2683,1721],{"class":1344},[1290,2685,2686,2688,2690,2692,2694],{"class":1292,"line":1807},[1290,2687,1726],{"class":1344},[1290,2689,1729],{"class":1299},[1290,2691,61],{"class":1344},[1290,2693,1734],{"class":1295},[1290,2695,2696],{"class":1344},"({ phone }),\n",[1290,2698,2699],{"class":1292,"line":1842},[1290,2700,1749],{"class":1344},[1290,2702,2703,2705,2707,2709,2711,2713,2715,2718,2720,2722,2724,2726,2728],{"class":1292,"line":1851},[1290,2704,1670],{"class":1612},[1290,2706,1812],{"class":1344},[1290,2708,1372],{"class":1299},[1290,2710,297],{"class":1344},[1290,2712,1823],{"class":1662},[1290,2714,1351],{"class":1344},[1290,2716,2717],{"class":1299},"number",[1290,2719,1826],{"class":1344},[1290,2721,1829],{"class":1612},[1290,2723,1678],{"class":1612},[1290,2725,1834],{"class":1344},[1290,2727,1337],{"class":1295},[1290,2729,1839],{"class":1344},[1290,2731,2732],{"class":1292,"line":1856},[1290,2733,1642],{"emptyLinePlaceholder":367},[1290,2735,2736,2739,2741],{"class":1292,"line":1861},[1290,2737,2738],{"class":1344},"  callTo.value ",[1290,2740,1829],{"class":1612},[1290,2742,2743],{"class":1344}," number\n",[1290,2745,2746,2749,2751],{"class":1292,"line":1879},[1290,2747,2748],{"class":1344},"  secondsLeft.value ",[1290,2750,1829],{"class":1612},[1290,2752,2753],{"class":1299}," 60\n",[1290,2755,2756],{"class":1292,"line":1921},[1290,2757,1642],{"emptyLinePlaceholder":367},[1290,2759,2760,2763,2765,2768,2770,2772,2775,2777],{"class":1292,"line":1937},[1290,2761,2762],{"class":1344},"  timer ",[1290,2764,1829],{"class":1612},[1290,2766,2767],{"class":1295}," setInterval",[1290,2769,1659],{"class":1344},[1290,2771,2633],{"class":1612},[1290,2773,2774],{"class":1344}," () ",[1290,2776,2154],{"class":1612},[1290,2778,2779],{"class":1344}," {\n",[1290,2781,2782,2785],{"class":1292,"line":1969},[1290,2783,2784],{"class":1344},"    secondsLeft.value",[1290,2786,2787],{"class":1612},"--\n",[1290,2789,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2810,2813,2815,2817,2820,2822],{"class":1292,"line":1986},[1290,2791,2077],{"class":1612},[1290,2793,1812],{"class":1344},[1290,2795,1786],{"class":1299},[1290,2797,1826],{"class":1344},[1290,2799,1829],{"class":1612},[1290,2801,1678],{"class":1612},[1290,2803,1758],{"class":1344},[1290,2805,1976],{"class":1612},[1290,2807,1681],{"class":1295},[1290,2809,1659],{"class":1344},[1290,2811,2812],{"class":1303},"`\u002Fapi\u002Fverification\u002F${",[1290,2814,1372],{"class":1344},[1290,2816,1789],{"class":1303},[1290,2818,2819],{"class":1344},")).",[1290,2821,1337],{"class":1295},[1290,2823,1839],{"class":1344},[1290,2825,2826],{"class":1292,"line":1991},[1290,2827,1642],{"emptyLinePlaceholder":367},[1290,2829,2830,2832,2834,2836,2838],{"class":1292,"line":1996},[1290,2831,2095],{"class":1612},[1290,2833,2098],{"class":1344},[1290,2835,2101],{"class":1612},[1290,2837,2104],{"class":1303},[1290,2839,1665],{"class":1344},[1290,2841,2842,2845,2847],{"class":1292,"line":2002},[1290,2843,2844],{"class":1344},"      done.value ",[1290,2846,1829],{"class":1612},[1290,2848,2113],{"class":1299},[1290,2850,2851,2854],{"class":1292,"line":2030},[1290,2852,2853],{"class":1295},"      stop",[1290,2855,1839],{"class":1344},[1290,2857,2858,2861,2864,2867,2869,2871,2873,2876,2879,2882,2885],{"class":1292,"line":2055},[1290,2859,2860],{"class":1344},"    } ",[1290,2862,2863],{"class":1612},"else",[1290,2865,2866],{"class":1612}," if",[1290,2868,2098],{"class":1344},[1290,2870,2101],{"class":1612},[1290,2872,2125],{"class":1303},[1290,2874,2875],{"class":1612}," ||",[1290,2877,2878],{"class":1344}," secondsLeft.value ",[1290,2880,2881],{"class":1612},"\u003C=",[1290,2883,2884],{"class":1299}," 0",[1290,2886,1665],{"class":1344},[1290,2888,2889,2891],{"class":1292,"line":2074},[1290,2890,2853],{"class":1295},[1290,2892,1839],{"class":1344},[1290,2894,2895],{"class":1292,"line":2092},[1290,2896,2253],{"class":1344},[1290,2898,2899,2902,2904],{"class":1292,"line":2116},[1290,2900,2901],{"class":1344},"  }, ",[1290,2903,2163],{"class":1299},[1290,2905,1792],{"class":1344},[1290,2907,2908],{"class":1292,"line":2135},[1290,2909,1560],{"class":1344},[1290,2911,2912],{"class":1292,"line":2169},[1290,2913,1642],{"emptyLinePlaceholder":367},[1290,2915,2916,2919,2922],{"class":1292,"line":2175},[1290,2917,2918],{"class":1612},"function",[1290,2920,2921],{"class":1295}," stop",[1290,2923,2924],{"class":1344},"() {\n",[1290,2926,2927,2930],{"class":1292,"line":2182},[1290,2928,2929],{"class":1295},"  clearInterval",[1290,2931,2932],{"class":1344},"(timer)\n",[1290,2934,2936,2938,2940],{"class":1292,"line":2935},35,[1290,2937,2762],{"class":1344},[1290,2939,1829],{"class":1612},[1290,2941,2624],{"class":1299},[1290,2943,2945],{"class":1292,"line":2944},36,[1290,2946,1560],{"class":1344},[1290,2948,2950],{"class":1292,"line":2949},37,[1290,2951,1642],{"emptyLinePlaceholder":367},[1290,2953,2955,2958],{"class":1292,"line":2954},38,[1290,2956,2957],{"class":1295},"onUnmounted",[1290,2959,2960],{"class":1344},"(stop)\n",[1290,2962,2964,2967,2969],{"class":1292,"line":2963},39,[1290,2965,2966],{"class":1344},"\u003C\u002F",[1290,2968,2532],{"class":2531},[1290,2970,2538],{"class":1344},[1290,2972,2974],{"class":1292,"line":2973},40,[1290,2975,1642],{"emptyLinePlaceholder":367},[1290,2977,2979,2981,2984],{"class":1292,"line":2978},41,[1290,2980,2068],{"class":1344},[1290,2982,2983],{"class":2531},"template",[1290,2985,2538],{"class":1344},[1290,2987,2989,2992,2994,2997,2999,3002,3005,3007,3010,3013,3015],{"class":1292,"line":2988},42,[1290,2990,2991],{"class":1344},"  \u003C",[1290,2993,57],{"class":2531},[1290,2995,2996],{"class":1295}," v-if",[1290,2998,1829],{"class":1344},[1290,3000,3001],{"class":1303},"\"callTo && !done\"",[1290,3003,3004],{"class":1295}," :href",[1290,3006,1829],{"class":1344},[1290,3008,3009],{"class":1303},"\"`tel:${callTo}`\"",[1290,3011,3012],{"class":1344},">Позвонить на {{ callTo }}\u003C\u002F",[1290,3014,57],{"class":2531},[1290,3016,2538],{"class":1344},[1290,3018,3020,3022,3024,3026,3028,3031,3034,3036],{"class":1292,"line":3019},43,[1290,3021,2991],{"class":1344},[1290,3023,10],{"class":2531},[1290,3025,2996],{"class":1295},[1290,3027,1829],{"class":1344},[1290,3029,3030],{"class":1303},"\"secondsLeft > 0\"",[1290,3032,3033],{"class":1344},">Ждём звонок: {{ secondsLeft }} с\u003C\u002F",[1290,3035,10],{"class":2531},[1290,3037,2538],{"class":1344},[1290,3039,3041,3043,3045],{"class":1292,"line":3040},44,[1290,3042,2966],{"class":1344},[1290,3044,2983],{"class":2531},[1290,3046,2538],{"class":1344},[1597,3048,3050],{"id":3049},"react","React",[1282,3052,3056],{"className":3053,"code":3054,"language":3055,"meta":335,"style":335},"language-jsx shiki shiki-themes github-light github-dark","import { useEffect, useRef, useState } from 'react'\n\nexport function CallVerification({ phone, onConfirmed }) {\n  const [callTo, setCallTo] = useState('')\n  const [secondsLeft, setSecondsLeft] = useState(0)\n  const idRef = useRef(null)\n\n  useEffect(() => {\n    let cancelled = false\n\n    async function start() {\n      const res = await fetch('\u002Fapi\u002Fverification\u002Fstart', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application\u002Fjson' },\n        body: JSON.stringify({ phone }),\n      })\n      const data = await res.json()\n      if (cancelled) return\n\n      idRef.current = data.id\n      setCallTo(data.callTo)\n      setSecondsLeft(60)\n    }\n\n    start()\n    return () => { cancelled = true }\n  }, [phone])\n\n  useEffect(() => {\n    if (!idRef.current || secondsLeft \u003C= 0) return\n\n    const timer = setTimeout(async () => {\n      const { status } = await (await fetch(`\u002Fapi\u002Fverification\u002F${idRef.current}`)).json()\n      if (status === 'CONFIRMED') onConfirmed()\n      else setSecondsLeft((s) => s - 2)\n    }, 2000)\n\n    return () => clearTimeout(timer)\n  }, [secondsLeft, onConfirmed])\n\n  if (!callTo) return null\n  return (\n    \u003C>\n      \u003Ca href={`tel:${callTo}`}>Позвонить на {callTo}\u003C\u002Fa>\n      {secondsLeft > 0 && \u003Cp>Ждём звонок: {secondsLeft} с\u003C\u002Fp>}\n    \u003C\u002F>\n  )\n}\n","jsx",[181,3057,3058,3070,3074,3096,3124,3150,3169,3173,3185,3197,3201,3212,3231,3240,3253,3266,3271,3288,3299,3303,3313,3321,3333,3337,3341,3348,3367,3372,3376,3386,3411,3415,3436,3476,3492,3520,3529,3533,3546,3551,3555,3570,3577,3582,3611,3638,3644,3650],{"__ignoreMap":335},[1290,3059,3060,3062,3065,3067],{"class":1292,"line":888},[1290,3061,2543],{"class":1612},[1290,3063,3064],{"class":1344}," { useEffect, useRef, useState } ",[1290,3066,2549],{"class":1612},[1290,3068,3069],{"class":1303}," 'react'\n",[1290,3071,3072],{"class":1292,"line":336},[1290,3073,1642],{"emptyLinePlaceholder":367},[1290,3075,3076,3078,3080,3083,3086,3088,3090,3093],{"class":1292,"line":1323},[1290,3077,1647],{"class":1612},[1290,3079,1653],{"class":1612},[1290,3081,3082],{"class":1295}," CallVerification",[1290,3084,3085],{"class":1344},"({ ",[1290,3087,1376],{"class":1662},[1290,3089,297],{"class":1344},[1290,3091,3092],{"class":1662},"onConfirmed",[1290,3094,3095],{"class":1344}," }) {\n",[1290,3097,3098,3100,3103,3105,3107,3110,3113,3115,3118,3120,3122],{"class":1292,"line":1521},[1290,3099,1670],{"class":1612},[1290,3101,3102],{"class":1344}," [",[1290,3104,1823],{"class":1299},[1290,3106,297],{"class":1344},[1290,3108,3109],{"class":1299},"setCallTo",[1290,3111,3112],{"class":1344},"] ",[1290,3114,1829],{"class":1612},[1290,3116,3117],{"class":1295}," useState",[1290,3119,1659],{"class":1344},[1290,3121,2573],{"class":1303},[1290,3123,1792],{"class":1344},[1290,3125,3126,3128,3130,3133,3135,3138,3140,3142,3144,3146,3148],{"class":1292,"line":1534},[1290,3127,1670],{"class":1612},[1290,3129,3102],{"class":1344},[1290,3131,3132],{"class":1299},"secondsLeft",[1290,3134,297],{"class":1344},[1290,3136,3137],{"class":1299},"setSecondsLeft",[1290,3139,3112],{"class":1344},[1290,3141,1829],{"class":1612},[1290,3143,3117],{"class":1295},[1290,3145,1659],{"class":1344},[1290,3147,2591],{"class":1299},[1290,3149,1792],{"class":1344},[1290,3151,3152,3154,3157,3159,3162,3164,3167],{"class":1292,"line":1547},[1290,3153,1670],{"class":1612},[1290,3155,3156],{"class":1299}," idRef",[1290,3158,1619],{"class":1612},[1290,3160,3161],{"class":1295}," useRef",[1290,3163,1659],{"class":1344},[1290,3165,3166],{"class":1299},"null",[1290,3168,1792],{"class":1344},[1290,3170,3171],{"class":1292,"line":368},[1290,3172,1642],{"emptyLinePlaceholder":367},[1290,3174,3175,3178,3181,3183],{"class":1292,"line":1186},[1290,3176,3177],{"class":1295},"  useEffect",[1290,3179,3180],{"class":1344},"(() ",[1290,3182,2154],{"class":1612},[1290,3184,2779],{"class":1344},[1290,3186,3187,3190,3193,3195],{"class":1292,"line":1746},[1290,3188,3189],{"class":1612},"    let",[1290,3191,3192],{"class":1344}," cancelled ",[1290,3194,1829],{"class":1612},[1290,3196,2132],{"class":1299},[1290,3198,3199],{"class":1292,"line":1752},[1290,3200,1642],{"emptyLinePlaceholder":367},[1290,3202,3203,3206,3208,3210],{"class":1292,"line":1795},[1290,3204,3205],{"class":1612},"    async",[1290,3207,1653],{"class":1612},[1290,3209,2638],{"class":1295},[1290,3211,2924],{"class":1344},[1290,3213,3214,3217,3219,3221,3223,3225,3227,3229],{"class":1292,"line":1800},[1290,3215,3216],{"class":1612},"      const",[1290,3218,1673],{"class":1299},[1290,3220,1619],{"class":1612},[1290,3222,1678],{"class":1612},[1290,3224,1681],{"class":1295},[1290,3226,1659],{"class":1344},[1290,3228,2661],{"class":1303},[1290,3230,1695],{"class":1344},[1290,3232,3233,3236,3238],{"class":1292,"line":1807},[1290,3234,3235],{"class":1344},"        method: ",[1290,3237,1703],{"class":1303},[1290,3239,1506],{"class":1344},[1290,3241,3242,3245,3247,3249,3251],{"class":1292,"line":1842},[1290,3243,3244],{"class":1344},"        headers: { ",[1290,3246,1713],{"class":1303},[1290,3248,1351],{"class":1344},[1290,3250,1718],{"class":1303},[1290,3252,1721],{"class":1344},[1290,3254,3255,3258,3260,3262,3264],{"class":1292,"line":1851},[1290,3256,3257],{"class":1344},"        body: ",[1290,3259,1729],{"class":1299},[1290,3261,61],{"class":1344},[1290,3263,1734],{"class":1295},[1290,3265,2696],{"class":1344},[1290,3267,3268],{"class":1292,"line":1856},[1290,3269,3270],{"class":1344},"      })\n",[1290,3272,3273,3275,3278,3280,3282,3284,3286],{"class":1292,"line":1861},[1290,3274,3216],{"class":1612},[1290,3276,3277],{"class":1299}," data",[1290,3279,1619],{"class":1612},[1290,3281,1678],{"class":1612},[1290,3283,1834],{"class":1344},[1290,3285,1337],{"class":1295},[1290,3287,1839],{"class":1344},[1290,3289,3290,3293,3296],{"class":1292,"line":1879},[1290,3291,3292],{"class":1612},"      if",[1290,3294,3295],{"class":1344}," (cancelled) ",[1290,3297,3298],{"class":1612},"return\n",[1290,3300,3301],{"class":1292,"line":1921},[1290,3302,1642],{"emptyLinePlaceholder":367},[1290,3304,3305,3308,3310],{"class":1292,"line":1937},[1290,3306,3307],{"class":1344},"      idRef.current ",[1290,3309,1829],{"class":1612},[1290,3311,3312],{"class":1344}," data.id\n",[1290,3314,3315,3318],{"class":1292,"line":1969},[1290,3316,3317],{"class":1295},"      setCallTo",[1290,3319,3320],{"class":1344},"(data.callTo)\n",[1290,3322,3323,3326,3328,3331],{"class":1292,"line":1986},[1290,3324,3325],{"class":1295},"      setSecondsLeft",[1290,3327,1659],{"class":1344},[1290,3329,3330],{"class":1299},"60",[1290,3332,1792],{"class":1344},[1290,3334,3335],{"class":1292,"line":1991},[1290,3336,2253],{"class":1344},[1290,3338,3339],{"class":1292,"line":1996},[1290,3340,1642],{"emptyLinePlaceholder":367},[1290,3342,3343,3346],{"class":1292,"line":2002},[1290,3344,3345],{"class":1295},"    start",[1290,3347,1839],{"class":1344},[1290,3349,3350,3353,3355,3357,3360,3362,3365],{"class":1292,"line":2030},[1290,3351,3352],{"class":1612},"    return",[1290,3354,2774],{"class":1344},[1290,3356,2154],{"class":1612},[1290,3358,3359],{"class":1344}," { cancelled ",[1290,3361,1829],{"class":1612},[1290,3363,3364],{"class":1299}," true",[1290,3366,1367],{"class":1344},[1290,3368,3369],{"class":1292,"line":2055},[1290,3370,3371],{"class":1344},"  }, [phone])\n",[1290,3373,3374],{"class":1292,"line":2074},[1290,3375,1642],{"emptyLinePlaceholder":367},[1290,3377,3378,3380,3382,3384],{"class":1292,"line":2092},[1290,3379,3177],{"class":1295},[1290,3381,3180],{"class":1344},[1290,3383,2154],{"class":1612},[1290,3385,2779],{"class":1344},[1290,3387,3388,3390,3392,3394,3397,3400,3403,3405,3407,3409],{"class":1292,"line":2116},[1290,3389,2095],{"class":1612},[1290,3391,1758],{"class":1344},[1290,3393,1761],{"class":1612},[1290,3395,3396],{"class":1344},"idRef.current ",[1290,3398,3399],{"class":1612},"||",[1290,3401,3402],{"class":1344}," secondsLeft ",[1290,3404,2881],{"class":1612},[1290,3406,2884],{"class":1299},[1290,3408,2107],{"class":1344},[1290,3410,3298],{"class":1612},[1290,3412,3413],{"class":1292,"line":2135},[1290,3414,1642],{"emptyLinePlaceholder":367},[1290,3416,3417,3419,3422,3424,3426,3428,3430,3432,3434],{"class":1292,"line":2169},[1290,3418,2077],{"class":1612},[1290,3420,3421],{"class":1299}," timer",[1290,3423,1619],{"class":1612},[1290,3425,2157],{"class":1295},[1290,3427,1659],{"class":1344},[1290,3429,2633],{"class":1612},[1290,3431,2774],{"class":1344},[1290,3433,2154],{"class":1612},[1290,3435,2779],{"class":1344},[1290,3437,3438,3440,3442,3444,3446,3448,3450,3452,3454,3456,3458,3460,3463,3465,3468,3470,3472,3474],{"class":1292,"line":2175},[1290,3439,3216],{"class":1612},[1290,3441,1812],{"class":1344},[1290,3443,1786],{"class":1299},[1290,3445,1826],{"class":1344},[1290,3447,1829],{"class":1612},[1290,3449,1678],{"class":1612},[1290,3451,1758],{"class":1344},[1290,3453,1976],{"class":1612},[1290,3455,1681],{"class":1295},[1290,3457,1659],{"class":1344},[1290,3459,2812],{"class":1303},[1290,3461,3462],{"class":1344},"idRef",[1290,3464,61],{"class":1303},[1290,3466,3467],{"class":1344},"current",[1290,3469,1789],{"class":1303},[1290,3471,2819],{"class":1344},[1290,3473,1337],{"class":1295},[1290,3475,1839],{"class":1344},[1290,3477,3478,3480,3482,3484,3486,3488,3490],{"class":1292,"line":2182},[1290,3479,3292],{"class":1612},[1290,3481,2098],{"class":1344},[1290,3483,2101],{"class":1612},[1290,3485,2104],{"class":1303},[1290,3487,2107],{"class":1344},[1290,3489,3092],{"class":1295},[1290,3491,1839],{"class":1344},[1290,3493,3494,3497,3500,3502,3505,3507,3509,3512,3515,3518],{"class":1292,"line":2935},[1290,3495,3496],{"class":1612},"      else",[1290,3498,3499],{"class":1295}," setSecondsLeft",[1290,3501,2146],{"class":1344},[1290,3503,3504],{"class":1662},"s",[1290,3506,2107],{"class":1344},[1290,3508,2154],{"class":1612},[1290,3510,3511],{"class":1344}," s ",[1290,3513,3514],{"class":1612},"-",[1290,3516,3517],{"class":1299}," 2",[1290,3519,1792],{"class":1344},[1290,3521,3522,3525,3527],{"class":1292,"line":2944},[1290,3523,3524],{"class":1344},"    }, ",[1290,3526,2163],{"class":1299},[1290,3528,1792],{"class":1344},[1290,3530,3531],{"class":1292,"line":2949},[1290,3532,1642],{"emptyLinePlaceholder":367},[1290,3534,3535,3537,3539,3541,3544],{"class":1292,"line":2954},[1290,3536,3352],{"class":1612},[1290,3538,2774],{"class":1344},[1290,3540,2154],{"class":1612},[1290,3542,3543],{"class":1295}," clearTimeout",[1290,3545,2932],{"class":1344},[1290,3547,3548],{"class":1292,"line":2963},[1290,3549,3550],{"class":1344},"  }, [secondsLeft, onConfirmed])\n",[1290,3552,3553],{"class":1292,"line":2973},[1290,3554,1642],{"emptyLinePlaceholder":367},[1290,3556,3557,3559,3561,3563,3566,3568],{"class":1292,"line":2978},[1290,3558,1755],{"class":1612},[1290,3560,1758],{"class":1344},[1290,3562,1761],{"class":1612},[1290,3564,3565],{"class":1344},"callTo) ",[1290,3567,2110],{"class":1612},[1290,3569,2624],{"class":1299},[1290,3571,3572,3574],{"class":1292,"line":2988},[1290,3573,1845],{"class":1612},[1290,3575,3576],{"class":1344}," (\n",[1290,3578,3579],{"class":1292,"line":3019},[1290,3580,3581],{"class":1344},"    \u003C>\n",[1290,3583,3584,3587,3589,3592,3594,3597,3600,3602,3604,3607,3609],{"class":1292,"line":3040},[1290,3585,3586],{"class":1344},"      \u003C",[1290,3588,57],{"class":2531},[1290,3590,3591],{"class":1295}," href",[1290,3593,1829],{"class":1612},[1290,3595,3596],{"class":1344},"{",[1290,3598,3599],{"class":1303},"`tel:${",[1290,3601,1823],{"class":1344},[1290,3603,1789],{"class":1303},[1290,3605,3606],{"class":1344},"}>Позвонить на {callTo}\u003C\u002F",[1290,3608,57],{"class":2531},[1290,3610,2538],{"class":1344},[1290,3612,3614,3617,3620,3622,3625,3628,3630,3633,3635],{"class":1292,"line":3613},45,[1290,3615,3616],{"class":1344},"      {secondsLeft ",[1290,3618,3619],{"class":1612},">",[1290,3621,2884],{"class":1299},[1290,3623,3624],{"class":1612}," &&",[1290,3626,3627],{"class":1344}," \u003C",[1290,3629,10],{"class":2531},[1290,3631,3632],{"class":1344},">Ждём звонок: {secondsLeft} с\u003C\u002F",[1290,3634,10],{"class":2531},[1290,3636,3637],{"class":1344},">}\n",[1290,3639,3641],{"class":1292,"line":3640},46,[1290,3642,3643],{"class":1344},"    \u003C\u002F>\n",[1290,3645,3647],{"class":1292,"line":3646},47,[1290,3648,3649],{"class":1344},"  )\n",[1290,3651,3653],{"class":1292,"line":3652},48,[1290,3654,1560],{"class":1344},[14,3656,3658],{"id":3657},"tipichnye-oshibki-pri-integracii","Типичные ошибки при интеграции",[19,3660,3661,3669,3675,3681,3687,3693,3699,3705],{},[22,3662,3663,3668],{},[25,3664,3665,3666,61],{},"Не обрабатывают ",[181,3667,202],{}," Пользователь видит вечный спиннер вместо кнопки «Позвонить ещё раз». Самая частая ошибка.",[22,3670,3671,3674],{},[25,3672,3673],{},"Держат ключ во фронтенде."," Ключ забирают из исходников страницы и тратят ваш баланс.",[22,3676,3677,3680],{},[25,3678,3679],{},"Опрашивают статус слишком часто."," Десять запросов в секунду ничего не ускоряют.",[22,3682,3683,3686],{},[25,3684,3685],{},"Не останавливают опрос."," Без условия по таймеру цикл продолжается после ухода пользователя со страницы.",[22,3688,3689,3692],{},[25,3690,3691],{},"Не проверяют баланс."," При нулевом балансе проверка не создастся, и форма перестанет работать молча.",[22,3694,3695,3698],{},[25,3696,3697],{},"Не ограничивают частоту на своём эндпоинте."," Создание проверок стоит денег — лимит на попытки обязателен.",[22,3700,3701,3704],{},[25,3702,3703],{},"Не приводят номер к E.164."," Номер с восьмёркой, скобками и дефисами не пройдёт валидацию.",[22,3706,3707,3710],{},[25,3708,3709],{},"Забывают предупредить, что звонить надо с подтверждаемого номера."," Звонок с рабочего телефона не подтвердится, а причина будет неочевидна.",[14,3712,3714],{"id":3713},"skolko-eto-zanimaet","Сколько это занимает",[67,3716,3718],{"className":3717},[70],[72,3719,3720,3730],{},[75,3721,3722],{},[78,3723,3724,3727],{},[81,3725,3726],{},"Шаг",[81,3728,3729],{},"Время",[94,3731,3732,3740,3748,3757,3764],{},[78,3733,3734,3737],{},[99,3735,3736],{},"Регистрация, проект, ключ",[99,3738,3739],{},"5 минут",[78,3741,3742,3745],{},[99,3743,3744],{},"Два эндпоинта на бэкенде",[99,3746,3747],{},"1–2 часа",[78,3749,3750,3755],{},[99,3751,3752,3753],{},"Экран ожидания с таймером и ",[181,3754,183],{},[99,3756,3747],{},[78,3758,3759,3762],{},[99,3760,3761],{},"Обработка граничных случаев и лимиты",[99,3763,3747],{},[78,3765,3766,3769],{},[99,3767,3768],{},"Тестирование на реальных номерах",[99,3770,3771],{},"1 час",[10,3773,3774],{},"Итого рабочий день на разработчика. Дольше обычно идёт не техническая часть, а согласование интерфейса и сравнение воронок.",[10,3776,3777,3778,852],{},"Актуальные цены — на странице ",[57,3779,765],{"href":764},[3781,3782,3783],"style",{},"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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":335,"searchDepth":336,"depth":336,"links":3785},[3786,3787,3788,3789,3790,3795,3799,3800],{"id":1203,"depth":336,"text":1204},{"id":1279,"depth":336,"text":1280},{"id":1448,"depth":336,"text":1449},{"id":1471,"depth":336,"text":1472},{"id":1594,"depth":336,"text":1595,"children":3791},[3792,3793,3794],{"id":1599,"depth":1323,"text":1600},{"id":2187,"depth":1323,"text":2188},{"id":2354,"depth":1323,"text":2355},{"id":2506,"depth":336,"text":2507,"children":3796},[3797,3798],{"id":2517,"depth":1323,"text":2518},{"id":3049,"depth":1323,"text":3050},{"id":3657,"depth":336,"text":3658},{"id":3713,"depth":336,"text":3714},{"title":3802,"text":3803,"label":3804,"href":348},"Получите ключ и попробуйте","Проект и ключ создаются сразу после регистрации. Инструкция с вашим ключом открывается в кабинете.","Получить API-ключ","Как интегрировать подтверждение номера через REST API: создание проверки, опрос статуса, обработка ошибок и примеры на Node.js, PHP, Python, Vue и React.",[3807,3810,3813,3816,3819],{"q":3808,"a":3809},"Есть ли вебхуки?","Нет. Результат забирается опросом `GET \u002Fstatus`. Это упрощает интеграцию:\nне нужен публично доступный обработчик, проверка подписи и защита\nот повторной доставки. Если вебхуки появятся, опрос продолжит работать.\n",{"q":3811,"a":3812},"Можно ли вызывать API прямо из браузера?","Нет. Запрос содержит ключ проекта, а всё, что попало во фронтенд, доступно\nпользователю. Оба запроса должны идти с вашего сервера, а браузер общается\nс вашим собственным эндпоинтом.\n",{"q":3814,"a":3815},"Сколько живёт проверка?","60 секунд с момента создания. После этого она переходит в `EXPIRED`,\nи звонок, поступивший позже, к ней уже не привяжется. Создавайте новую\nпроверку вместо продления старой.\n",{"q":3817,"a":3818},"Что вернётся, если на балансе нет денег?","Создание проверки завершится ошибкой о недостатке средств — проверка\nне будет создана. Уже существующие проверки при этом дорабатывают штатно.\n",{"q":3820,"a":3821},"Нужен ли SDK?","Нет. Это обычный REST с JSON: два запроса, которые делаются штатным\nHTTP-клиентом любого языка. Отдельной библиотеки не требуется.\n","API подтверждения номера телефона: интеграция за час",{},[1188,373,891],{"title":1195,"description":3805},"blog\u002Fapi-podtverzhdeniya-nomera","Интеграция состоит из двух запросов: создать проверку и узнать её статус. Первый\nвозвращает выделенный номер, который нужно показать пользователю, второй — одно из трёх\nсостояний: `PENDING`, `CONFIRMED` или `EXPIRED`. Вебхуков нет, результат забирается\nопросом — поэтому не нужен публичный обработчик, проверка подписи и защита от повторных\nдоставок. Ключ проекта используется только с сервера.\n","FCFlMRQUYKfE8axfA1BS8q2zx_WEzpDY3DgBePLh8Ho",{"data":3830,"body":3831},{},{"type":3832,"children":3833},"root",[3834],{"type":3835,"tag":10,"props":3836,"children":3837},"element",{},[3838],{"type":3839,"value":3840},"text","Подтверждение номера на сайте делается тремя способами: своя интеграция с SMS-агрегатором,\nготовый виджет или проверка звонком. Последний вариант не требует доставки сообщения —\nпользователь сам звонит на выделенный номер, и вводить ему нечего. На бэкенде это два\nHTTP-запроса и три статуса, во фронтенде — кликабельный номер и таймер на 60 секунд.\nПодтверждение стоит 0,20 ₽, несостоявшееся не тарифицируется.",{"data":3842,"body":3843},{},{"type":3832,"children":3844},[3845],{"type":3835,"tag":10,"props":3846,"children":3847},{},[3848,3850,3855],{"type":3839,"value":3849},"С телефона, глядя на экран компьютера. Поэтому номер нужно показывать крупно\nи с разделителями, чтобы его было легко набрать вручную, а не мелким шрифтом\nв одну строку. На мобильных браузерах номер должен быть ссылкой ",{"type":3835,"tag":181,"props":3851,"children":3853},{"className":3852},[],[3854],{"type":3839,"value":183},{"type":3839,"value":61},{"data":3857,"body":3858},{},{"type":3832,"children":3859},[3860],{"type":3835,"tag":10,"props":3861,"children":3862},{},[3863],{"type":3839,"value":3864},"Нет. Ключ проекта нельзя размещать во фронтенде — из браузера его заберёт кто\nугодно и начнёт тратить ваш баланс. Создание проверки и опрос статуса должны\nидти через ваш собственный сервер.",{"data":3866,"body":3867},{},{"type":3832,"children":3868},[3869],{"type":3835,"tag":10,"props":3870,"children":3871},{},[3872,3874,3879,3881,3886],{"type":3839,"value":3873},"Достаточно раза в пару секунд: сценарий укладывается в 60 секунд, и более\nчастый опрос ничего не ускоряет. Прекращать опрос нужно по ",{"type":3835,"tag":181,"props":3875,"children":3877},{"className":3876},[],[3878],{"type":3839,"value":198},{"type":3839,"value":3880},",\nпо ",{"type":3835,"tag":181,"props":3882,"children":3884},{"className":3883},[],[3885],{"type":3839,"value":202},{"type":3839,"value":3887}," и по истечении таймера, иначе запросы будут идти вечно.",{"data":3889,"body":3890},{},{"type":3832,"children":3891},[3892],{"type":3835,"tag":10,"props":3893,"children":3894},{},[3895],{"type":3839,"value":3896},"Оставить в форме ссылку «Подтвердить другим способом». Скрытый номер,\nзарубежная SIM и корпоративная АТС встречаются нечасто, но без запасного\nварианта такие пользователи не пройдут дальше.",1787687891535]