PHPackages                             confish/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. [Utility &amp; Helpers](/categories/utility)
4. /
5. confish/sdk

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

confish/sdk
===========

Official PHP SDK for confish — typed configuration, actions, and webhooks.

v0.1.0(1mo ago)048↑200%MITPHPPHP ^8.1CI passing

Since May 1Pushed 1mo agoCompare

[ Source](https://github.com/confishhq/confish-php)[ Packagist](https://packagist.org/packages/confish/sdk)[ Docs](https://confi.sh)[ RSS](/packages/confish-sdk/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (3)Versions (2)Used By (0)

confish/sdk
===========

[](#confishsdk)

Official PHP SDK for [confish](https://confi.sh) — typed configuration, actions, and webhook verification.

- One dependency (Guzzle)
- Typed exceptions and automatic retry on `429`/`5xx`
- Long-running action consumer with graceful-shutdown hook
- HMAC-SHA256 webhook verification (no extra deps)

Install
-------

[](#install)

```
composer require confish/sdk
```

Requires PHP 8.1+.

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

[](#quick-start)

```
use Confish\Confish;

$client = new Confish(
    envId: getenv('CONFISH_ENV_ID'),
    apiKey: getenv('CONFISH_API_KEY'),
);

$config = $client->fetch();
echo $config['site_name'];
```

`fetch`, `update`, and `replace` return `array`. PHPStan/Psalm users can document expected shapes via array shapes:

```
/** @var array{site_name: string, max_upload_mb: int, maintenance_mode: bool} $config */
$config = $client->fetch();
```

Reading and writing config
--------------------------

[](#reading-and-writing-config)

```
// GET /c/{env_id}
$config = $client->fetch();

// PATCH — only listed fields change
$client->update(['maintenance_mode' => true]);

// PUT — replaces everything; omitted fields reset to defaults
$client->replace([
    'site_name'        => 'My App',
    'max_upload_mb'    => 50,
    'maintenance_mode' => false,
]);
```

`update` and `replace` return the full updated configuration.

> Write access must be enabled in environment settings before `update` and `replace` will work.

Logging
-------

[](#logging)

```
use Confish\LogLevel;

$client->logger->info('Worker started', ['region' => 'eu-west-1']);
$client->logger->error('Job failed', ['job_id' => 'abc']);

// Or directly:
$logId = $client->log(LogLevel::Critical, 'system down', ['code' => 503]);
```

Levels via the `LogLevel` enum: `Debug`, `Info`, `Notice`, `Warning`, `Error`, `Critical`, `Alert`.

Actions
-------

[](#actions)

The action consumer polls for pending actions, acknowledges them, runs your handler, and reports completion or failure — including idempotent skip if another consumer claimed the same action first.

```
use Confish\Action;
use Confish\ActionUpdater;
use Confish\Confish;
use Confish\Exception\SkipActionException;

$client = new Confish(envId: '...', apiKey: '...');

// Wire SIGTERM/SIGINT to a stop flag (requires ext-pcntl).
$shouldStop = false;
if (function_exists('pcntl_async_signals')) {
    pcntl_async_signals(true);
    pcntl_signal(SIGTERM, function () use (&$shouldStop) { $shouldStop = true; });
    pcntl_signal(SIGINT,  function () use (&$shouldStop) { $shouldStop = true; });
}

$client->actions->consume(
    handler: function (Action $action, ActionUpdater $u): ?array {
        if ($action->type === 'place_order') {
            $u->update('Submitting order', ['params' => $action->params]);
            // ... do work ...
            return ['order_id' => 'abc123', 'filled_price' => 66980.0];
        }
        throw new RuntimeException("unknown action type: {$action->type}");
    },
    pollInterval: 15.0,    // base — defaults to 15s
    maxPollInterval: 60.0, // adaptive backoff cap
    shouldStop: fn (): bool => $shouldStop,
    onError: fn (Throwable $e, Action $a) => error_log("action {$a->id}: {$e->getMessage()}"),
);
```

What happens automatically:

- A returned `array` becomes the action's `result` on completion.
- Throwing any exception fails the action with `['error' => $e->getMessage()]`.
- Throwing `SkipActionException` leaves the action acknowledged without resolving it.
- A `409 Conflict` on ack is silently skipped — safe to run multiple consumers.
- The `shouldStop` callback is checked at the top of every poll; the loop exits cleanly.
- After 3 consecutive empty polls the loop doubles its sleep up to `maxPollInterval`, resetting to `pollInterval` the moment any action is processed. Idle consumers make ~240 requests/hour by default.

You can also drive the lifecycle manually:

```
$actions = $client->actions->list();
$client->actions->ack('action_id');
$client->actions->update('action_id', 'progress', ['step' => 2]);
$client->actions->complete('action_id', ['order_id' => 'abc']);
$client->actions->fail('action_id', ['error' => 'timeout']);
```

Webhook verification
--------------------

[](#webhook-verification)

```
use Confish\Webhook;

// Inside a controller / route handler:
$body      = file_get_contents('php://input');         // raw, unparsed
$signature = $_SERVER['HTTP_X_CONFISH_SIGNATURE'] ?? null;

if (! Webhook::verify(
    body: $body,
    signature: $signature,
    secret: getenv('CONFISH_WEBHOOK_SECRET'),
)) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($body, true);
// handle $payload['event'] ...
```

Laravel example:

```
use Confish\Webhook;
use Illuminate\Http\Request;

Route::post('/webhook', function (Request $request) {
    abort_unless(
        Webhook::verify(
            body: $request->getContent(),
            signature: $request->header('X-Confish-Signature'),
            secret: config('services.confish.webhook_secret'),
        ),
        401,
    );
    $payload = $request->json()->all();
    // ...
    return response()->noContent();
});
```

`verify` uses constant-time comparison and rejects timestamps older than 5 minutes by default. Pass `toleranceSeconds: 0` to disable. Always pass the **raw, unparsed body** — re-serializing parsed JSON breaks verification.

Errors
------

[](#errors)

```
use Confish\Exception\{
    AuthException,
    ConfishException,
    ConflictException,
    ForbiddenException,
    NetworkException,
    RateLimitException,
    ServerException,
    ValidationException,
};

try {
    $client->fetch();
} catch (RateLimitException $e) {
    sleep($e->retryAfter ?? 1);
} catch (ValidationException $e) {
    foreach ($e->errors as $field => $messages) {
        echo "$field: ".implode(', ', $messages);
    }
} catch (ConfishException $e) {
    echo "HTTP {$e->statusCode}: {$e->getMessage()}";
}
```

By default the client retries `429` (honoring `Retry-After`) and `5xx` responses up to twice. Tune with `maxRetries` on the constructor.

Options
-------

[](#options)

```
use GuzzleHttp\Client as GuzzleClient;

$client = new Confish(
    envId: 'a1b2c3d4e5f6',
    apiKey: 'confish_sk_...',
    baseUrl: Confish::DEFAULT_BASE_URL, // override for self-hosted
    httpClient: new GuzzleClient(['timeout' => 10.0]), // inject your own
    userAgent: 'my-app/1.0',
    maxRetries: 2,
    maxRetryDelay: 30.0,
);
```

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance92

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity32

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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

40d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/bb0ca10d9739b1ad4e168b78d0bff5a22962a41c23c406f1a7daad8027188686?d=identicon)[Bravilogy](/maintainers/Bravilogy)

---

Top Contributors

[![Bravilogy](https://avatars.githubusercontent.com/u/10072816?v=4)](https://github.com/Bravilogy "Bravilogy (2 commits)")

---

Tags

sdkconfigurationconfigfeature-flagsconfish

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

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

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

###  Alternatives

[aws/aws-sdk-php

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.3k532.1M2.5k](/packages/aws-aws-sdk-php)[caseyamcl/configula

A simple, but versatile, PHP config loader

42148.7k6](/packages/caseyamcl-configula)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

232.5k](/packages/eslazarev-wildberries-sdk)[michaels/data-manager

Simple data manager for nested data, dot notation array access, extendability, and container interoperability.

131.9k2](/packages/michaels-data-manager)

PHPackages © 2026

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