Filecheck server SDK for PHP 8.1+ — jobs, uploads, webhooks, and the server-side job verification
your fulfillment path must run before trusting a browser-submitted job id. Mirrors
filecheck-node
method-for-method.
HTTP via any PSR-18 client (auto-discovered with php-http/discovery, or injected), with a
bundled cURL fallback so the package works with zero extra dependencies.
composer require filecheck/filecheck-php$fc = new \Filecheck\FilecheckClient(getenv('FILECHECK_SECRET_KEY'));
$result = $fc->jobs->verify($_POST['filecheck_job_id'], ['workflow_id' => 'wf_…']);
if (!$result->ok) {
http_response_code(422);
exit("Files not accepted ({$result->reason})");
}
// fulfil the order; $result->state is 'ready' or 'partial' (warnings accepted by policy)Laravel
// AppServiceProvider
$this->app->singleton(FilecheckClient::class,
fn () => new FilecheckClient(config('services.filecheck.secret')));
// CheckoutController
public function store(Request $request, FilecheckClient $fc)
{
$result = $fc->jobs->verify($request->input('filecheck_job_id'), ['workflow_id' => 'wf_…']);
abort_unless($result->ok, 422, "Files not accepted ({$result->reason})");
// …
}verify() is the encoded docs checklist: the job must be terminal
(status ∈ done|skipped|error), proceedable (the browser Element's canProceed equivalent —
ready/partial after applying the job's onFail policy), and — with workflow_id — must have
run the expected workflow. Options: policy (override the resolved onFail), strict (fail
closed on status: 'error' jobs).
// Two-leg upload (presign + S3) in one call → fileRef
$upload = $fc->uploads->create('/tmp/artwork.pdf', ['mime_type' => 'application/pdf']);
// Validate against PDF/X profiles — waits for the result by default
$res = $fc->jobs->validate(['sources' => [['fileRef' => $upload->fileRef, 'profile' => ['1b']]]]);
echo $res->job->outcome;
// Preflight + autofix — async by default; 'wait' => true to block until terminal
$fixed = $fc->jobs->fix([
'sources' => [['fileRef' => $upload->fileRef, 'profileId' => 'default']],
'wait' => true,
]);The raw API inconsistently flips sync/async with sync: true (create/preflight/previews/fix —
async default) and async: true (validate/optimize — sync default). The SDK normalizes all six
behind 'wait' / 'wait_timeout' (seconds, default 120) with defaults matching each endpoint.
When the server's ~27 s wait budget expires (HTTP 202), the SDK keeps polling GET /jobs/{id}
client-side. Every submit returns a JobResult { job, pending }.
Blocking waits tie up the PHP worker — mind FPM time limits; prefer webhooks for long jobs.
| Method | Endpoint |
|---|---|
$fc->jobs->create($params) |
POST /jobs |
$fc->jobs->preflight/previews/fix/validate/optimize($params) |
POST /jobs/… sugar endpoints |
$fc->jobs->retrieve($id) |
GET /jobs/{id} → Data\Job |
$fc->jobs->retrieveRuns($id) |
GET /jobs/{id}?expand=runs → Data\JobRuns |
$fc->jobs->list(['limit', 'next_key']) / ->iterate() |
GET /jobs (+ auto-pagination) |
$fc->jobs->delete($id) |
DELETE /jobs/{id} |
$fc->jobs->waitUntilTerminal($id, $opts) |
polling helper |
$fc->jobs->verify($id, $opts) |
fulfillment gate → Data\VerifyResult |
$fc->uploads->create($pathOrBytesOrStream, $opts) |
POST /uploads + S3 leg → Data\Upload |
$fc->orders->create($orderId, $params) |
POST /orders/{id} |
$fc->workflows/connectors/rules/profiles/optimizePresets->all()/->get($id) |
read-only library |
Webhooks::constructEvent($rawBody, $sig, $secret, $opts) |
webhook parsing/verification |
Client options: new FilecheckClient('sk_…', ['base_url', 'timeout' /* seconds */, 'max_retries', 'http_client' /* PSR-18 */, 'transport']). Secret keys are sk_…; passing a
publishable pk_… key throws immediately. Keys are never echoed in full — masked to sk_…abc4.
Responses are readonly value objects (Data\Job, Data\Task, Data\Step, Data\JobRuns,
Data\VerifyResult, …) that keep the raw payload in ->raw for forward compatibility.
Signing status: Filecheck's per-job
job.completedwebhooks are delivered unsigned today; the signature scheme is not finalized.constructEventis structured so verification becomes the default without breaking changes once it ships. Until then,['verify' => false]is the explicit, temporary escape hatch.
$event = \Filecheck\Webhook\Webhooks::constructEvent(
file_get_contents('php://input'), // ALWAYS the raw body
$_SERVER['HTTP_X_FILECHECK_SIGNATURE'] ?? null,
null,
['verify' => false], // TEMPORARY until Filecheck ships webhook signing
);
if ($event->type === \Filecheck\Data\WebhookEvent::TYPE_JOB_COMPLETED) {
// $event->payload: id, status, outcome, taskIds, tasks[], finalized, …
}In Laravel, exempt the route from CSRF and use $request->getContent() for the raw body.
Verification uses hash_equals() (constant-time compare).
Typed exceptions in Filecheck\Exception: AuthenticationException (401/403 — including API
Gateway's bare {"message":"Forbidden"} shape), InvalidRequestException (400),
NotFoundException (404), RateLimitException (429), ApiException (5xx / non-JSON bodies),
ConnectionException (network/timeout), WebhookSignatureException. The API has no
machine-readable error codes — branch on exception class, not message text.
Retries: idempotent GETs only (default 2×, full-jitter backoff, Retry-After honored) on
429/502/503/504 and connection failures. POSTs are never auto-retried — the API has no
idempotency keys, and a duplicated POST /jobs creates and bills a second job.
composer install
composer test # PHPUnit
composer phpstan # level 8Test fixtures in fixtures/ are a synced copy of the shared fixtures in the
filecheck-integrations monorepo, so this SDK and filecheck assert against identical
payloads.
MIT