Интеграция CRM-системы amoCRM с сайтом
amoCRM ориентирована на отделы продаж: воронка, контакты, задачи, чаты. REST API покрывает все основные сущности. Аутентификация — только через OAuth 2.0 (не webhook-токены как в Битрикс24), что требует настройки приложения и хранения refresh-токена.
OAuth 2.0 для server-to-server
amoCRM использует Authorization Code flow с долгоживущим refresh-токеном (60 дней). Алгоритм:
- Создаём интеграцию в аккаунте amoCRM:
Настройки → Интеграции → Создать интеграцию - Получаем
client_id,client_secret,redirect_uri - Первичная авторизация — ручной шаг через браузер (генерация code)
- Обмен code на access + refresh токены — программно
- Автоматическое обновление access-токена через refresh
class AmoCrmTokenStorage {
private const CACHE_KEY = 'amocrm_tokens';
public function getAccessToken(): string {
$tokens = Cache::get(self::CACHE_KEY);
if (!$tokens || now()->gte($tokens['expires_at'])) {
$tokens = $this->refreshTokens($tokens['refresh_token']);
}
return $tokens['access_token'];
}
private function refreshTokens(string $refreshToken): array {
$response = Http::post(config('amocrm.base_url') . '/oauth2/access_token', [
'client_id' => config('amocrm.client_id'),
'client_secret' => config('amocrm.client_secret'),
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'redirect_uri' => config('amocrm.redirect_uri'),
]);
$data = $response->json();
$tokens = [
'access_token' => $data['access_token'],
'refresh_token' => $data['refresh_token'],
'expires_at' => now()->addSeconds($data['expires_in'] - 60),
];
Cache::put(self::CACHE_KEY, $tokens, now()->addDays(60));
// Также сохраняем в БД как резерв (cache может очиститься)
DB::table('settings')->updateOrInsert(
['key' => 'amocrm_tokens'],
['value' => json_encode($tokens), 'updated_at' => now()]
);
return $tokens;
}
}
API-клиент
class AmoCrmClient {
private AmoCrmTokenStorage $tokenStorage;
private string $baseUrl;
public function __construct(AmoCrmTokenStorage $tokenStorage) {
$this->tokenStorage = $tokenStorage;
$this->baseUrl = rtrim(config('amocrm.base_url'), '/') . '/api/v4';
}
public function get(string $endpoint, array $params = []): array {
return Http::withToken($this->tokenStorage->getAccessToken())
->timeout(15)
->get($this->baseUrl . $endpoint, $params)
->throw()
->json();
}
public function post(string $endpoint, array $data): array {
return Http::withToken($this->tokenStorage->getAccessToken())
->timeout(15)
->post($this->baseUrl . $endpoint, $data)
->throw()
->json();
}
public function patch(string $endpoint, array $data): array {
return Http::withToken($this->tokenStorage->getAccessToken())
->timeout(15)
->patch($this->baseUrl . $endpoint, $data)
->throw()
->json();
}
}
Создание контакта и сделки
amoCRM строится на трёх основных сущностях: Contact, Lead (сделка), Company. Новый лид с сайта — это связка Contact + Lead:
class AmoCrmLeadService {
public function createLeadFromForm(array $formData): array {
$amo = app(AmoCrmClient::class);
// Ищем существующий контакт по телефону
$existing = $amo->get('/contacts', [
'query' => $formData['phone'],
'with' => 'leads',
]);
$contactId = null;
if (!empty($existing['_embedded']['contacts'])) {
$contactId = $existing['_embedded']['contacts'][0]['id'];
}
// Создаём контакт если не найден
if (!$contactId) {
$result = $amo->post('/contacts', [[
'name' => $formData['name'],
'custom_fields_values' => [
[
'field_code' => 'PHONE',
'values' => [['value' => $formData['phone'], 'enum_code' => 'WORK']],
],
[
'field_code' => 'EMAIL',
'values' => [['value' => $formData['email'] ?? '', 'enum_code' => 'WORK']],
],
],
]]);
$contactId = $result['_embedded']['contacts'][0]['id'];
}
// Создаём сделку
$pipelineId = config('amocrm.pipeline_id');
$stageId = config('amocrm.initial_stage_id');
$leadResult = $amo->post('/leads', [[
'name' => 'Заявка с сайта: ' . $formData['name'],
'pipeline_id' => $pipelineId,
'status_id' => $stageId,
'_embedded' => [
'contacts' => [['id' => $contactId]],
],
'custom_fields_values' => [
[
'field_id' => config('amocrm.fields.source'),
'values' => [['value' => $formData['source'] ?? 'Сайт']],
],
[
'field_id' => config('amocrm.fields.utm_campaign'),
'values' => [['value' => session('utm_campaign') ?? '']],
],
],
]]);
$leadId = $leadResult['_embedded']['leads'][0]['id'];
// Добавляем примечание
$amo->post('/leads/' . $leadId . '/notes', [[
'note_type' => 'common',
'params' => ['text' => 'Сообщение: ' . ($formData['message'] ?? '')],
]]);
return ['lead_id' => $leadId, 'contact_id' => $contactId];
}
}
Webhook от amoCRM
Настройка webhook в amoCRM: Настройки → Интеграции → Webhooks. При изменении сделки amoCRM шлёт POST:
// routes/api.php
Route::post('/webhooks/amocrm', [AmoCrmWebhookController::class, 'handle']);
class AmoCrmWebhookController extends Controller {
public function handle(Request $request): Response {
// amoCRM шлёт данные в form-encoded формате, не JSON!
$data = $request->all();
if (isset($data['leads']['status'])) {
foreach ($data['leads']['status'] as $lead) {
$this->syncLeadStatus($lead['id'], $lead['status_id']);
}
}
if (isset($data['leads']['add'])) {
// Новая сделка создана в amoCRM (не с сайта)
foreach ($data['leads']['add'] as $lead) {
Log::info('New lead in amoCRM', ['id' => $lead['id']]);
}
}
return response('ok');
}
private function syncLeadStatus(int $leadId, int $statusId): void {
$order = Order::where('amocrm_lead_id', $leadId)->first();
if (!$order) return;
$map = [
config('amocrm.stages.won') => 'completed',
config('amocrm.stages.lost') => 'cancelled',
config('amocrm.stages.in_work') => 'processing',
];
if ($status = $map[$statusId] ?? null) {
$order->update(['status' => $status]);
}
}
}
Пользовательские поля
amoCRM поддерживает кастомные поля для сделок и контактов. ID полей нужно получить через API и сохранить в конфиге:
// Получить список полей воронки
$fields = $amo->get('/leads/custom_fields');
foreach ($fields['_embedded']['custom_fields'] as $field) {
echo $field['id'] . ' — ' . $field['name'] . PHP_EOL;
}
Затем в config/amocrm.php:
'fields' => [
'source' => 12345,
'utm_source' => 12346,
'utm_campaign' => 12347,
'order_number' => 12348,
],
Обновление сделки при оплате
add_action('order_paid', function(Order $order) {
if (!$order->amocrm_lead_id) return;
app(AmoCrmClient::class)->patch('/leads/' . $order->amocrm_lead_id, [
'price' => $order->total,
'status_id' => config('amocrm.stages.won'),
'custom_fields_values' => [
[
'field_id' => config('amocrm.fields.order_number'),
'values' => [['value' => $order->number]],
],
],
]);
});
Сроки реализации
OAuth-авторизация, базовая отправка лидов из форм, хранение токена: 1–2 дня. Полноценная интеграция: двусторонняя синхронизация статусов, webhook-обработчик, кастомные поля, UTM-метки: 3–4 дня. Синхронизация каталога, создание задач по событиям сайта, интеграция чата: плюс 2–3 дня.







