Start here
Installation
From composer require to a verified connection in about ten minutes. This page installs the bundle, gives the SDK an HTTP client, puts your key in the environment, imports the routes, and checks the credentials against Acrobat Sign. Optional pieces (Doctrine, Messenger, Security) are at the end.
Requirements
| What | Version | Why |
|---|---|---|
| PHP | 8.2 or newer | The minimum the core SDK and the bundle are tested on. |
ext-sodium | any | Encrypts stored OAuth tokens. Bundled with PHP; some minimal Docker images need docker-php-ext-install sodium. |
| Symfony | 7.4 or 8.x | The bundle is an AbstractBundle and requires ^7.4 || ^8.0 of FrameworkBundle, HttpKernel, Routing, Cache, Console and friends. |
| A PSR-18 client | any | The core SDK sends requests through PSR-18. See the HTTP client. |
| Acrobat Sign account | developer or paid | A free developer account works for building and testing; agreements from it get a [DEMO USE ONLY] prefix. |
The core package, conceptlz/adobe-sign, comes in as a dependency; you don't require it
separately. The bundle and the core share one version number and are released together.
Install in six steps
-
Require the bundle
composer require conceptlz/adobe-sign-symfonyThe package type is
symfony-bundle, so Symfony Flex adds it toconfig/bundles.phpfor you. There is no recipe, so Flex doesn't create config files; the next steps do that by hand. -
Register the bundle if Flex didn't
config/bundles.php return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], // ... Conceptlz\AdobeSign\Symfony\AdobeSignBundle::class => ['all' => true], ];Register it in every environment. Disabling it in
testwould remove the services your tests need; swap the HTTP client instead (see Testing). -
Make sure a PSR-18 client exists
composer require symfony/http-client nyholm/psr7Skip this if your app already has
symfony/http-clientand a PSR-17 implementation. Details in the HTTP client. -
Put the key in the environment
Declare the variable (empty) in the committed
.envand give it a value in.env.local, which is git-ignored. See credentials. -
Create the configuration and import the routes
Two small YAML files:
config/packages/adobe_sign.yamlandconfig/routes/adobe_sign.yaml. See the config file and importing the routes. -
Verify
php bin/console adobe-sign:verifyIt calls Acrobat Sign with your credentials and prints who they belong to. See checking the connection.
The HTTP client (PSR-18)
The core SDK never creates its own HTTP stack. It accepts any PSR-18 client, which keeps it framework-free and lets your tests swap in a fake. The bundle decides which client to hand it in this order:
- The service named in
adobe_sign.http_client, if you set one. - Otherwise the framework's PSR-18 client, service
psr18.http_client. FrameworkBundle registers it whensymfony/http-clientis installed together with a PSR-17 implementation such asnyholm/psr7. It inherits yourframework.http_clientsettings (timeouts, proxies, retry options). - Otherwise
php-http/discoverylooks for any installed PSR-18 client, for example Guzzle 7.
The PSR-17 request and stream factories are always found by discovery, which is why
nyholm/psr7 (or guzzlehttp/psr7) must be installed. If nothing can be found,
the first API call throws Http\Discovery\Exception\NotFoundException; see
Troubleshooting.
framework:
http_client:
default_options:
timeout: 10 # seconds to wait for the first byte
max_duration: 120 # large signed PDFs can take a while
A typo in http_client fails quietly
The bundle references the http_client service with "null on invalid", so an id that doesn't exist falls back to discovery instead of failing the container build. If requests don't go where you expect, check the id with php bin/console debug:container <id>.
Credentials in the environment
Acrobat Sign accepts two kinds of credentials, and the bundle supports both. Pick one now; you can add the other later.
Integration Key or OAuth?
| Integration Key | OAuth | |
|---|---|---|
| Who sends | One Acrobat Sign user (usually a service account) for the whole app | Each of your users, from their own Acrobat Sign account |
| Setup | One environment variable | An API application at Adobe, a client id and secret, routes, token storage, a firewall |
| Expiry | Never, until revoked in Acrobat Sign | Access tokens expire after about an hour and refresh automatically |
| Where agreements live | In that one account | In each user's account, under their name |
| Good for | Internal tools, one company sending to its clients | SaaS products whose customers already use Acrobat Sign |
Create an Integration Key in Acrobat Sign under Account → Acrobat Sign API → API Information →
Integration Key. Give it the scopes your app needs (at least agreement_read,
agreement_write, agreement_send, user_login, and
webhook_read/webhook_write if you register webhooks).
###> conceptlz/adobe-sign-symfony ###
ADOBE_SIGN_INTEGRATION_KEY=
ADOBE_SIGN_CLIENT_ID=
ADOBE_SIGN_CLIENT_SECRET=
###< conceptlz/adobe-sign-symfony ###
ADOBE_SIGN_INTEGRATION_KEY=3AAABLblqZhB...your-key...
# Only for OAuth or webhooks:
ADOBE_SIGN_CLIENT_ID=CBJCHBCAABAA...
ADOBE_SIGN_CLIENT_SECRET=...
Declaring the variables empty in .env matters: a %env(ADOBE_SIGN_INTEGRATION_KEY)%
reference to an undefined variable throws EnvNotFoundException when the container builds the
manager, while an empty one gives you the bundle's clearer MisconfiguredException that names
the setting. On servers, prefer real environment variables or Symfony secrets:
php bin/console secrets:set ADOBE_SIGN_INTEGRATION_KEY --env=prod
Treat the key like a password
An Integration Key can send, read and cancel agreements as its owner. Never commit it, never log it, and never put it in front-end code. The SDK redacts the Authorization header from every exception it creates, so error trackers don't receive it.
The config file
The bundle's configuration root is adobe_sign. For an Integration Key, one line is enough;
every other key has a default.
adobe_sign:
integration_key: '%env(ADOBE_SIGN_INTEGRATION_KEY)%'
adobe_sign:
auth: oauth
oauth:
client_id: '%env(ADOBE_SIGN_CLIENT_ID)%'
client_secret: '%env(ADOBE_SIGN_CLIENT_SECRET)%'
token_store: doctrine # or keep "cache" with a persistent pool
redirect_after: /settings/integrations
See Configuration for every key, its default and what it changes. Two
defaults are worth knowing now: cache_pool and oauth.token_cache_pool are both
cache.app. That is fine on one machine, but on several servers or for OAuth tokens you
want a shared, persistent pool; see cache and HTTP.
Importing the routes
The bundle ships its routes as PHP files but doesn't load them by itself, so you decide which ones exist and under which prefix.
# Everything: OAuth connect routes and the webhook
adobe_sign:
resource: '@AdobeSignBundle/config/routes.php'
# Or pick one:
# adobe_sign_oauth:
# resource: '@AdobeSignBundle/config/routes/oauth.php'
# adobe_sign_webhook:
# resource: '@AdobeSignBundle/config/routes/webhook.php'
| Route name | Method | Path | File |
|---|---|---|---|
adobe_sign_oauth_redirect | GET | /adobe-sign/oauth/redirect | routes/oauth.php |
adobe_sign_oauth_callback | GET | /adobe-sign/oauth/callback | routes/oauth.php |
adobe_sign_webhook | GET, POST | /adobe-sign/webhook | routes/webhook.php |
Add prefix: /integrations to an import to move its paths; the route names stay the same,
and the default OAuth redirect URI follows the callback route automatically. Check the result with:
php bin/console debug:router | grep adobe_sign
Using only an Integration Key and no webhooks? Then you don't need to import anything: the services work without routes.
Checking the connection
$ php bin/console adobe-sign:verify
[OK] Connected to Acrobat Sign.
---------------- -------------------------
User sender@example.com
Account admin yes
Account CBJCHBCAABAA...
---------------- -------------------------
The command calls GET /users/me with the default client. The first call
of a fresh app also discovers the account's data centre through GET /baseUris
and caches it in cache_pool. On any SDK error it prints the SDK's message (for example
INVALID_ACCESS_TOKEN) and exits with status 1, so you can run it in a deploy pipeline as a
smoke test. It never prints the key.
For OAuth, check one user's connection by their token key, which is their user identifier by default:
php bin/console adobe-sign:verify --user=jane@example.com
Optional pieces
The bundle has hard dependencies only on Symfony components every full-stack app already has. Three features need more, and the bundle registers their services only when you configure them.
Doctrine DBAL, for a token table
composer require doctrine/dbal # usually via symfony/orm-pack
Needed only for oauth.token_store: doctrine. With Doctrine ORM installed as well, the table
is added to the ORM schema so doctrine:migrations:diff generates its migration; without the
ORM, run php bin/console adobe-sign:create-token-table. See Token
storage & Doctrine.
Symfony Messenger, for async webhooks
composer require symfony/messenger
Needed only for webhooks.messenger. Enabling it without Messenger installed fails the
container build with a missing messenger.default_bus service, which is deliberate: a
silently ignored setting would lose notifications. See Async with Messenger.
Symfony Security, for OAuth
composer require symfony/security-bundle
The OAuth callback stores tokens under the signed-in user, which it reads from
security.token_storage. Without SecurityBundle there is never a signed-in user, so the callback
answers 401. Your app also needs sessions enabled (framework.session), because the
single-use OAuth state lives there. See protecting the routes.
Upgrading
Before 1.0, a minor version (0.5 → 0.6) may contain breaking changes; patch versions don't. Update the bundle and the core together, read the changelog, clear the cache and restart workers:
composer update conceptlz/adobe-sign-symfony conceptlz/adobe-sign --with-dependencies
php bin/console cache:clear
php bin/console messenger:stop-workers
Common questions
Why didn't Flex create config/packages/adobe_sign.yaml?
There is no Flex recipe for the bundle yet. Flex still registers it in config/bundles.php because the package type is symfony-bundle, but the config file and the route import are yours to create. Both are a few lines; copy them from this page.
Can I use Guzzle instead of symfony/http-client?
Yes. Guzzle 7 is a PSR-18 client. Either let discovery find it (if psr18.http_client isn't registered), or register it as a service and set adobe_sign.http_client to that service id.
Do I have to import the routes?
Only for the features that need them: the OAuth routes for users connecting their accounts, the webhook route for Adobe's callbacks. Sending agreements with an Integration Key works without any route.
Can I run adobe-sign:verify in CI?
Yes, as a post-deploy smoke test with real credentials: it exits 1 on failure. Don't run it in the test suite, where the HTTP client should be a mock; it would then only test your mock.
Composer says ext-sodium is missing. Do I need it for an Integration Key?
The requirement is unconditional, because the token encryptor is part of the bundle. Sodium ships with PHP; enable it in php.ini or install it in your Docker image (docker-php-ext-install sodium).