PHPackages                             madtec/omnileads-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. madtec/omnileads-sdk

ActiveLibrary[API Development](/categories/api)

madtec/omnileads-sdk
====================

PHP SDK for OmniLeads API with Laravel integration

v1.0.0(3mo ago)00[1 PRs](https://github.com/Rizayev/omnileads-sdk-php/pulls)MITPHPPHP ^8.2CI passing

Since Apr 28Pushed 1mo agoCompare

[ Source](https://github.com/Rizayev/omnileads-sdk-php)[ Packagist](https://packagist.org/packages/madtec/omnileads-sdk)[ Docs](https://github.com/Rizayev/omnileads-sdk-php)[ RSS](/packages/madtec-omnileads-sdk/feed)WikiDiscussions main Synced yesterday

READMEChangelogDependencies (9)Versions (5)Used By (0)

OmniLeads SDK for PHP
=====================

[](#omnileads-sdk-for-php)

[![Latest Version on Packagist](https://camo.githubusercontent.com/f3267d7381eb4ee24008193379b00c719a74a168082ab8e8a143a19fb58c0873/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d61647465632f6f6d6e696c656164732d73646b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/madtec/omnileads-sdk)[![Tests](https://github.com/Rizayev/omnileads-sdk-php/actions/workflows/tests.yml/badge.svg)](https://github.com/Rizayev/omnileads-sdk-php/actions/workflows/tests.yml)[![Static Analysis](https://github.com/Rizayev/omnileads-sdk-php/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/Rizayev/omnileads-sdk-php/actions/workflows/static-analysis.yml)[![Total Downloads](https://camo.githubusercontent.com/4c62b482b5fab16d78ff99635774ec9e966c846e3e35fcc2ba3b842ef5e7d1ee/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d61647465632f6f6d6e696c656164732d73646b2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/madtec/omnileads-sdk)[![License](https://camo.githubusercontent.com/e8f8bba60cc2396db216537eeb5539cfe1bb8b2924889f00c63ec7279710120e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d61647465632f6f6d6e696c656164732d73646b2e7376673f7374796c653d666c61742d737175617265)](LICENSE)

Production-ready PHP SDK for the [OmniLeads API](https://api.madtec.ru/api). Ships with first-class Laravel integration (ServiceProvider, Facade, config publishing) but the client itself is framework-agnostic and works in any PHP 8.2+ project.

Requirements
------------

[](#requirements)

- PHP **8.2+**
- Laravel **10.x / 11.x / 12.x** (optional — only required when using the Laravel integration)
- Guzzle HTTP **7.8+**

Installation
------------

[](#installation)

```
composer require madtec/omnileads-sdk
```

Publish the Laravel config (optional but recommended):

```
php artisan vendor:publish --tag=omnileads-config
```

Set the environment variables in `.env`:

```
OMNILEADS_BASE_URL=https://api.madtec.ru/api
OMNILEADS_API_KEY=your-api-key-here
OMNILEADS_JWT=your-jwt-here          # only for /Auth/me/webhook-settings
OMNILEADS_TIMEOUT=30
OMNILEADS_RETRY_ENABLED=true
OMNILEADS_RETRY_TIMES=3
OMNILEADS_RETRY_SLEEP_MS=200
OMNILEADS_LOG_CHANNEL=stack          # optional: PSR-3 channel for request logs
```

Quick start
-----------

[](#quick-start)

### 1. Create a project

[](#1-create-a-project)

```
use Madtec\OmniLeads\DTO\Requests\CreateProjectRequest;
use Madtec\OmniLeads\Enums\ProjectState;
use Madtec\OmniLeads\Facades\OmniLeads;

$result = OmniLeads::projects()->create(new CreateProjectRequest(
    name: 'CRM import',
    limit: 1000,
    limitTypeId: 1,
    description: 'Imported daily',
    state: ProjectState::ACTIVE,
    geoRegions: [77, 78],
));

$projectId = $result->id;
```

### 2. Add a source

[](#2-add-a-source)

```
use Madtec\OmniLeads\DTO\Requests\CreateSourceRequest;
use Madtec\OmniLeads\Enums\SourceData;
use Madtec\OmniLeads\Enums\SourceType;
use Madtec\OmniLeads\Facades\OmniLeads;

OmniLeads::sources()->add($projectId, new CreateSourceRequest(
    sourceTypeId: SourceType::DOMAIN,
    sourceDataId: SourceData::MEGAFON,
    sourceValues: ['example.com'],
    limit: 500,
));
```

### 3. Fetch data for a day

[](#3-fetch-data-for-a-day)

```
use Madtec\OmniLeads\Facades\OmniLeads;

// All projects, no pagination
$data = OmniLeads::data()->byDay('2026-04-08');

// Or stream through paginated pages
foreach (OmniLeads::data()->iterateByDay('2026-04-08', pageSize: 1000) as $project) {
    foreach ($project->segments as $segment) {
        foreach ($segment->phones as $phone) {
            // process $phone->phone, $phone->createdAt
        }
    }
}
```

### 4. Sync projects in bulk

[](#4-sync-projects-in-bulk)

```
use Madtec\OmniLeads\DTO\Requests\SyncProjectDTO;
use Madtec\OmniLeads\DTO\Requests\SyncProjectsRequest;
use Madtec\OmniLeads\DTO\Requests\SyncSegmentDTO;
use Madtec\OmniLeads\Enums\SourceData;
use Madtec\OmniLeads\Enums\SourceType;
use Madtec\OmniLeads\Facades\OmniLeads;

$result = OmniLeads::sync()->push(new SyncProjectsRequest([
    new SyncProjectDTO(
        name: 'Daily import',
        limit: 5000,
        limitType: 'Дневной',
        segments: [
            SyncSegmentDTO::fromEnums(SourceType::DOMAIN, SourceData::MEGAFON, ['a.com'], 500),
            SyncSegmentDTO::fromEnums(SourceType::PHONE,  SourceData::MTS,     ['79001234567']),
        ],
    ),
]));

if (! $result->success) {
    foreach ($result->errors as $error) {
        report(new \RuntimeException($error));
    }
}
```

### 5. Receive webhook notifications

[](#5-receive-webhook-notifications)

See [Webhook handler](#webhook-handler) below.

Available methods
-----------------

[](#available-methods)

```
use Madtec\OmniLeads\Facades\OmniLeads;

// Projects
OmniLeads::projects()->list();
OmniLeads::projects()->get($projectId);
OmniLeads::projects()->create($createProjectRequest);
OmniLeads::projects()->update($projectId, $updateProjectRequest);
OmniLeads::projects()->changeState($projectId, 'paused');
OmniLeads::projects()->activate($projectId);   // shortcut
OmniLeads::projects()->pause($projectId);      // shortcut

// Sources
OmniLeads::sources()->add($projectId, $createSourceRequest);
OmniLeads::sources()->update($projectId, $sourceId, $updateSourceRequest);
OmniLeads::sources()->delete($projectId, $sourceId);

// Data
OmniLeads::data()->byDay('2026-04-08');
OmniLeads::data()->byDayPaged('2026-04-08', page: 1, pageSize: 1000);
OmniLeads::data()->iterateByDay('2026-04-08');

// Reference data
OmniLeads::limitTypes()->all();
OmniLeads::geoRegions()->all();           // remote API
OmniLeads::geoRegions()->builtin();       // hardcoded list, no HTTP

// Webhook settings (Bearer JWT auth)
OmniLeads::auth()->getWebhookSettings();
OmniLeads::auth()->updateWebhookSettings($webhookSettingsRequest);

// Sync
OmniLeads::sync()->push($syncProjectsRequest);
OmniLeads::sync()->pull();
```

Built-in enums
--------------

[](#built-in-enums)

```
use Madtec\OmniLeads\Enums\DayOfWeek;
use Madtec\OmniLeads\Enums\GeoRegions;
use Madtec\OmniLeads\Enums\ProjectState;
use Madtec\OmniLeads\Enums\SourceData;
use Madtec\OmniLeads\Enums\SourceType;

SourceType::DOMAIN->value;       // 3
SourceData::MEGAFON->value;      // 8
ProjectState::ACTIVE;            // active|paused
DayOfWeek::MONDAY->value;        // 1

GeoRegions::name(77);            // "Москва"
GeoRegions::all();               // [0 => "Все регионы", 1 => "Адыгея", ...]
```

Error handling
--------------

[](#error-handling)

Every error raised by the SDK extends `Madtec\OmniLeads\Exceptions\OmniLeadsException`. The HTTP-derived ones extend `ApiException`:

```
use Madtec\OmniLeads\Exceptions\ApiException;
use Madtec\OmniLeads\Exceptions\AuthenticationException;
use Madtec\OmniLeads\Exceptions\ConfigurationException;
use Madtec\OmniLeads\Exceptions\NotFoundException;
use Madtec\OmniLeads\Exceptions\ServerException;
use Madtec\OmniLeads\Exceptions\ValidationException;
use Madtec\OmniLeads\Facades\OmniLeads;

try {
    $project = OmniLeads::projects()->get($id);
} catch (ConfigurationException $e) {
    // Local pre-flight validation failed (bad GUID, missing JWT, ...)
} catch (ValidationException $e) {
    // 400 Bad Request — $e->errorMessage holds the API message
} catch (AuthenticationException $e) {
    // 401/403
} catch (NotFoundException $e) {
    // 404
} catch (ServerException $e) {
    // 5xx — already retried per `OMNILEADS_RETRY_*`
} catch (ApiException $e) {
    // any other HTTP error or network failure ($e->statusCode === 0)
}
```

`ApiException` exposes `statusCode`, `responseBody`, `requestMethod`, `requestUri`, `errorMessage`, and `requestId` (when present in the body).

Retry behaviour
---------------

[](#retry-behaviour)

Retries are enabled by default for **5xx** responses and network failures (connection refused, DNS timeouts, etc.). Backoff is exponential: `sleep_ms * 2^(attempt - 1)`. **4xx responses are never retried** because they signal a client-side error.

Disable retries entirely:

```
OMNILEADS_RETRY_ENABLED=false
```

Or per-request, by constructing a fresh `OmniLeadsClient` with `RetryConfig::disabled()`.

Known API quirks
----------------

[](#known-api-quirks)

Documented here so you do not have to rediscover them. Each item was confirmed by running the SDK against the production OmniLeads API.

### `GET /Project` is paginated

[](#get-project-is-paginated)

The published spec lists the response as a flat array, but the endpoint actually returns a paginated wrapper:

```
{
  "items":            [...],
  "page":             1,
  "pageSize":         10,
  "totalCount":       N,
  "totalPages":       N,
  "hasPreviousPage":  false,
  "hasNextPage":      false
}
```

`OmniLeads::projects()->list()` unwraps `items` for you. Use `listPaged()` for explicit pagination control or `iterateAll()` to walk every page.

### Source `limit` is read-only when project `limitTypeId = 1` (Эффективный)

[](#source-limit-is-read-only-when-project-limittypeid--1-эффективный)

If the project uses the balanced-between-sources limit type, the API auto-distributes the project's total limit across its sources. Trying to `PUT /Project/{id}/sources/{sourceId}` with a `limit` in the body returns:

```
HTTP 400
{ "message": "Редактирование лимита источника запрещено для выбранного типа лимита проекта" }

```

The SDK surfaces this as a `ValidationException` with the API message in `$e->errorMessage`. To control source limits manually, switch the project to `limitTypeId = 3` (Ручной) first via `PUT /Project/{id}`.

### There is no `DELETE /Project/{id}`

[](#there-is-no-delete-projectid)

The API responds with `HTTP 405 Method Not Allowed`. To soft-delete a project:

```
use Madtec\OmniLeads\Facades\OmniLeads;
use Madtec\OmniLeads\DTO\Requests\UpdateProjectRequest;

$projectId = '...';

// 1. drop every source
$project = OmniLeads::projects()->get($projectId);
foreach ($project->projectSources as $src) {
    OmniLeads::sources()->delete($projectId, $src->id);
}

// 2. pause it
OmniLeads::projects()->pause($projectId);

// 3. (optional) flag it visually
OmniLeads::projects()->update($projectId, new UpdateProjectRequest(
    name: '[ARCHIVED] '.$project->name,
));
```

### `POST /Project/sync` is currently unstable

[](#post-projectsync-is-currently-unstable)

At the time of writing, the sync endpoint returns `HTTP 500 {"message":"Внутренняя ошибка сервера"}` for every payload — even the exact example from the official documentation. The SDK serializes the payload correctly; you will get a `ServerException` until OmniLeads ships a fix. Track this with the platform's support if you depend on the sync flow.

### `PUT /Project/{id}` returns the project but skips a few optional fields

[](#put-projectid-returns-the-project-but-skips-a-few-optional-fields)

`workingDaysOfWeek`, `workingDates`, `excludedDates`, and `description` come back as `null` in the immediate `PUT` response, even when the change was applied. A subsequent `GET /Project/{id}` returns them correctly. The SDK exposes whatever the server sends — if you need the canonical state right after an update, re-fetch with `OmniLeads::projects()->get($id)`.

### Source ids are inconsistently typed

[](#source-ids-are-inconsistently-typed)

`POST /Project/{id}/sources` returns `id` as a string, while `PUT /Project/{id}/sources/{sourceId}` returns it as a number. The SDK normalizes both into `string $id` on the response DTO, so consumer code does not have to care.

### Webhook `phone` is a JSON number

[](#webhook-phone-is-a-json-number)

Each phone in `import.completed` arrives as `"phone": 79001234567` (numeric, not quoted). The SDK casts it to `string` on `DayPhone::$phone` so you keep arbitrary-precision values without losing leading-zero edge cases.

Webhook handler
---------------

[](#webhook-handler)

The OmniLeads platform pushes an `import.completed` event after each finished import. Below is a complete example of how to receive it in Laravel.

### Route

[](#route)

```
// routes/web.php

use App\Http\Controllers\OmniLeadsWebhookController;

Route::post(
    '/webhooks/omnileads/{secret}',
    OmniLeadsWebhookController::class,
)->where('secret', '[a-f0-9]{32}')->name('webhooks.omnileads');
```

> The OmniLeads API does not currently sign webhook payloads. Protect the endpoint with a shared secret embedded in the URL (as above) and/or an IP allow list. See [SECURITY.md](SECURITY.md).

### Controller

[](#controller)

```
// app/Http/Controllers/OmniLeadsWebhookController.php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Madtec\OmniLeads\Exceptions\ValidationException;
use Madtec\OmniLeads\Webhook\ImportCompletedReceived;
use Madtec\OmniLeads\Webhook\WebhookPayloadParser;

final class OmniLeadsWebhookController
{
    public function __construct(
        private readonly WebhookPayloadParser $parser,
    ) {}

    public function __invoke(Request $request, string $secret): JsonResponse
    {
        if (! hash_equals(config('services.omnileads.webhook_secret', ''), $secret)) {
            return response()->json(['error' => 'invalid secret'], 403);
        }

        try {
            $event = $this->parser->parse($request->getContent());
        } catch (ValidationException $e) {
            return response()->json(['error' => $e->errorMessage], 400);
        }

        ImportCompletedReceived::dispatch($event);

        return response()->json(['ok' => true]);
    }
}
```

### Listener

[](#listener)

```
// app/Listeners/ProcessOmniLeadsImport.php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Madtec\OmniLeads\Webhook\ImportCompletedReceived;

final class ProcessOmniLeadsImport implements ShouldQueue
{
    public function handle(ImportCompletedReceived $eventEnvelope): void
    {
        $event = $eventEnvelope->event;

        foreach ($event->projects as $project) {
            foreach ($project->segments as $segment) {
                foreach ($segment->phones as $phone) {
                    // store $phone->phone, $phone->createdAt
                }
            }
        }
    }
}
```

Register it in `EventServiceProvider`:

```
protected $listen = [
    \Madtec\OmniLeads\Webhook\ImportCompletedReceived::class => [
        \App\Listeners\ProcessOmniLeadsImport::class,
    ],
];
```

Testing your application
------------------------

[](#testing-your-application)

The Facade exposes a `fake()` helper plus a `swap()` method on the manager so you can inject a custom client in tests:

```
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Madtec\OmniLeads\Config\OmniLeadsConfig;
use Madtec\OmniLeads\Config\RetryConfig;
use Madtec\OmniLeads\Facades\OmniLeads;
use Madtec\OmniLeads\OmniLeadsClient;

$mock = new MockHandler([
    new Response(200, [], json_encode([])),
]);

$guzzle = new GuzzleClient([
    'base_uri' => 'https://api.test/api/',
    'handler'  => HandlerStack::create($mock),
]);

OmniLeads::fake(new OmniLeadsClient(
    config: new OmniLeadsConfig(
        baseUrl: 'https://api.test/api',
        apiKey: 'test',
        jwt: null,
        timeout: 5,
        retry: RetryConfig::disabled(),
    ),
    guzzle: $guzzle,
));

$result = OmniLeads::projects()->list(); // returns []
```

Without Laravel
---------------

[](#without-laravel)

The package works in any PHP project — Laravel only adds sugar:

```
use Madtec\OmniLeads\Config\OmniLeadsConfig;
use Madtec\OmniLeads\Config\RetryConfig;
use Madtec\OmniLeads\OmniLeadsClient;

$client = new OmniLeadsClient(
    config: new OmniLeadsConfig(
        baseUrl: 'https://api.madtec.ru/api',
        apiKey: getenv('OMNILEADS_API_KEY') ?: null,
        jwt: getenv('OMNILEADS_JWT') ?: null,
        timeout: 30,
        retry: new RetryConfig(enabled: true, times: 3, sleepMs: 200),
    ),
);

$projects = $client->projects()->list();
```

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md) for the full release history.

Credits
-------

[](#credits)

Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). A list of contributors is generated automatically by [contrib.rocks](https://contrib.rocks).

Security
--------

[](#security)

If you discover a security vulnerability, please follow the disclosure process in [SECURITY.md](SECURITY.md). Do **not** open a public GitHub issue.

License
-------

[](#license)

Released under the MIT License — see [LICENSE](LICENSE) for the full text.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance87

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 77.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

95d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/20006084?v=4)[Elsevar](/maintainers/Elsevar)[@elsevar](https://github.com/elsevar)

---

Top Contributors

[![Rizayev](https://avatars.githubusercontent.com/u/3787054?v=4)](https://github.com/Rizayev "Rizayev (7 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

phpapilaravelsdkomnileadsmadtec

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/madtec-omnileads-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/madtec-omnileads-sdk/health.svg)](https://phpackages.com/packages/madtec-omnileads-sdk)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M352](/packages/psalm-plugin-laravel)[illuminate/broadcasting

The Illuminate Broadcasting package.

7127.4M230](/packages/illuminate-broadcasting)[illuminate/log

The Illuminate Log package.

6225.7M670](/packages/illuminate-log)[illuminate/mail

The Illuminate Mail package.

5910.7M549](/packages/illuminate-mail)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
