PHPackages                             signingstudio/signingstudio-php - 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. signingstudio/signingstudio-php

ActiveLibrary

signingstudio/signingstudio-php
===============================

Official PHP SDK for the Signing Studio e-signature API.

00

Since Jul 22Compare

[ Source](https://github.com/alyasdds/signingstudio-php)[ Packagist](https://packagist.org/packages/signingstudio/signingstudio-php)[ RSS](/packages/signingstudio-signingstudio-php/feed)WikiDiscussions Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Signing Studio PHP SDK
======================

[](#signing-studio-php-sdk)

[![CI](https://github.com/signingstudio/signingstudio-php/actions/workflows/ci.yml/badge.svg)](https://github.com/signingstudio/signingstudio-php/actions/workflows/ci.yml)[![Latest Stable Version](https://camo.githubusercontent.com/460139d5a31cbc6c8bcf1aa51591fb25a8a02a54bcb68972ecfca37643e77eb4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7369676e696e6773747564696f2f7369676e696e6773747564696f2d7068702e737667)](https://packagist.org/packages/signingstudio/signingstudio-php)[![License](https://camo.githubusercontent.com/be2c13bd201312b9ca1469ca902f179892e17dd58b57efae5b3beca3918c9a00/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7369676e696e6773747564696f2f7369676e696e6773747564696f2d7068702e737667)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/bd9077fe830f984a7dccb2fcadaeae6700e5da5d4278147ff3cd59b20eee8adb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f7369676e696e6773747564696f2f7369676e696e6773747564696f2d7068702e737667)](composer.json)

Official PHP client for the **[Signing Studio](https://signingstudio.com)** e-signature API. Covers every public v1 endpoint — send documents from templates, poll signing progress, manage templates and their fields, and verify webhook deliveries.

Install
-------

[](#install)

```
composer require signingstudio/signingstudio-php
```

Requires **PHP 8.1+** and the `json` + `hash` extensions (both ship by default).

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

[](#quick-start)

```
use SigningStudio\Client;

$client = new Client('sk_live_your_api_key');

$doc = $client->documents()->send([
    'template_id' => '11111111-2222-3333-4444-555555555555',
    'title'       => 'MSA — Acme',
    'recipients'  => [
        ['name' => 'Alex Doe', 'email' => 'alex@acme.com'],
    ],
]);

echo "Sent {$doc['id']} ({$doc['status']})\n";
```

Get your API key from **Signing Studio → Settings → API Keys**. The plaintext key is shown once at creation — store it in a secrets manager, never in source control.

Configuration
-------------

[](#configuration)

The one-arg constructor is enough for most callers. Pass a `Config` when you need to override the base URL (staging), retry ceiling, or timeouts:

```
use SigningStudio\Client;
use SigningStudio\Config;

$client = new Client(new Config(
    apiKey: 'sk_live_...',
    baseUrl: 'https://api.signingstudio.com',   // default
    maxRetries: 3,                              // 429 + 5xx + network errors
    connectTimeout: 10,                         // seconds
    requestTimeout: 120,                        // seconds
    userAgent: 'my-app/1.4',
));
```

The retry policy is intentionally conservative:

- **429** with `Retry-After ≤ 60s` → sleep and retry, up to `maxRetries`.
- **429** with `Retry-After > 60s` (typically a daily quota) → surface `RateLimitException` immediately.
- **5xx** → exponential backoff (500ms · 1s · 2s · 4s · …) with jitter, up to `maxRetries`.
- **Network errors** (connect fail, read timeout) → same backoff as 5xx.

Documents
---------

[](#documents)

```
// List
$list = $client->documents()->list([
    'status' => 'sent',
    'view'   => 'active',   // 'archived' | 'deleted' | 'all'
    'limit'  => 50,
    'offset' => 0,
]);

// Send from a template
$doc = $client->documents()->send([
    'template_id'  => $templateId,
    'title'        => 'MSA — Acme',
    'subject'      => 'Please sign',                // optional email subject
    'message'      => 'Signing at your convenience',
    'expires_at'   => '2026-08-01T00:00:00Z',       // optional
    'recipients'   => [
        ['name' => 'Alex', 'email' => 'alex@acme.com', 'signing_order' => 0],
        ['name' => 'Bo',   'email' => 'bo@acme.com',   'signing_order' => 1],
    ],
    'prefill_values' => [                           // optional
        ['field_name' => 'company', 'value' => 'Acme Inc.'],
    ],
]);

// Read
$doc = $client->documents()->get($id);
$progress = $client->documents()->progress($id);  // cheap; ideal for polling
$activity = $client->documents()->activity($id);  // full audit log

// Actions
$client->documents()->cancel($id);
$client->documents()->archive($id);
$client->documents()->unarchive($id);
$client->documents()->restore($id);
$client->documents()->delete($id);                // soft
$client->documents()->delete($id, hard: true);    // hard purge (must be cancelled/completed/declined/expired)

// Reminder — mints a fresh signing URL for a specific recipient.
$out = $client->documents()->remind($documentId, $recipientId);
$fresh = $out['signing_url'];

// Download
$url = $client->documents()->downloadUrl($id)['url']; // presigned; ~1h TTL
$bytes = $client->documents()->downloadPdf($id);       // raw bytes
```

### Send-payload rules the server enforces

[](#send-payload-rules-the-server-enforces)

- `template_id` is required; ad-hoc PDF sends without a template are not exposed on v1.
- `recipients` must have at least one entry. Sequential signing runs in `signing_order`.
- `prefill_values[]` must have either `field_id` (uuid) or a non-empty `field_name` slug plus a `value`.
- Signature / initials fields cannot be prefilled — 422 if you try.
- Any template field that is both `required: true` and `readonly: true` MUST be prefilled — 422 with a list of missing labels.
- Sends count against the account's monthly `documents_per_month` quota — 429 when tripped.

Templates
---------

[](#templates)

```
// List active templates
$templates = $client->templates()->list();

// Create from a PDF (path, resource, or raw bytes all accepted)
$template = $client->templates()->create(
    __DIR__ . '/msa.pdf',
    [
        'name'             => 'MSA v2',
        'signer_count'     => 1,
        'delivery_methods' => ['email'],
        'signers'          => [
            ['role' => 'Customer', 'delivery' => ['email']],
        ],
    ],
);

// Get / partial update / soft-delete
$template = $client->templates()->get($id);
$client->templates()->update($id, ['name' => 'MSA v3']);
$client->templates()->delete($id);

// PDF versioning — replace and history
$client->templates()->replacePdf($id, __DIR__ . '/msa-updated.pdf');
$versions = $client->templates()->history($id);
$oldPdfUrl = $client->templates()->historyPdfUrl($id, $versions[0]['id'])['url'];

// Fields — full replace
$client->templates()->setFields($id, [
    [
        'field_type'   => 'signature',
        'page'         => 1,
        'x' => 60, 'y' => 82, 'width' => 30, 'height' => 6,
        'signer_index' => 0,
        'required'     => true,
    ],
    [
        'field_type'   => 'text',
        'page'         => 1,
        'x' => 10, 'y' => 20, 'width' => 30, 'height' => 4,
        'signer_index' => 0,
        'name'         => 'company',
        'label'        => 'Company name',
        'required'     => true,
    ],
]);

// Duplicate + archive
$copy = $client->templates()->duplicate($id);
$client->templates()->archive($id);
```

### PDF upload constraints

[](#pdf-upload-constraints)

- **Max 50 MB** per file.
- **`application/pdf` only** — other content types 400.
- The multipart form field name must be `file` (the SDK sets this for you).
- Passing a string that looks like a filesystem path opens it with `fopen`; anything else is treated as raw PDF bytes.

Webhooks
--------

[](#webhooks)

Verify incoming deliveries against your webhook's secret before trusting the payload:

```
use SigningStudio\Client;

$verifier = Client::webhookVerifier('whs_your_secret');

$raw = file_get_contents('php://input') ?: '';
$sig = $_SERVER['HTTP_X_DDS_SIGNATURE'] ?? '';

if (!$verifier->isValid($raw, $sig)) {
    http_response_code(401);
    exit;
}

$payload = json_decode($raw, true);
// $payload['event'] is one of:
//   document.sent | document.viewed | document.signed | document.declined | document.completed
```

### Payload shape

[](#payload-shape)

```
{
  "event": "document.signed",
  "data": {
    "document": {
      "id": "uuid",
      "title": "string",
      "status": "sent|viewed|completed|declined|cancelled|expired",
      "template_id": "uuid|null",
      "sent_at": "…|null",
      "completed_at": "…|null"
    },
    "download_url": "https://…|null",
    "download_expires_in_seconds": 3600,
    "recipients": [ /* …with viewed_at/signed_at/declined_at */ ],
    // Event-specific:
    //   document.viewed / .signed → { "recipient": {…}, "ip": "…"|null }
    //   document.declined         → { "recipient": {…}, "reason": "…"|null, "ip": "…"|null }
    //   document.completed        → { "completed_by": {…} }
  },
  "timestamp": "2026-07-20T14:32:11Z"
}
```

**Always sign the RAW body**, not a parsed-and-re-encoded body. Any whitespace shift breaks the HMAC.

Errors
------

[](#errors)

```
use SigningStudio\Exception\{
    SigningStudioException,
    ApiException,
    AuthenticationException,
    NotFoundException,
    ValidationException,
    RateLimitException,
};

try {
    $client->documents()->send([...]);
} catch (ValidationException $e) {
    foreach ($e->getErrors() as $field => $messages) { /* … */ }
} catch (RateLimitException $e) {
    // $e->getWindow() === 'minute' | 'day' | null (quota)
    // $e->getRetryAfter() === int|null
} catch (AuthenticationException $e) {
    // Refresh the API key.
} catch (NotFoundException $e) {
    // The resource does not exist (or belongs to another tenant).
} catch (ApiException $e) {
    // Everything else that came back with a non-2xx HTTP status.
    error_log("request={$e->getRequestId()} status={$e->getStatusCode()}");
}
```

All SDK-thrown exceptions inherit from `SigningStudioException`.

Rate limits
-----------

[](#rate-limits)

Every response carries:

- `X-RateLimit-Limit-Minute`, `X-RateLimit-Remaining-Minute`
- `X-RateLimit-Limit-Day`, `X-RateLimit-Remaining-Day`

`Response::getRateLimitRemainingMinute()` / `getRateLimitRemainingDay()` expose the remaining counts:

```
$response = $client->documents()->list([...]);
// Response object isn't returned by the resource methods directly today —
// they return the parsed data array. The rate-limit headers are still
// received by the transport; if you need them, drop down to the HttpClient.
```

Platform defaults are **120 requests/minute** and **20,000 requests/day** per API key. Per-account and per-key overrides can raise or lower these.

Testing
-------

[](#testing)

```
composer install
composer test           # phpunit
composer analyse        # phpstan level 7
composer cs-check       # php-cs-fixer dry-run
```

CI runs against PHP 8.1, 8.2, 8.3, 8.4 on every push/PR.

Versioning
----------

[](#versioning)

Semantic versioning. Breaking changes bump the major; new endpoints or fields bump the minor; bug fixes bump the patch. `CHANGELOG.md` records every release.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

9

—

LowBetter than 0% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/7940e8ab189e2fd3c1dfb1d12b4d9140f0689e69ae1d626b6ff6cd0a071d85ae?d=identicon)[alyasdds](/maintainers/alyasdds)

### Embed Badge

![Health badge](/badges/signingstudio-signingstudio-php/health.svg)

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

PHPackages © 2026

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