Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Amocrm Api Library Laravel Package

amocrm/amocrm-api-library

Official PHP SDK for integrating with amoCRM. It handles OAuth 2.0 authentication, REST API requests, entities such as leads and contacts, custom fields, and webhooks, with client and model classes that simplify building CRM integrations.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. The package ships no Laravel service provider, so you wire it yourself.

composer require amocrm/amocrm-api-library:^5.0

Pin the major and commit composer.lock — this library cuts minor releases frequently (last release 2026-07-01) and the changelog is Russian-only, so surprise breaks are a real cost.

Where to look first. src/AmoRestClient.php is the whole surface area — every $client->leads(), ->contacts(), ->notes() accessor lives there. Then src/Oauth/ for the token model, and src/*/ for entity models (LeadModel, ContactModel, NoteModel, request/filter classes). The README is the primary doc and is in Russian; treat the source as the spec.

Step 1 — one-time OAuth, stored in your DB. amoCRM is per-account OAuth2, so you run the authorization-code flow once per portal and persist the pair.

// app/Services/Amo/AuthFactory.php
$auth = new AmoCRMOAuth(
    config('services.amocrm.client_id'),
    config('services.amocrm.client_secret'),
    config('services.amocrm.redirect_uri'),
);
$auth->setBaseDomain(config('services.amocrm.domain')); // acme.amocrm.ru

// callback
$token = $auth->getAccessTokenByAuthorizationCode($request->query('code'));
AmoAccount::updateOrCreate([...], [
    'access_token'  => $token->getToken(),
    'refresh_token' => $token->getRefreshToken(),
    'expires_at'    => now()->addSeconds($token->getExpiresIn()),
]);

Step 2 — build the client per account.

$auth = (new AmoCRMOAuth($id, $secret, $redirect))
    ->setBaseDomain($account->domain);

$token = new Token();
$token->setToken($account->access_token)
      ->setRefreshToken($account->refresh_token)
      ->setExpiresIn($account->expires_at->diffInSeconds(now(), false));
$auth->setAccessToken($token);

$client = new AmoRestClient($auth);
$client->setAccountBaseDomain($account->domain);

Step 3 — first use case: website form → lead + contact.

$contact = (new ContactModel())
    ->setFirstName($data['first_name'])
    ->setCustomFieldsValues(new CustomFieldsValuesCollection([
        (new CustomFieldValueModel())
            ->setFieldCode('EMAIL')
            ->setValues([(new CustomFieldValueModel())->setValue($data['email'])]),
    ]));

$contact = $client->contacts()->addOne($contact);

$lead = (new LeadModel())
    ->setName('Website: pricing enquiry')
    ->setPipelineId($pipelineId)
    ->setStatusId($incomingStatusId)
    ->setResponsibleUserId($userId)
    ->setContactsId([$contact->getId()]);

$client->leads()->addOne($lead);

That create-plus-link flow is the 80% case and is a good smoke test that your token plumbing works.

Implementation Patterns

Bind a per-account client in the container, never new it inline. One portal per tenant is normal, so resolve by ID rather than a global singleton:

// app/Providers/AmoServiceProvider.php
$this->app->bind(AmoRestClient::class, fn ($app) => $app->make(AmoClientFactory::class)
    ->forAccount($app->make(AmoAccount::class)));

Wrap it in your own AmoGateway with domain methods (createLead(), syncContact()) so the ~40 service accessors never leak into controllers.

Refresh centrally, not in call sites. Access tokens last ~24h; refresh tokens rotate. Put the check in the factory and take a lock:

Cache::lock("amo:token:{$account->id}", 15)->block(10, function () use ($account) {
    $account->refresh();
    if ($account->expires_at->isFuture()) return;
    $new = (new AmoCRMOAuth($id, $secret, $redirect))
        ->setBaseDomain($account->domain)
        ->getAccessTokenByRefreshToken($account->refresh_token);
    $account->update([...]);
});

Queue every write. API calls are network-bound at 7 req/s per integration; synchronous writes from a web request will time out and, worse, double-submit. Use dispatch() + retry(5, [1000, 5000, 15000]) and backoff on 429.

Bulk sync with pagination + chunked jobs. For reads, page the collection and hand each page to a job:

$collection = $client->leads()->get(
    (new LeadsRequest())
        ->setLimit(250)
        ->setPage($page)
        ->setWith(['contacts'])
        ->setFilter((new LeadsFilterModel())->setUpdatedAt($since, $until))
);

->setWith(['contacts']) is the single biggest win — it collapses an N+1 into one call per page. Filter on updated_at as a checkpoint column, keyed on id for idempotency, so a re-run is safe.

Cache reference data aggressively. Pipelines, statuses, users and custom-field definitions change rarely and cost a request each:

Cache::rememberForever("amo:{$account->id}:custom_fields:leads", fn () =>
    $client->customFields('leads')->get()->all()
);

Map field names to IDs once and store the map; resolve field_id from it rather than hardcoding integers in code.

Webhooks are thin. amoCRM webhooks deliver account/entity IDs and little else, and they're not signed. Receive, verify the portal (account.subdomain), enqueue, then re-fetch the entity through the API in the job. Exclude the route from CSRF in bootstrap/app.php.

Error handling by class, not message. Catch AmoCRMApiException and branch on getErrorCode() / getStatusCode():

  • 401 → force refresh, retry once.
  • 429 → release the job with exponential backoff.
  • 400 with validation errors → log getLastRequestInfo(), do not retry.

Testing. AmoRestClient accepts a PSR-18 HTTP client, so inject a fake returning canned JSON and unit-test your gateway without hitting the portal. This is the cheapest way to keep sync jobs trustworthy.

Gotchas and Tips

  • Refresh tokens are single-use and rotating. Two concurrent workers refreshing the same account will invalidate each other's token and brick the integration until manual re-auth. The lock above is not optional — it's the most common production failure with this library.
  • setAccountBaseDomain() is required and easy to forget; without it you get opaque 4xx errors. Also don't confuse it with the OAuth setBaseDomain().
  • 204 No Content throws. Deletes and empty result sets raise AmoCRMApiNoContentException, a separate class from the general API exception. Wrap deletes in a catch or your jobs will "fail" on success.
  • Batch ceiling is ~250 entities per request. add()/update() with a bigger collection will be rejected; chunk to 250 (conservative: 200) and queue the chunks.
  • 429 is per integration per account, not per token. Laravel's RateLimiter won't see these calls — use a Redis token bucket around the gateway if you have many workers.
  • Custom field writes are the fiddly part. setFieldCode() only works for system codes (EMAIL, PHONE); anything custom needs setFieldId() from your cached map. Multiselect/enum values need setEnumId() alongside setValue(), and the value object shape differs per field type — dump one real entity with getLastRequestInfo() before writing a mapper.
  • getLastRequestInfo() is your debugger. It contains the last request/response pair; log it on every 4xx. Without it, this library's exceptions are nearly information-free.
  • Entity linking on create is inconsistent. setContactsId() on a new lead behaves differently across versions and account configs; if linking silently drops, fall back to creating the lead, then updating it with the _embedded.contacts payload.
  • Extension points are clean where it matters: implement TokenStorageInterface to let the library persist tokens directly, and decorate the PSR-18 client with a Guzzle middleware for structured logging and retries instead of wrapping every call site.
  • Ecosystem is thin (0 dependents, 185 stars). There's no community Laravel bridge, no English docs, and answers are mostly Russian forums. Budget for reading src/ and for owning your own abstraction layer — the library is a competent HTTP/model layer, not a framework integration.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle