Когда впервые пишешь собственную интеграцию с amoCRM через REST API, сами запросы к сделкам, контактам или компаниям обычно оказываются не самой сложной частью.
Основной вопрос возникает раньше:
как один раз авторизовать интеграцию, а затем поддерживать доступ к amoCRM без постоянного ручного получения ключей?
1–2 года назад я реализовывал такую интеграцию на PHP. Токены хранили в MySQL, а работу с OAuth разбили на несколько отдельных файлов.
Сейчас решил восстановить общую архитектуру этой реализации.
Если убрать детали, OAuth‑интеграция выглядит довольно просто:
Authorization Code → Access Token + Refresh Token → сохранение в БД → запросы к REST API → обновление токенов → повторное сохранение в БД.
Код авторизации берется в AmoCRM:

В общем все эти значения помещаются в data и потом отсылаются через curl:
subdomain = 'https://xxx.amocrm.ru/'; //Поддомен нужного аккаунта $link = 'https:/xxx.amocrm.ru/oauth2/access_token'; //Формируем URL для запроса $data = [ 'client_id' => '6a2d4741-xxxx-xxxx-xxxx-92277723dcea', // ID интеграции 'client_secret' => 'PZp1wm7VpAUBvpTzADj4CRHjmuvRR4PwVS7EqVxxxNvpMglIWK8uy6KWSgxxvdjY', //секретный ключ 'grant_type' => 'authorization_code', 'code' => 'def50200exxxx8a701f977d7e9151d7b02da0000001d28e4a100000004c2e7350000xxb0f8fcd6b982615f86242baf3eea6ee5cbe282f19e8fd3d973ab858afa6691701f83c61f3e02af1029334772747c6242dc50e6e0bfe31e45666a8be5d93f0d5242300e2006c4bfe8cd4fa7c661f1ed5a4f290b8b283b44c3c22e247aa6abd587d7ce666ee6956ec8cb281ef6813ba6494f4eba97a8b20486cd3dff0ae6d486a195ae50755e13cf00f9608d3e699c5d7baefdbfc22da8ee21d1af69e6183abba81ba2a174341ce1e62c2dd7bbf956aee3624265d5bd9c098ffc24d9071d63b7c6e6db1ba5d55a853483ac77e42d6ed8ca32be8ccb2e7ee488716da9f8c21be27489f7d6ceadebe8d120b22233e053c1659fd872b4eefc756cd4770d23e9a48707d492051e417995193f51e6322c339d669e3298aee671ef2caa8c61b662d34685b7f8186be04a5f495a2123bacebfa7f2f8a9901260f8ba4757ab252849af7920445a8fe8397c78bbb94900e0f7f8984f18efb235bb378d5413c26100338c91d1d21dd15e4641ee85dc2b3187f6bb20a34fb94993302b18afe4ea577fdc9f8bcaf4a550eb7fdc41c206b318f7f210f696787fe0cbfe50843ce86849cf5dc6b834cb752cbe6e86df89b3402b7d3f4c28c3588f5908afac5452ee76a1b1b768aa25a5769cf272fa993101f07de20c28', 'redirect_uri' => 'https://xxx.ru/', ]; $curl = curl_init(); curl_setopt($curl,CURLOPT_RETURNTRANSFER, true); curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0'); curl_setopt($curl,CURLOPT_URL, $link); curl_setopt($curl,CURLOPT_HTTPHEADER,['Content-Type:application/json']); curl_setopt($curl,CURLOPT_HEADER, false); curl_setopt($curl,CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2); $out = curl_exec($curl); var_dump($out); $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); $code = (int)$code; $errors = [ 400 => 'Bad request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not found', 500 => 'Internal server error', 502 => 'Bad gateway', 503 => 'Service unavailable', ]; try { if ($code < 200 || $code > 204) { throw new Exception(isset($errors[$code]) ? $errors[$code] : 'Undefined error', $code); } } catch(\Exception $e) { die('Ошибка: ' . $e->getMessage() . PHP_EOL . 'Код ошибки: ' . $e->getCode()); }
Схема такая:
Authorization Code
↓
POST /oauth2/access_token
↓
Access Token
Refresh Token
По итогу получает в обратку Access Token и Refresh Token и их записываем в БД себе, мы в MySQL записывали.
function save_token ($access_token, $refresh_token) { $config = [ 'host' => '127.0.0.1', 'db_name' => 'xxx', 'username' => 'xxxx', 'password' => "xxx", 'port' => 3308, ]; $d = "mysql:host={$config ['host']};port={$config["port"]};dbname={$config ["db_name"]};charset=utf8"; try { $pdo = new PDO($d, $config ["username"], $config ["password"]); echo('<br> Подключение успешно к БД <br>'); } catch (PDOException $e) { echo "Connect failed: ". $e -> getMessage(); } $insert = "INSERT INTO `AmoAuth`(`access_token`, `refresh_token`) VALUES ('$access_token','$refresh_token')"; try { $pdo -> query($insert); echo("Токены успешно сохранены"); } catch (\Throwable $th) { echo("Ошибка записи в БД $th"); } }
Все, $access_token есть. Зачем? Что бы потом его использовать в запросах к AmoCRM, например:
GET /api/v4/leads
GET /api/v4/contacts
POST /api/v4/leads
Потому что если access_token не валиден, то запросы не пройдут, он нужен далее для работы.
Access token amoCRM имеет ограниченный срок действия, поэтому его надо постоянно обновлять и для этого нужен Refresh Token.
Логика получается следующая:
Есть Access Token — Токен ещё действителен?
да → REST API
нет → Refresh Token → новый Access Token + новый Refresh Token → База
Если Access Token истек, то надо переобновить.
А что бы понять — надо проверить на валидность:
public function access_token_valid ($access_token) { //проверка access_token на валидность $this -> errorslogservice -> errors_log ("access_token_valid запускается"); $url = 'https://xxx.amocrm.ru/api/v4/account'; $headers = [ 'Authorization: Bearer ' . $access_token ]; $curl = curl_init(); curl_setopt($curl,CURLOPT_RETURNTRANSFER, true); curl_setopt($curl,CURLOPT_ENCODING, ''); curl_setopt($curl,CURLOPT_MAXREDIRS, 10); curl_setopt($curl,CURLOPT_TIMEOUT, 0); curl_setopt($curl,CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0'); curl_setopt($curl,CURLOPT_URL, $url); curl_setopt($curl,CURLOPT_HTTPHEADER, $headers); curl_setopt($curl,CURLOPT_HEADER, false); curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2); $out = curl_exec($curl); $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); $this -> errorslogservice -> errors_log ($out); $res = json_decode($out); return isset($res -> status); }
Как взять токен с БД писать не буду и так понятно, обычный SQL запрос внутри PHP с pdo по типу такого $query = $this → pdo → query($request).
Если оказался не валидным — надо запросить новый Access Token, то формируем код где дата Refresh Token:
private function refresh_amo_token ($refresh_token) { //получение нового access_token и refresh_token через refresh_token $this -> errorslogservice -> errors_log ("refresh_amo_token запускается"); $url = 'https://xxx.amocrm.ru/oauth2/access_token'; $data = [ 'client_id' => 'xxxxxxxx-84f7-4bc9-911c-92277723dcea', 'client_secret' => 'PZp1xxxxxxxxxxxxxxRR4PwVS7EqV7G1NvpMglIWK8uy6KWSg0AvdjY', 'grant_type' => 'refresh_token', 'refresh_token' => $refresh_token, 'redirect_uri' => 'https://xxx.ru/', ]; $headers = ['Content-Type:application/json']; $curl = curl_init(); curl_setopt($curl,CURLOPT_RETURNTRANSFER, true); curl_setopt($curl,CURLOPT_ENCODING, ''); curl_setopt($curl,CURLOPT_MAXREDIRS, 10); curl_setopt($curl,CURLOPT_TIMEOUT, 0); curl_setopt($curl,CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl,CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($curl,CURLOPT_USERAGENT,'amoCRM-oAuth-client/1.0'); curl_setopt($curl,CURLOPT_URL, $url); curl_setopt($curl,CURLOPT_HTTPHEADER, $headers); curl_setopt($curl,CURLOPT_HEADER, false); curl_setopt($curl,CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($curl,CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($curl,CURLOPT_SSL_VERIFYPEER, 1); curl_setopt($curl,CURLOPT_SSL_VERIFYHOST, 2); $out = curl_exec($curl); $this -> errorslogservice -> errors_log ($out); $code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); if ($code >= 400) { throw new Exception ("ошибка API AmoCRM $out"); } $tokens = json_decode($out); if (!isset ($tokens -> access_token)) { throw new Exception ("Ошибка получения токена".json_encode($tokens)); } return $tokens; }
В ответ получаем новую пару Access Token и Refresh Token и перезаписываем их в БД себе.
И так по кругу, Access Token — не валидный — запросили новый — перезаписали в БД — используем в запросах Rest API к Амо.
Как вариант слышал что можно ещё сохранять expires_at что бы каждый раз не проверять валидность. Мы же делали каждый раз в curl проверяя, наверное это не очень оптимально если запросов будет много, но при малом‑среднем количестве работает нормально:
$access_token = $this -> tokensService -> get_token(); if ($this -> tokensService -> access_token_valid ($access_token)) { $access_token = $this -> tokensService -> refresh_token (); };
И погнало по кругу.
Если свести всё к нескольким правилам:
Authorization Code нужен в основном для первоначальной авторизации.
Он обменивается на access_token и refresh_token.
Access_token используется для REST‑запросов.
Когда access token заканчивается, используется refresh_token.
После refresh amoCRM выдаёт новую пару Access + Refresh.
Оба новых токена необходимо сохранить.
Старый Refresh token после обмена использовать нельзя.
При колхозном подходе все это можно запихать в 1 файл PHP, но по правильному разбивать на: Authamo.php, AmoAPIService.php, TokenService.php — что бы каждый выполнял свою функцию.
Authamo.php — авторизация первый раз
AmoAPIService.php — это как раз регулярные запросы к API, когда токен уже есть
TokensService.php — это как раз работа с токенами если при проверке IF в AmoAPIService.php вылетело что невалидный.
prishelec
Сейчас можно создавать долгодействующий токен. Что очень удобно..
IvanDB Автор
Точно, есть такое. Это скорее всего подойдет для индивидуальной интеграции, но для приложений маркета, как слышал (пока ещё не писал), там только с заменой скорее всего т.к. клиенты разные. Спасибо за информацию.
prishelec
Сначала был вечный токен. Потом временный через OAuth 2.0.
У меня был алгоритм. Если получаем ошибку 401 то обновляем токен и заново используем метод который использовали. Но бывает проблема на сервере и все токен не обновиться. В теории да, можно предусмотреть дополнительную обработку и т.д.
По поводу ваших клиентов. Ну напишите в мануале где брать токен без всяких OAuth 2.0. Меньше «головной боли» будет.
IvanDB Автор
Если клиент будет себе ставить приложение с маркета Амо, то врят ли он будет заморачиватсья с токенами, им надо нажать кнопки и что бы все работало. Хотя может и будут копировать токен, надо будет смотреть, потому что если у готового приложения OAuth 2.0 будет отлетать из за того что токен не обновить, то проще и правда сделать инструкцию откуда скопировать долгосрочный и куда вставить. Спасибо за идеи.