Help
Troubleshooting
Each entry starts from what you see, explains why it happens, and gives the fix. Start with the quick lookup; most problems are configuration, a missing queue worker, or something Adobe can't reach.
First, three checks
- Do the credentials work?
php artisan adobe-sign:verifyIt calls Adobe with the default client and prints the user, or the exact error. Add
--user=IDfor an OAuth connection. - Is the config you think you have loaded?
php artisan config:clear php artisan tinker --execute="dump(config('adobe-sign.auth'), config('adobe-sign.webhooks.client_ids'))"Cached config ignores
.envedits. Restart Octane and queue workers after config changes. - Are the routes there?
php artisan route:list --name=adobe-signYou should see
adobe-sign.webhookand, unless disabled, the twoadobe-sign.oauth.*routes.
Quick lookup
| You see | Go to |
|---|---|
MisconfiguredException | Missing configuration |
| "Base table or view not found" / "no such table: adobe_sign_tokens" | Token table missing |
AuthenticationException with INVALID_ACCESS_TOKEN | Key rejected |
| OAuth callback answers 400 | State mismatch |
| Adobe's page says the redirect URI is invalid, or callback answers 403 | Redirect URI |
OAuthException with EXPIRED_TOKEN, INVALID_TOKEN or NO_TOKEN | Connection expired |
webhooks()->create() fails | Webhook can't be registered |
| "Acrobat Sign only delivers webhooks to HTTPS URLs" | http:// URL |
| Webhook answers 403 | Client id not allowed |
| Webhook answers 419 or redirects to login | Session middleware on the route |
| Webhook answers 200 but nothing happens | No events processed |
| The same event is handled twice | Duplicate events |
| Webhooks worked, then stopped | Webhook disabled by Adobe |
403 INVALID_API_ACCESS_POINT | Wrong data centre |
AccountCapabilityException | Account doesn't allow it |
NotFoundException right after creating an agreement | Not ready yet |
RateLimitException | Throttled |
DecryptException ("The MAC is invalid") | APP_KEY changed |
Find your problem
21 problems in 5 groups. Type an error code, an exception name or a word from the message to narrow them down, or pick one from the index.
- Webhook can't be registered
- "Acrobat Sign only delivers webhooks to HTTPS URLs"
- Webhook answers 403: client id not allowed
- Webhook answers 419 or redirects to a login page
- Webhook answers 200, but nothing happens
- The same event is handled twice
- Cache store not shared between servers
- Webhooks worked, then stopped
Setup and configuration
MisconfiguredException
What you see
Conceptlz\AdobeSign\Laravel\MisconfiguredException from the facade, an injected client, adobe-sign:verify, the OAuth routes or the webhook route.
Why it happens
A setting the feature needs is empty. The message names it:
| Message starts with | Fix |
|---|---|
| Set ADOBE_SIGN_INTEGRATION_KEY… | Add the key, or set ADOBE_SIGN_AUTH=oauth if you meant to use OAuth. |
| adobe-sign.auth is "oauth" but no user is signed in… | You're in a job, command, listener or guest request. Use AdobeSign::forUser($userId) there. |
| Set ADOBE_SIGN_CLIENT_ID and ADOBE_SIGN_CLIENT_SECRET… | Add both OAuth app credentials. |
| Set ADOBE_SIGN_WEBHOOK_CLIENT_IDS… | Add the client id(s) Adobe sends, or ADOBE_SIGN_CLIENT_ID. |
| ADOBE_SIGN_SIGNATURE_LEVEL must be ses, aes or qes… | Fix the value; any case works, other words don't. |
How to fix it
Set the value, then php artisan config:clear (or re-cache on servers) and restart long-running workers.
Token table missing
What you see
A database error such as SQLSTATE[42S02]: Base table or view not found or no such table: adobe_sign_tokens, from the OAuth callback, forUser() or isConnected().
Why it happens
The package doesn't load its migration automatically; it has to be published and run. Or you changed oauth.table after migrating.
How to fix it
php artisan vendor:publish --tag=adobe-sign-migrations
php artisan migrate
If you renamed the table, set oauth.table before running the migration, or rename the table to match.
Key rejected: INVALID_ACCESS_TOKEN
What you see
AuthenticationException mentioning INVALID_ACCESS_TOKEN; adobe-sign:verify fails.
Why it happens
The Integration Key is wrong, truncated, revoked, or belongs to a different environment. A stray quote or space in .env is common.
How to fix it
Copy the key again from Acrobat Sign (Account → Acrobat Sign API → API Information), clear the config cache and re-run adobe-sign:verify.
OAuth
Callback answers 400 (state mismatch)
What you see
After consenting at Adobe the user lands on a 400 page.
Why it happens
The callback couldn't match the state in the URL with the one stored in the session. The package pulls the state on first use, so it fails when:
- the session expired or was regenerated while the user was at Adobe;
- the session cookie wasn't sent on the redirect back (a
SameSite=strictcookie, or the callback is on a different domain or scheme than the redirect route); - the user started twice in two tabs and finished the older one;
- the callback URL was reloaded or replayed, which is the protection working.
A 400 also happens when the URL has no code, or the api_access_point isn't an Adobe host.
How to fix it
Keep Laravel's default same_site = 'lax' in config/session.php, serve the redirect route and the callback from the same host and scheme, and let the user start again from the Connect button.
Redirect URI rejected
What you see
Adobe shows an error about the redirect URI instead of the consent screen, or the callback answers 403 after the code exchange.
Why it happens
The redirect URI your app sends doesn't match one registered on the Adobe application character for character. Behind a proxy, route('adobe-sign.oauth.callback') may come out as http:// or with an internal host.
How to fix it
Register the exact URL on the application's OAuth configuration, configure trusted proxies so Laravel generates https:// URLs, or set ADOBE_SIGN_REDIRECT_URI explicitly. If you changed oauth.route_prefix, update the Adobe side too.
Connection expired: OAuthException
What you see
OAuthException with EXPIRED_TOKEN, INVALID_TOKEN, NO_REFRESH_TOKEN or NO_TOKEN when using forUser().
Why it happens
NO_TOKEN: the user never connected (or the key differs, e.g. after changing resolveUserKeyUsing()). The others: the refresh token went unused for 60 days, or the user or an admin revoked access.
How to fix it
Send the user through route('adobe-sign.oauth.redirect') again; catching OAuthException and redirecting is the usual pattern (see Refreshing tokens). Check AdobeSign::isConnected($user) before calls to avoid NO_TOKEN.
DecryptException after deploying
What you see
Illuminate\Contracts\Encryption\DecryptException ("The MAC is invalid.") when reading tokens.
Why it happens
Tokens are encrypted with APP_KEY, and it changed, or differs between servers.
How to fix it
Use the same APP_KEY on every server. When rotating, put the old key in APP_PREVIOUS_KEYS. If the old key is lost, delete the rows and ask users to reconnect.
Webhooks
Webhook can't be registered
What you see
webhooks()->create() throws, while uploading and creating agreements works.
Why it happens
Adobe verifies the URL immediately with a GET and expects a 2xx with the client-id echo. It fails when the URL isn't publicly reachable over HTTPS, when the client id Adobe sends isn't allowed (your route answers 403), when middleware intercepts the request, or when the allow-list isn't configured at all (500).
How to fix it
Check the URL from outside your network, check your log for "Rejected Acrobat Sign webhook", and confirm the route has no session or auth middleware. Locally, use a tunnel and set APP_URL to it (see local development).
"Acrobat Sign only delivers webhooks to HTTPS URLs"
What you see
InvalidArgumentException from WebhookInfo, before any request.
Why it happens
route('adobe-sign.webhook') produced an http:// URL: APP_URL is http, or a TLS-terminating proxy isn't trusted, or the code runs in a queue or console where only APP_URL is known.
How to fix it
Set APP_URL=https://… and configure trusted proxies. In tests, URL::forceScheme('https').
Webhook answers 403: client id not allowed
What you see
Adobe's deliveries or your tunnel inspector show 403; your log has "Rejected Acrobat Sign webhook".
Why it happens
The X-AdobeSign-ClientId header is missing or not in ADOBE_SIGN_WEBHOOK_CLIENT_IDS (or ADOBE_SIGN_CLIENT_ID when that list is empty). Typical after adding a second Adobe application, or when webhooks are created with an Integration Key and the allow-list holds an OAuth app's id.
How to fix it
Log the incoming header once (see the allow-list), add the id, clear the config cache. Ids are comma-separated with no quotes around each one.
Webhook answers 419 or redirects to a login page
What you see
419 "Page Expired" or a 302 to /login on Adobe's calls.
Why it happens
Session, CSRF or auth middleware is running on the route: web or auth was added to webhooks.middleware, or a global middleware applies to every route.
How to fix it
Keep webhooks.middleware free of web and auth, and exclude the webhook path from any global middleware that needs a session or user.
Webhook answers 200, but nothing happens
What you see
Adobe shows successful deliveries (or your tunnel shows 200s), but statuses don't update and no PDFs are stored.
Why it happens
Most often your listeners are queued and no worker is processing that queue. Other causes: the listener isn't registered (discovery off, or the event cache is stale), the delivery was a duplicate and correctly ignored, the listener returns early because the event isn't the type it expects, or the job failed.
How to fix it
Work through it in order:
- Is a worker running for the listener's queue?
php artisan queue:work --queue=webhooks,default, or check Horizon/Supervisor. - Is the listener registered?
php artisan event:list --event=WebhookReceivedshould list it. After adding listeners in production, runphp artisan event:cacheagain. - Did the job fail?
php artisan queue:failed. - Did the event fire? Temporarily log in the listener's first line, or in tests use
Event::fake([WebhookReceived::class]).
The same event is handled twice
What you see
Two emails, two downloads, two status changes for one signature.
Why it happens
Dedupe keys live in the cache. With a per-server store (file, array, APCu) a retry landing on another server isn't recognised, and php artisan cache:clear forgets every key. A queued job can also run twice after a timeout, and two different notifications can lead to the same action.
How to fix it
Point ADOBE_SIGN_CACHE_STORE at a store all servers share (Redis, Memcached, database, DynamoDB), and make listeners and jobs idempotent: check your own record before acting, or use ShouldBeUnique.
Cache store not shared between servers
What you see
Duplicate events as above, plus every server making its own GET /baseUris call.
Why it happens
The default cache store is local to each machine (for example file), so neither the data-centre lookup nor the webhook ids are shared.
How to fix it
ADOBE_SIGN_CACHE_STORE=redis (or another shared store). Only the SDK's keys move; the rest of your app can keep its default store.
Webhooks worked, then stopped
What you see
No more deliveries for some or all agreements.
Why it happens
Adobe disables a webhook after 72 hours of failed deliveries (an outage, an expired certificate, a broken deploy, a 403 after an allow-list change). Notifications while it is disabled are not sent later.
How to fix it
Fix the cause, then AdobeSign::webhooks()->updateState($webhookId, 'ACTIVE'), and reconcile agreements your records still show as open by asking the API for their status. notifyOnProblems() on the webhook builder makes Adobe email you before it gets that far.
API errors
403 INVALID_API_ACCESS_POINT
What you see
An ApiException with status 403 and code INVALID_API_ACCESS_POINT.
Why it happens
The request went to the wrong data centre. The SDK already handles the normal case: on this error it forgets the cached data centre, rediscovers it and retries once. If you still see it, the rediscovery itself points somewhere the credentials don't belong: a wrong bootstrap_url (for example a Gov Cloud account against the commercial host), or an OAuth token whose stored api_access_point no longer matches the account.
How to fix it
Leave ADOBE_SIGN_BOOTSTRAP_URL at its default unless your account lives in a separate Adobe environment. For an OAuth user, have them connect again, which stores a fresh access point. Clearing the SDK's cache store is safe; the data centre is rediscovered on the next call.
AccountCapabilityException
What you see
AccountCapabilityException with codes such as HYBRID_ROUTING_NOT_ALLOWED or GOV_ID_LIMIT_EXCEEDED, or a message saying something is not allowed by account settings.
Why it happens
The request is valid, but the Acrobat Sign account's plan or settings don't allow it: mixed sequential and parallel signing (hybrid routing), an identity method that isn't enabled or has no quota, digital signatures not enabled for AES or QES.
How to fix it
Enable the feature in Acrobat Sign admin or upgrade the plan, or change the request (for example a purely sequential order). Show users a clear message rather than a 500; $e->errorCode tells you which capability is missing. See turning exceptions into responses.
NotFoundException right after creating an agreement
What you see
signingUrlFor() or similar calls throw NotFoundException (AGREEMENT_NOT_SIGNABLE) within seconds of create(); other calls may report AGREEMENT_NOT_EXPOSED.
Why it happens
Adobe is still processing the document; the status is DOCUMENTS_NOT_YET_PROCESSED for a few seconds.
How to fix it
Retry after a short delay, or answer "try again shortly" (the example controller returns 409). In a job, $this->release(5).
RateLimitException
What you see
RateLimitException, usually during bulk work.
Why it happens
Adobe throttled the account and asked for a wait longer than retries.max_delay_seconds, or retries ran out.
How to fix it
Do bulk work in queued jobs and release them for $e->retryAfter seconds. Spread schedules out; don't raise max_delay_seconds for web requests.
Production and performance
Adobe reports webhook timeouts
What you see
Deliveries are retried even though your code ran.
Why it happens
The whole request took more than 5 seconds, usually because a listener isn't queued, or QUEUE_CONNECTION=sync in production.
How to fix it
Implement ShouldQueue on every listener that does real work and use a real queue connection.
Octane or workers use old settings
What you see
A changed key or level only applies to some requests.
Why it happens
Long-running processes loaded config at start. The manager itself is rebuilt per request and per job, but from the config the process already has.
How to fix it
php artisan octane:reload and php artisan queue:restart after changing config, as part of every deploy.
Common questions
How can I see what the SDK sends?
Set ADOBE_SIGN_LOG_CHANNEL to a channel at debug level to log requests and retries (never headers or bodies). To see full requests during development, bind a Guzzle client with a logging or history middleware as the PSR-18 client; never do that in production, since bodies contain documents and personal data.
Will an exception in my error tracker leak the key or tokens?
No. The SDK redacts the Authorization header and OAuth secrets from every exception it creates, and the token model hides tokens from serialisation. Your own code can still leak them if it logs config('adobe-sign') wholesale.
Why do agreement names start with [DEMO USE ONLY]?
Adobe adds that prefix to agreements created from developer accounts. It disappears on a paid production account; nothing in your code causes it.
None of this matches my problem. What next?
Check the core's troubleshooting page for API-level issues, then open an issue with the exception class, Adobe's error code, the package version and what you expected. Leave out keys, tokens and personal data.