plasgos / crm-sdk
Official PHP SDK for the Plasgos CRM Api Integration API.
Requires
- php: >=8.1
- ext-hash: *
- ext-json: *
- guzzlehttp/guzzle: ^7.5
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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-Keyon every POST, reused on retries. - Typed exceptions carrying the API
errorCode. - A
Pagetype withautoPaging(). - 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,channelsendpoints(includingtemplates),templateVariablesmessagescontacts,contactGroupspipelines,leadsbroadcasts,followupswebhooks
$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:
->itemsand->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