Download the PHP package silon/silon-sdk without Composer
On this page you can find all versions of the php package silon/silon-sdk. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package silon-sdk
Silon PHP SDK
PHP client for the Silon messaging platform API — send
messages on any channel (WhatsApp, SMS, email, push, web push, voice), manage
CRM contacts and groups, run bulk campaigns, consume events, and verify
webhooks. PHP 8.1+, zero runtime dependencies (native curl), PSR-4.
Installation
Requires PHP >= 8.1 with the curl and json extensions. The SDK pulls in no
runtime packages; the default transport is native curl. You can swap in your
own HTTP client (Guzzle, a PSR-18 adapter, a mock) — see
Custom HTTP client.
Quickstart
Configuration resolves in this order and fails fast at construction (a
Silon\Exception\SilonException) if a required value is missing:
- API key —
apiKey, elseSILON_API_KEY. Required. - Base URL —
baseUrl, elseSILON_BASE_URL, elseworkspace→https://<workspace>.silon.tech, elseSILON_WORKSPACE. Required; a trailing slash is stripped. timeout(seconds, default30),maxRetries(default6),headers(added to every request).
Every operation takes an associative array of the API's own field names
(snake_case) and returns a typed model whose properties mirror the API.
Unknown response fields are preserved and readable via $model->new_field, so
an older SDK keeps working as the API grows.
Sending
Three entry points, every channel: messages->send targets a single recipient
via to; messages->sendBatch sends many independent, personalised messages
in one call — inline rows via messages (max 500) or an uploaded CSV via
file; broadcasts->create fans one piece of content out to an audience
(a client group, explicit client ids, or an inline recipients list). On
send, exactly one of to / audience is required, and on sendBatch
exactly one of messages / file — the SDK validates client-side and throws
SilonException before any network call.
Every messages->send / messages->sendBatch / broadcasts->create /
otp->send call carries an Idempotency-Key header (auto-generated UUIDv4
unless you pass idempotency_key), so automatic retries can never double-send.
The key is a request header, never part of the JSON body. Channel-specific
fields not covered by the documented keys can go in extra_body, which is
merged into the JSON body last.
Scheduling and cancellation
Pass send_at — a DateTimeInterface with a UTC offset, or an ISO-8601
string (e.g. "2026-07-15T09:00:00+03:00") — on messages->send,
broadcasts->create, or the file form of messages->sendBatch to schedule
the send. It must be strictly in the future and at most 90 days ahead; naive
date-times are rejected — otherwise the server answers 422 send-at-invalid.
The response is the normal 202 envelope with status: "scheduled", and its
id is stable across the lifecycle: messages->retrieve / broadcasts-> retrieve resolve it before dispatch ("scheduled") and after (the normal
queued/sent lifecycle).
messages->cancel($id) and broadcasts->cancel($id) return the same envelope
types as their create calls (now showing status: "canceled") and emit a
message.canceled / broadcast.canceled event. Cancel is idempotent by
nature — repeating it answers 200 with the canceled envelope again (no
Idempotency-Key is sent, and none is needed). Once the send has dispatched
(or for an immediate send's id) the server answers 409 not-cancellable
(ConflictException); an unknown id is a 404. send_at on sendBatch works
with the file form only; with inline messages it is rejected
422 batch-invalid (schedule those individually via messages->send).
Suppressions
$client->suppressions manages the workspace do-not-contact list. A row is an
address (E.164 phone or email, stored normalized so any formatting matches)
optionally scoped to one channel — omit channel to suppress the address
everywhere. It is enforced automatically on every send path:
- Single-recipient sends (
messages->sendwithto,otp->send) to a suppressed address reject422 recipient-suppressed(UnprocessableEntityException). - Fan-outs (
broadcasts->create,messages->sendBatch, legacy bulk) silently skip suppressed recipients — never an error. The202envelope itemises why rows were skipped in an additiveskippedobject (suppressed/wrong_channel/duplicate), withskipped_countstaying the sum. On broadcastsskippedisnullexactly whentarget_countisnull(a scheduled audience resolving at dispatch).
Transactional/legal sends may bypass suppression with
'override_suppression' => true — single-recipient sends only, and the key
must carry the suppressions:override scope (else 403 missing-scope).
CRM: clients and groups
Templates
Slug-keyed message templates with an immutable version spine — the same rows
sends render for 'template' => ['slug' => ...].
Webhooks
Verify the Silon-Signature header on incoming deliveries — no HTTP client
needed. Always verify against the raw request body, not re-encoded JSON.
Webhooks::verifySignature($payload, $header, $secret, $tolerance = 300)
returns a bool (constant-time compare; $tolerance <= 0 skips the freshness
check), and Webhooks::sign($secret, $payload, $timestamp = null) produces a
valid header for tests. You can also read the same event stream over HTTP with
$client->events->list() / $client->events->retrieve($id), and manage
subscriptions with $client->webhookEndpoints (create returns a one-time
secret; ->test($id) sends a signed ping; ->listAttempts($id) is the
delivery ledger).
Pagination
Cursor endpoints return a Silon\CursorPage, which is iterable, count()-able,
and indexable for the current page, and offers hasNextPage(), nextPage(),
and a lazy autoPaging() generator that walks every page. nextPage() never
follows the opaque next URL directly — it extracts only its query params and
re-requests the original path against your configured base URL (proxy safety).
Errors
Every non-2xx response raises a typed exception under Silon\Exception\,
selected by status code, all extending ApiStatusException (itself a
SilonException):
| Status | Exception |
|---|---|
| 400 | BadRequestException |
| 401 | AuthenticationException |
| 403 | PermissionDeniedException |
| 404 | NotFoundException |
| 409 | ConflictException |
| 410 | GoneException |
| 422 | UnprocessableEntityException |
| 429 | RateLimitException (adds ->retryAfter seconds) |
| ≥500 | InternalServerException |
Each carries ->statusCode, ->requestId (from X-Request-Id), ->errorType,
->errors (a list of ErrorDetail with code / detail / attr),
->retryable (the body's verbatim retryable flag, null when absent —
true iff retrying the same request could ever succeed), and ->body (the
parsed JSON, useful for shapes like the OTP-verify failure's
remaining_attempts). Transport failures raise ApiConnectionException
(timeouts: ApiTimeoutException, a subtype).
Retries
The SDK retries automatically (default maxRetries = 6) with exponential
backoff and jitter, honouring the server's Retry-After / RateLimit-Reset
hint. A request is retried only when it is safe: the method is
GET/HEAD/OPTIONS/PUT/DELETE or it carries an Idempotency-Key (so every
send / sendBatch / broadcasts->create / otp->send is retry-safe), and
the failure is a connection error/timeout or HTTP 429/500/502/503/504. Other
POST/PATCH requests are never retried. The same Idempotency-Key is replayed
on every attempt, so a retried send cannot double-fire.
Test mode
sk_test_ API keys traverse the full pipeline (validation, scopes, throttles,
idempotency, delivery rows, events) but never reach a provider and never bill;
every affected envelope carries livemode: false. Delivery status is simulated
a few seconds after the 202, so polling and webhooks behave realistically.
Magic recipients (test mode only, deterministic):
| Recipient | Result |
|---|---|
+15005550001 / [email protected] |
delivered |
+15005550002 / [email protected] |
failed (simulated provider error) |
+15005550009 / [email protected] |
always suppressed (single send → 422 recipient-suppressed; fan-out → skipped into skipped.suppressed) |
| any other | delivered |
In live mode a magic recipient is rejected 422 test-recipient-in-live.
Test-mode OTPs are never dispatched; the magic code 000000 always verifies
(and only it). Webhook endpoints carry a create-time livemode flag (default
true): test events deliver only to livemode: false endpoints, live events
only to livemode: true ones.
Custom HTTP client
The default transport is native curl. Inject any
Silon\Http\HttpClientInterface to control TLS (private CAs, client certs),
route through a proxy, or drive the SDK against a mock in tests:
Async
This SDK is synchronous, matching the platform idiom for PHP. Every call blocks until the response is ready and returns a typed model. (The Python SDK additionally ships an async client.)
Development
License
MIT
All versions of silon-sdk with dependencies
ext-curl Version *
ext-json Version *