la-souris/document-signer-validsign
ValidSign driver for the la-souris document-signer package, letting Laravel applications send documents for e-signature through ValidSign's API. Configure credentials, submit signing requests, and check signing status.
This package is a thin adapter: it implements la-souris/document-signer-sdk's SignatureProvider contract against ValidSign. That framing matters — you should almost never typehint ValidSignProvider in your app code. Bind it, then depend on the SDK contract everywhere else.
Setup:
composer.json and CI matrix. This is a hard floor, not a soft one.composer require la-souris/valid-sign (plus la-souris/document-signer-sdk:^1.0, which is a hard dependency).ValidSignConfig — base URL (sandbox vs. production), API credentials, and any account/client identifiers your ValidSign tenant requires. Keep these in config/services.php-style env vars, never inline.$this->app->bind(SignatureProvider::class, fn ($app) =>
new ValidSignProvider($app->make(ValidSignClient::class))
);
Where to look first:
ValidSignProvider — the contract surface. This is your API.ValidSignConfig — every knob the package exposes.ValidSignClient — the HTTP layer; the place to look when you need to debug a raw request/response.ValidSignPlaceholderReplacer — only relevant if you use anchor-based templates.First use case: send a document whose PDF/Word template contains anchor placeholders (e.g. {{signer_1_signature}}), let ValidSignPlaceholderReplacer swap them for real signer fields, dispatch via the SDK, persist the returned envelope/document identifiers, then later call downloadSignedDocument() and downloadAudit() to archive the signed PDF plus the Evidence Summary Report. That end-to-end slice exercises every piece of the package and is the right thing to build first.
Contract-first injection. Typehint SignatureProvider in your jobs, controllers, and services. When ValidSign has an outage or you add a second provider, the swap is a container binding, not a grep-and-replace.
Anchor-based templating. ValidSignPlaceholderReplacer exists so that document design stays in Word/PDF authoring and code stays out of it. The workflow: design templates with stable anchor tokens → store the token↔signer mapping in your own DB → pass that mapping to the replacer at send time. This keeps template revisions from requiring code deploys.
Persist the ValidSign identifiers, not just yours. downloadSignedDocument() takes the caller's Document::$id verbatim — it does not translate from your Eloquent key. So your schema needs a dedicated column for the ValidSign document id, populated at send time. Treat your internal id and the provider id as two distinct concepts from day one; retrofitting this after the fact means backfilling from logs.
Retryable vs. terminal failures. A 404 during download is ambiguous by nature: it can mean "envelope not finalized yet" or "this document genuinely doesn't exist." The package maps the former to the SDK's SignedDocumentUnavailableException, which is your signal to retry. The idiomatic pattern is a queued job with backoff:
try {
$pdf = $this->signer->downloadSignedDocument($document);
} catch (SignedDocumentUnavailableException $e) {
$this->release(60); // let the envelope finish
}
Bound the attempts anyway — see Gotchas.
Webhook handling. ValidSign callbacks are mapped to the SDK's EventType enum. Two hard requirements: make the handler idempotent (key on provider document id + event type, not on your local row id), and store the raw payload alongside the parsed event. Webhooks are the one place where you will need the original JSON months later and won't have it.
Compliance archiving. downloadAudit() returns the Evidence Summary Report as a PDF blob, not JSON. Store it next to the signed document in the same archival step — both are compliance artifacts and separating them creates a retrieval problem. Route both through one storage path keyed by envelope id.
Testing seam. Fake the SignatureProvider contract in feature tests rather than mocking the HTTP client. Reserve real HTTP fakes for the two or three tests that specifically assert the 404→exception mapping and the EventType translation.
The 404 ambiguity is the sharpest edge. The package treats a 404 on a non-finalized envelope as retryable. But a permanently missing document also 404s. Always pair the retry with a max-attempts cap and an age-based cutoff (e.g. give up after N attempts or sent_at + 48h), then escalate to a dead-letter queue. An unbounded retry loop here will silently churn queue workers forever.
Never pass your local primary key to downloadSignedDocument(). The id is forwarded verbatim to ValidSign. Passing $document->id from your Eloquent model is the single most likely first-day bug — it produces a ValidSign 404 that looks exactly like the retryable "not finalized" case, so you'll retry forever on a document id that was never valid.
Anchor matching is exact and fails quiet. Whitespace inside braces, case differences, or a token that survives PDF flattening will leave a placeholder unreplaced rather than erroring. After calling ValidSignPlaceholderReplacer, assert that no anchors remain in the outgoing payload, and log the token set at send time. A missing signature field discovered post-signature is a re-send and an apology.
ValidSignConfig is the extension point — use it before forking. Timeouts, base URLs, and environment switching should all go through config. If you find yourself reaching into ValidSignClient from application code to override a header, that's a signal to file an upstream issue or wrap it in your own decorator implementing SignatureProvider, not to patch the package in vendor/.
Pin the SDK constraint deliberately. The provider is coupled to document-signer-sdk ^1.0. A minor bump in the SDK that changes the SignatureProvider contract shape is invisible to you until runtime. Keep the ValidSignProvider binding in exactly one service provider (never re-bind per-feature) so an SDK upgrade is a one-file review.
Webhooks arrive out of order and duplicated. ValidSign is not unusual in this respect. Guard your handlers: if sent lands after completed, do not regress the state machine. A monotonic status column plus an updated_at comparison is cheap insurance.
Audit download is a binary response. Don't route it through a JSON-decoding HTTP helper or you'll get an opaque parse error instead of a PDF. Verify content-type and write bytes directly to storage. Same for the signed document itself.
PHP 8.3 is a real ceiling on adoption. If you maintain packages or services that still target 8.2, this package cannot be installed alongside them. Check your dependency graph before committing — Composer will tell you, but the error message points at the SDK constraint rather than at your runtime, which is misleading.
Debugging order when something fails: log the raw request from ValidSignClient → check ValidSignConfig env resolution (Laravel's config:cache will happily serve stale credentials) → verify the document id you're sending is the ValidSign id → then look at envelope state. In practice, stale config cache accounts for a surprising share of "credentials stopped working" reports.
How can I help you explore Laravel packages today?