Loading...
Loading...
The SignBolt PHP SDK provides a PSR-compliant, composer-installable client for the SignBolt REST API. Works in vanilla PHP, Laravel, Symfony, and WordPress.
PSR-18 HTTP client compatible (use any HTTP library under the hood)
PSR-3 logger compatible for observability
Native support in Laravel, Symfony, and WordPress
Webhook signature verification helper
Automatic retries with exponential backoff
PHP 8.1 or newer required. The SDK uses Guzzle under the hood by default but is PSR-18 compatible so you can swap in any compliant HTTP client. PHP 7.x is not supported β upgrade to a current PHP release.
composer require signbolt/signbolt-php# Requires PHP 8.1+ with ext-curl and ext-jsonGenerate an API key from your SignBolt dashboard. Store in environment variables (`.env` for Laravel, server environment for bare PHP). Never commit API keys to version control.
<?php
require 'vendor/autoload.php';
use SignBoltSignBolt;
// Load from environment
$client = new SignBolt([
'api_key' => getenv('SIGNBOLT_API_KEY'),
]);
// With additional options
$client = new SignBolt([
'api_key' => getenv('SIGNBOLT_API_KEY'),
'base_url' => 'https://signbolt.au/api/v1',
'timeout' => 30.0,
'max_retries' => 3,
]);
// In Laravel (config/services.php)
/*
'signbolt' => [
'api_key' => env('SIGNBOLT_API_KEY'),
'webhook_secret' => env('SIGNBOLT_WEBHOOK_SECRET'),
],
*/
// Then in a service provider or controller
$client = new SignBolt([
'api_key' => config('services.signbolt.api_key'),
]);Send a document to a recipient for signature.
<?php
use SignBoltSignBolt;
use SignBoltModelsRecipient;
use SignBoltModelsSignatureField;
$client = new SignBolt(['api_key' => getenv('SIGNBOLT_API_KEY')]);
function sendContract(SignBolt $client): array {
// Read PDF from disk
$pdfContent = file_get_contents(__DIR__ . '/contracts/service-agreement.pdf');
if ($pdfContent === false) {
throw new RuntimeException('Could not read PDF file');
}
$response = $client->documents->send([
'file' => $pdfContent,
'filename' => 'service-agreement.pdf',
'recipients' => [
[
'email' => 'client@example.com',
'name' => 'Jane Client',
'role' => 'signer',
],
],
'subject' => 'Please sign: Service Agreement',
'message' => 'Hi Jane, please review and sign the attached service agreement.',
'fields' => [
[
'type' => 'signature',
'page' => 1,
'x' => 100,
'y' => 500,
'width' => 200,
'height' => 60,
'required' => true,
'recipient_email' => 'client@example.com',
],
[
'type' => 'date',
'page' => 1,
'x' => 350,
'y' => 510,
'width' => 120,
'height' => 40,
'required' => true,
'recipient_email' => 'client@example.com',
],
],
'webhook_url' => 'https://api.yoursite.com/webhooks/signbolt',
'expires_in_days' => 14,
]);
echo "Document ID: " . $response['document_id'] . PHP_EOL;
echo "Signing URL: " . $response['signing_urls'][0]['url'] . PHP_EOL;
echo "Status: " . $response['status'] . PHP_EOL;
return $response;
}
sendContract($client);
// Laravel controller equivalent
/*
namespace AppHttpControllers;
use IlluminateHttpRequest;
use SignBoltSignBolt;
class ContractController extends Controller
{
public function sendContract(Request $request, SignBolt $client)
{
$pdf = $request->file('contract');
$response = $client->documents->send([
'file' => $pdf->getContent(),
'filename' => $pdf->getClientOriginalName(),
'recipients' => [
['email' => $request->input('email'), 'name' => $request->input('name')],
],
'fields' => [
['type' => 'signature', 'page' => 1, 'x' => 100, 'y' => 500, 'width' => 200, 'height' => 60],
],
]);
return response()->json($response);
}
}
*/Verify webhook signatures in Laravel, Symfony, or plain PHP.
<?php
// Plain PHP endpoint
use SignBoltSignBolt;
$client = new SignBolt(['api_key' => getenv('SIGNBOLT_API_KEY')]);
$webhookSecret = getenv('SIGNBOLT_WEBHOOK_SECRET');
$signature = $_SERVER['HTTP_X_SIGNBOLT_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
if (!$client->webhooks->verify($body, $signature, $webhookSecret)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
$event = json_decode($body, true);
switch ($event['type']) {
case 'document.signed':
$documentId = $event['data']['documentId'];
$signer = $event['data']['signerEmail'];
error_log("Document $documentId signed by $signer");
// Update database, send notification
break;
case 'document.completed':
$documentId = $event['data']['documentId'];
error_log("Document $documentId fully signed");
// Download final PDF
break;
case 'document.declined':
error_log("Signer declined: " . $event['data']['signerEmail']);
break;
case 'document.expired':
error_log("Document {$event['data']['documentId']} expired");
break;
}
http_response_code(200);
echo json_encode(['received' => true]);
// Laravel route/controller
/*
// routes/web.php
Route::post('/webhooks/signbolt', [WebhookController::class, 'signbolt']);
// app/Http/Controllers/WebhookController.php
namespace AppHttpControllers;
use IlluminateHttpRequest;
use SignBoltSignBolt;
class WebhookController extends Controller
{
public function signbolt(Request $request, SignBolt $client)
{
$signature = $request->header('X-SignBolt-Signature');
$body = $request->getContent();
if (!$client->webhooks->verify($body, $signature, config('services.signbolt.webhook_secret'))) {
abort(401, 'Invalid signature');
}
$event = json_decode($body, true);
if ($event['type'] === 'document.completed') {
// Dispatch a job to process the completed document
dispatch(new AppJobsProcessSignedDocument($event['data']['documentId']));
}
return response()->json(['received' => true]);
}
}
*/Handle errors with the SignBoltException class.
<?php
use SignBoltSignBolt;
use SignBoltExceptionsSignBoltException;
$client = new SignBolt(['api_key' => getenv('SIGNBOLT_API_KEY')]);
function sendWithErrorHandling(SignBolt $client, string $pdfContent): ?array {
try {
$response = $client->documents->send([
'file' => $pdfContent,
'filename' => 'contract.pdf',
'recipients' => [['email' => 'client@example.com', 'name' => 'Client']],
'fields' => [['type' => 'signature', 'page' => 1, 'x' => 100, 'y' => 500, 'width' => 200, 'height' => 60]],
]);
return $response;
} catch (SignBoltException $e) {
switch ($e->getStatusCode()) {
case 400:
error_log('Bad request: ' . $e->getMessage());
// $e->getDetails() has field-level errors
break;
case 401:
error_log('Invalid API key β check credentials');
break;
case 403:
error_log('API access requires Business plan β upgrade at /pricing');
break;
case 413:
error_log('PDF exceeds 25MB β compress or split');
break;
case 429:
error_log('Rate limited β SDK will retry automatically');
break;
case 500:
case 502:
case 503:
error_log('SignBolt server error β transient, safe to retry');
break;
default:
error_log('SignBolt error: ' . $e->getMessage());
}
throw $e;
} catch (Exception $e) {
// Non-SignBolt error (network, parsing)
error_log('Unexpected error: ' . $e->getMessage());
throw $e;
}
}List signed documents with pagination.
<?php
use SignBoltSignBolt;
$client = new SignBolt(['api_key' => getenv('SIGNBOLT_API_KEY')]);
// Recent completed documents
function getRecentSigned(SignBolt $client): array {
$page1 = $client->documents->list([
'status' => 'completed',
'limit' => 50,
]);
echo "Got " . count($page1['data']) . " documents
";
$allDocs = $page1['data'];
// Paginate manually
while (!empty($page1['next_cursor'])) {
$page1 = $client->documents->list([
'status' => 'completed',
'limit' => 50,
'cursor' => $page1['next_cursor'],
]);
$allDocs = array_merge($allDocs, $page1['data']);
}
return $allDocs;
}
// Filter by date range
$docs = $client->documents->list([
'created_after' => '2026-03-01T00:00:00Z',
'created_before' => '2026-04-01T00:00:00Z',
'limit' => 100,
]);
foreach ($docs['data'] as $doc) {
echo "{$doc['filename']} β signed by {$doc['signer_email']} on {$doc['signed_at']}
";
}The SignBolt API enforces rate limits to ensure fair use across all customers. The SDK respects these limits with automatic retries.
Standard rate limit: 60 requests per minute per API key.
Burst allowance: up to 120 requests in a 60-second window.
Hourly limit: 3,000 requests per API key per hour.
Send endpoint: limited to 10 documents per minute.
SDK retries rate-limited requests automatically using exponential backoff.
Yes. Install via composer and register the client in a service provider. See the Laravel example in the send section. For Laravel 10+, dependency injection via service container works out of the box.
Yes. Install via composer (if you manage dependencies that way) or include the autoloader manually in your plugin. Use the SDK in custom post types, REST API endpoints, or admin hooks. Store the API key in wp-config.php or an options setting.
No. The SDK requires PHP 8.1 or newer. PHP 7.x reached end-of-life and is not supported. The modern SDK uses features (named arguments, enums, readonly properties) that require 8.1+. For PHP 7.4 projects, consider upgrading PHP before adopting.
Yes. Register the `SignBolt` class as a service in `services.yaml`. Bind the API key via a parameter bag or environment variable. Use dependency injection in controllers and services. The SDK is PSR-18 compatible so you can wire it into Symfony's HTTP client infrastructure if preferred.
The SDK retries transient failures automatically. For application-level retries in background jobs (Laravel queues, Symfony Messenger), let the job retry the entire operation β SDK retries are short-timeframe, job retries handle longer outages. Configure maximum retry counts in your queue system to avoid infinite loops.
Yes. API keys prefixed `sb_test_` are sandbox keys. Requests behave identically to production but don't send emails or bill against plan limits. Swap keys via environment variables to move between environments.
The SDK only calls the SignBolt API host (by default signbolt.au). It does not fetch user-controlled URLs. Webhook verification uses constant-time comparison for HMAC signatures. For your own endpoints receiving SignBolt webhooks, validate signatures before processing.
API access is included in the Business plan ($24/month). Upgrade to get your API key.