postyou/adobe-sign-bundle
A Symfony bundle that integrates Adobe Sign (formerly EchoSign) into your application, providing service configuration and a client wrapper for sending agreements, managing signing workflows, and handling Adobe Sign API calls.
This is a Symfony bundle (postyou/adobe-sign-bundle) targeting the Adobe Acrobat Sign REST API — it is not a Laravel package and has no service provider or facade. The framework-agnostic service classes work fine inside Laravel, but you wire them yourself.
Minimal steps:
composer require postyou/adobe-sign-bundle — and pin the exact patch version ("0.0.15"), since this is still a 0.0.x release line.Resources/config/services.yaml first. That file tells you the real service IDs, constructor arguments, and which classes are public — more useful than the README for figuring out what you can actually inject or resolve.AdobeSignServiceProvider that reads config/services.php (client_id, client_secret, redirect_uri, scopes, api_host) and binds the bundle's client class as a singleton, passing the config explicitly rather than relying on Symfony's container.POST /agreements with a SEND state → poll or wait for the webhook. That single flow exercises auth, file upload, error handling, and status polling, which is 90% of what you'll build.New in v0.0.15: AdobeErrorResponseException. This is the exception you should design your error handling around from day one — it represents a well-formed error returned by Adobe (4xx/5xx with an error body), as opposed to transport/network failures. Start by writing your first try/catch against it before you write the happy path; retrofitting typed error handling into a growing integration is painful.
Wrap, don't inject, the client everywhere. Bind the bundle client to a Laravel singleton and expose it through your own thin AdobeSign class. Every call site then depends on your class, so a breaking change in the bundle's method signatures (likely at this version) touches one file.
Proactive token refresh. Adobe access tokens are short-lived (~1 hour). Store expires_at, and refresh when now() > expires_at - 5 minutes. Adobe rotates the refresh token — if you don't persist the new one returned by the refresh call, you lose the account until someone re-authorizes manually. Do this inside a cached job/lock so concurrent requests don't race the same rotating refresh token.
Queue everything that sends. Agreement creation, sending, and reminder updates should be queued jobs with $tries = 3 and exponential backoff. Adobe calls can take several seconds and you do not want a 30s HTTP request inside a web request handler.
Centralized error translation. Catch AdobeErrorResponseException in one place (an exception handler mapping or a wrapper around every client call) and map Adobe's code field to domain outcomes:
| Adobe code | Typical cause | Your handling |
|---|---|---|
INVALID_ACCESS_TOKEN |
expired/revoked token | refresh once, retry |
PERMISSION_DENIED |
scopes or sender not authorized | surface to user |
INVALID_FILE_TYPE / MISSING_DOCUMENT |
bad upload | 422 to user, no retry |
RATE_LIMITED / 429 |
burst | back off, honor Retry-After |
Keep the raw error payload (code, message, requestId) on the exception's context when you log or rethrow.
Webhook-first status. Don't poll for agreement status in production. Register a webhook, dispatch a job from the controller, and only fall back to polling (GET /agreements/{id}) for reconciliation and for records that never got a webhook.
Test with fake HTTP, not the sandbox. Use Laravel's Http::fake() or a Guzzle MockHandler to replay recorded Adobe responses — including a recorded error body so you can assert your v0.0.15 exception path actually fires. Sandbox integration tests are slow, rate-limited, and will flake in CI.
0.0.x means no BC guarantees. Adding a new exception class is a green flag, but the comparison URL shows they're incrementing the patch digit for everything — feature additions included. Pin exact versions and read the diff between pins; assume any release can change a method signature.AdobeErrorResponseException almost certainly fires only when Adobe returns a parseable error body. Network timeouts, DNS failures, and HTML error pages from a proxy will surface as the underlying HTTP client exception instead. Catch both, and don't catch (\Throwable) around the client or you'll swallow programming errors.na1/eu1/jp1 are different base URLs. A wrong shard returns a confusing auth or 404 error that looks like a credentials problem. Make the host a config value, not a hardcoded constant.fileInfos vs. transientDocumentId vs. libraryDocumentId are mutually exclusive input shapes on agreement creation. This is the single most common source of 400 responses when integrating; validate you're sending exactly one.requestId header and correlate — that's what Adobe support will ask for.vendor/. When the next 0.0.x bump rearranges the service definition, your override is the one place to fix.curl using the same token. If curl works and the bundle fails, the problem is serialization/headers (very often a multipart/form-data vs. application/json mix-up on part uploads) rather than credentials.How can I help you explore Laravel packages today?