Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Universign Bundle Laravel Package

mpp/universign-bundle

MPP Universign Bundle integrates Universign electronic signature services into Symfony/PHP applications, providing configuration and service wiring to initiate and manage secure e-signature workflows with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

mpp/universign-bundle is a Symfony bundle from Mon Petit Placement that wraps Universign, the French qualified-trust e-signature provider (advanced/qualified signatures, timestamping, SMS OTP signer authentication). It is almost certainly an internal extraction: one bundle, zero stars, zero dependents, and a very recent release date. Treat it as readable reference code first, dependency second.

The first thing to resolve is framework fit. It is a Symfony bundle, not a Laravel package. There is no UniversignServiceProvider, no facade, no publishable config, and the bundle's runtime contract is a Symfony DI container (DependencyInjection/Configuration.php, Resources/config/services.xml|yaml, autowired service ids). You have three realistic options:

  1. Use the underlying Universign PHP SDK / raw HTTP API directly in Laravel (Http::withBasicAuth(...) against the transaction endpoints). This is what most Laravel teams should do — the bundle is mostly config tree + a thin client, and you'd be re-implementing ~50 lines to avoid dragging in a container bridge.
  2. Run the bundle as a sidecar — a small Symfony service exposing one or two internal HTTP endpoints (POST /signature-requests) that your Laravel app calls. Good if you want the vendor's exact request shaping.
  3. Install Symfony's DI/config components into Laravel and boot the bundle manually. Possible, but you're now maintaining a bridge that only you use, for a package with no community.

If you're on Symfony after all, or you go with option 3:

composer require mpp/universign-bundle

Then register the bundle, and read DependencyInjection/Configuration.php before the README — for bundles with thin docs, the config tree is the actual documentation: it tells you exactly which env vars exist (credentials, sandbox vs production endpoint, signature level, callback/return URLs). Expect something like:

# config/packages/mpp_universign.yaml  (illustrative — confirm keys in Configuration.php)
mpp_universign:
    login: '%env(UNIVERSIGN_LOGIN)%'
    password: '%env(UNIVERSIGN_PASSWORD)%'
    sandbox: '%env(bool:UNIVERSIGN_SANDBOX)%'

Then look at, in order: the client/API service class (the thing you'll actually inject), Resources/config/ for how it's constructed, and Tests/ for the only working usage examples you'll get.

First use case to prove out: create one signature transaction for a single PDF (a quote, a mandate, a T&Cs acceptance) with one signer, get back a signing URL, redirect the user, and handle the return. That single flow exercises credentials, document upload, signer validation, and status retrieval — i.e. every part that usually breaks.

Implementation Patterns

Put an adapter in front of it, even if you like the API. Define App\Contracts\SignatureGateway (request(...): SignatureRef, status(SignatureRef): SignatureStatus) and implement it with the bundle's client. Providers like Universign get swapped, get renegotiated, or get augmented with a second provider for international signers; a two-method interface costs nothing now and saves a refactor later. It also gives you a fake for tests.

Persist, don't just redirect. The transaction id returned by the provider is your only handle on the signature. Store it immediately, before the redirect, alongside what you sent:

signature_requests: id, provider, provider_ref, status, document_hash,
                    signer_email, signer_phone, signature_level,
                    requested_at, expires_at, completed_at, raw_payload (json)

Keeping raw_payload and the document hash is cheap and is exactly what audit/compliance will ask for when someone disputes a signature six months later.

Never trust the return URL as the source of truth. Signers close tabs, mobile browsers drop the redirect, and Universign's callback can arrive before or after the user lands back on your site. Implement three layers:

  • Webhook/callback route: verify authenticity (shared secret in the URL, HTTP basic auth, or IP allowlist — check what the bundle configures), then re-fetch the transaction status from the API before flipping anything to signed. Payloads are hints, not facts.
  • Return route: renders "thanks, we're confirming" and then does the same status fetch.
  • Scheduled reconciliation (php artisan schedule): poll anything stuck in pending older than N minutes; this catches the callbacks you never received.

Queue the outbound call. The request to create a transaction is a network call to a third party and can take seconds on large PDFs. Dispatch a job from the controller, return immediately, and let the job create the transaction and email/notify the signer. Same for webhook processing: acknowledge 200 fast, then dispatch() the state transition.

Make signature level and document handling config, not literals. Simple vs advanced vs qualified changes the legal weight and the signer UX (SMS OTP, ID verification, longer flows). Put it in config/universign.php per document type, not inline at the call site. For documents: keep an immutable copy on S3/disk, pass a signed URL or base64 depending on what the client supports, and hash what you actually sent.

Testing. Keep a FakeSignatureGateway bound in testing that records requests and returns canned refs — assert your flow (persistence, redirect, webhook transitions) without network. Then have a tiny, separately-tagged suite that hits Universign's sandbox for the one happy path, so an SDK upgrade breaks loudly in CI rather than in production.

Gotchas and Tips

  • The biggest risk is maintenance, not code. Zero dependents and an internal-looking repo means no community answers, no Stack Overflow, and semver discipline you can't verify. Pin an exact version ("mpp/universign-bundle": "1.x.y"), read the diff before every bump, and consider vendoring/forking the client class outright if you only need two or three methods.
  • Bundle bootstrapping friction is front-loaded. Symfony bundle config trees are strict: an unknown key throws, and env-var placeholders are resolved by Symfony's container, not Laravel's. If you go the bridge route, expect the first day to be fighting Configuration/Extension rather than Universign.
  • Credentials differ sharply between sandbox and production. Universign's production access typically involves mutual TLS client certificates and/or IP allowlisting, while sandbox is plain credentials. Don't plan to "just flip the URL"; budget for cert handling in your HTTP client and for the fact that your local dev cannot reach production.
  • Duplicate transactions are the classic bug. Retries, double-clicks, and job re-delivery all create a second signature request for the same contract. Check your own signature_requests for an existing pending row for that document before calling out; treat the provider call as non-idempotent.
  • PDF constraints fail late and quietly. Encrypted PDFs, non-embedded fonts, oversized files, or PDFs already containing a signature field can be rejected with an unhelpful error. Validate size and PDF/A-ness on your side before submitting, and surface the provider's raw error in logs.
  • Signer data validation is stricter than Laravel's defaults. French mobile numbers for SMS OTP, names split into first/last, and sometimes a required locale. Validate with the same rules the provider enforces, or you get failures mid-flow after the signer has already committed.
  • Timeouts and retries. Set aggressive connect/read timeouts on the HTTP client (e.g. 5s/30s) and retry only status reads and document fetches — never transaction creation. Log the provider's transaction id on every exception; it's the only thing their support will ask for.
  • Debugging without the Symfony profiler. Wrap the bundle's client in a decorated service (or a Guzzle/Laravel Http middleware) that dumps request/response bodies to a dedicated log channel — and redact the password and any base64 PDF body, or you'll blow up your log storage and leak PII.
  • Extension points, if you stay on Symfony: decorate the client service rather than editing vendor code, and override service definitions in your app's services.yaml. The config tree is the intended customization surface; anything beyond it belongs in your adapter.
  • Verdict for a Laravel shop: the bundle's value is the request/response shapes and the config defaults, not its runtime. Read it, copy the call flow into a UniversignClient of your own with Http:: and a queued job, and you'll have less coupling, better logging, and no dependency on a package with one maintainer and no users.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels