Search by

plasgos / crm-sdk

plasgos

Official PHP SDK for the Plasgos CRM Api Integration API.

v2.0.0 2026-10-27 05:49 UTC

This package is auto-updated.

Last update: 2026-10-27 05:57:59 UTC


README

Official PHP SDK for Plasgos CRM: API Integrasi v3. The SDK covers:

  • omnichannel messages (WhatsApp official and unofficial, Instagram, Messenger)
  • contacts and leads
  • broadcasts
  • followups
  • signed webhooks

Features:

  • PHP 8.1+ with PSR-18 / PSR-17 transport (Guzzle by default, injectable).
  • Automatic request signing that covers the method, path, query and body.
  • An automatic Idempotency-Key on every POST, reused on retries.
  • Typed exceptions carrying the API errorCode.
  • A Page type with autoPaging().
  • Webhook HMAC verification.

Upgrading from 1.x? See UPGRADING.md. The v1 client lives on as the deprecated LegacyClient.

Install

composer require plasgos/crm-sdk

Quick start

use Plasgos\CrmSdk\Client;
use Plasgos\CrmSdk\ClientConfig;
use Plasgos\CrmSdk\Content;
use Plasgos\CrmSdk\Enum\BaseUrl;
use Plasgos\CrmSdk\Exception\PlasgosApiException;

$client = new Client(
    getenv('PLASGOS_API_KEY'),
    getenv('PLASGOS_SECRET_KEY'),
    new ClientConfig(baseUrl: BaseUrl::Sandbox), // Production by default; or an absolute URL
);

$me = $client->me->get();
echo $me->data['tenant']['name'], PHP_EOL;

try {
    $sent = $client->messages->send([
        'endpoint_id' => '6281100000001',
        'contact' => ['phone_number' => '081234567890', 'name' => 'Budi Santoso'], // profile only for a NEW contact
        'content' => Content::template('66f1a2b3c4d5e6f708192a3b')
            ->dynamic('name', '{{full_name}}')
            ->custom('order', 'INV-2026-0917'),
    ], idempotencyKey: 'wc-1234-processing');
    echo $sent->data['status'], PHP_EOL; // pending|sent|failed (202)
} catch (PlasgosApiException $e) {
    // Branch on the stable code; show getMessage() (Indonesian) to people.
    if ($e->errorCode === 'requires_template') { /* ... */ }
    error_log("{$e->errorCode} {$e->requestId}");
}

foreach ($client->contacts->list(['per_page' => 100])->autoPaging() as $contact) {
    // every page fetched lazily
}

$client->contacts->upsert(['phone_number' => '081234567890', 'name' => 'Budi', 'tags' => ['woocommerce']]);
$client->leads->create(['contact' => ['phone_number' => '081234567890'], 'pipeline_id' => 'pl_1']);
$client->followups->enroll('fu_1', ['contact' => ['id' => 'ct_1']]);

Resources:

  • me, channels
  • endpoints (including templates), templateVariables
  • messages
  • contacts, contactGroups
  • pipelines, leads
  • broadcasts, followups
  • webhooks

$client->request($method, $path, ...) is an escape hatch for endpoints that do not have a method yet. The full surface is in docs/SDK_SPEC.md §3.

Responses

$r->httpStatus;  // 200, 201, 202 …
$r->message;     // user-facing text
$r->data;        // payload
$r->meta;        // e.g. ['created' => true] (201) / ['created' => false] (200)
$r->requestId;   // quote it when contacting Plasgos
$r->rateLimit;   // ['limit' => 120, 'remaining' => 119, 'reset' => 42]
$r->replayed;    // true when the server replayed the first response for this Idempotency-Key

data and meta are typed per operation for PHPStan/Psalm and IDEs: types are generated from OpenAPI as array shapes in Plasgos\CrmSdk\Types\ApiTypes (Response<GetContactData, GetContactMeta>, Page<ListContactsItem>, bodies such as SendMessageBody). Use a schema in your own code with @phpstan-import-type Contact from \Plasgos\CrmSdk\Types\ApiTypes.

List methods return a Page, which provides:

  • ->items and ->pagination
  • ->hasNextPage() and ->nextPage()
  • ->autoPaging()
  • iteration and count() over the current page

Errors

PlasgosApiException
├─ AuthenticationException            401
│  └─ SignatureExpiredException       401 signature_expired (check the server clock)
├─ PermissionException                403 (scope_missing, api_key_revoked, …)
│  └─ PlanException                   403 not_entitled
├─ NotFoundException                  404
├─ ConflictException                  409 (idempotency_in_progress, identity_taken, …)
├─ BadRequestException                400
├─ ValidationException                422 / 400 with fields  → ->fields
├─ RateLimitException                 429 → ->retryAfter (rate_limited | daily_quota_exceeded)
├─ ServerException                    5xx
│  ├─ ChannelException                502 provider error
│  └─ ServiceUnavailableException     503
└─ TransportException                 no response

Every exception exposes errorCode, errorType, details, fields, docUrl, requestId and retryAfter.

Retries. Transport errors, 502/503 and rate_limited (for any method) are retried automatically up to maxRetries, reusing the same idempotency key. A plain 500 is retried only for GET. Other 4xx errors and daily_quota_exceeded are never retried.

Webhooks

use Plasgos\CrmSdk\Webhook\EventType;
use Plasgos\CrmSdk\Webhook\Webhook;

$event = Webhook::verify(file_get_contents('php://input'), getallheaders(), $signingSecret);
// during secret rotation: Webhook::verify($body, $headers, [$newSecret, $previousSecret]);
// PSR-7: Webhook::verifyRequest($serverRequest, $signingSecret);

if ($event['type'] === EventType::MESSAGE_RECEIVED) { /* $event['data'] is a Message */ }

Deliveries can repeat, so drop $event['id'] values you have already processed.

Custom transport

new ClientConfig(
    httpClient: $psr18Client,          // e.g. an adapter over wp_remote_request()
    requestFactory: $psr17RequestFactory,
    streamFactory: $psr17StreamFactory,
    timeout: 20.0,
    maxRetries: 2,
    maxRetryWait: 30,
    userAgent: 'my-app/1.0 plasgos-crm-sdk-php/2.0.0',
    logger: $psr3Logger,               // one debug record per attempt; never headers or bodies
);

Per-call options

Every resource method takes a RequestOptions as its last argument (docs/SDK_SPEC.md §1.1):

use Plasgos\CrmSdk\RequestOptions;

$client->messages->send($input, 'wc-1234-processing', new RequestOptions(timeout: 5, maxRetries: 0));
$client->contacts->get($id, new RequestOptions(headers: ['X-Trace-Id' => $traceId]));

timeout per call needs the default Guzzle transport (PSR-18 has no per-request timeout); with your own PSR-18 client set the timeout there; the SDK throws LogicException rather than ignore it.

Legacy v1 client (deprecated)

LegacyClient (/api/v1) and CredentialsClient (/v2/integration) keep the 1.x behaviour and the Legacy\Envelope response type. See docs/SDK_SPEC_LEGACY_V1.md.

Development

composer install
composer test
composer analyse   # PHPStan level max
# optional smoke test against a real server:
PLASGOS_LIVE_BASE_URL=https://api-crm.sandbox.plasgos.co.id PLASGOS_LIVE_API_KEY=… PLASGOS_LIVE_SECRET_KEY=… composer test -- --filter LiveTest