Guides
Identity verification
By default, anyone who can open the email can open the agreement. An identity check adds a step between the email and the document: a one-time code, a password, a scan of an ID card or a national eID login. This page explains each method the SDK supports, what it costs the signer and you, and the limits Acrobat Sign puts on combining them.
Identity checks are not signature levels
It is easy to mix up two different questions:
| Identity check | Signature level | |
|---|---|---|
| Question it answers | Who may open the agreement? | How is the signature made, and what is it worth legally? |
| Set with | ParticipantInfo::verifyWith(Verification) | signatureLevel(SignatureLevel) |
| Adobe field | securityOption.authenticationMethod per participant | isDigitalSignatureRequired per participant set |
| Evidence | a line in the audit report and an event such as EMAIL_OTP_AUTHENTICATED | a certificate in the signed PDF (AES/QES) |
An identity check makes an SES much more convincing, because you can show the signer controlled the phone or inbox, or held the ID. It does not make the signature an AES: only a certificate-based digital signature does that. The two combine freely, and Signature levels explains the other dimension.
Adding a check to a participant
Identity checks belong to a person, so they are set on ParticipantInfo, the member of a
participant set, with a Verification built by one of its static constructors:
use Conceptlz\AdobeSign\Builder\{AgreementInfo, ParticipantInfo, ParticipantSetInfo, Verification};
$agreementId = $sign->agreements()->create(
AgreementInfo::create('Tenancy agreement')
->file($documentId)
->participantSet(ParticipantSetInfo::signer(
ParticipantInfo::email('tenant@example.com', 'Tenant')
->verifyWith(Verification::emailOtp()),
))
->participantSet(ParticipantSetInfo::signer(
ParticipantInfo::email('landlord@example.ae', 'Landlord')
->verifyWith(Verification::phone('501234567', '971', 'AE')),
)),
);
Each participant becomes a securityOption in the request:
"memberInfos": [{
"email": "landlord@example.ae",
"name": "Landlord",
"securityOption": {
"authenticationMethod": "PHONE",
"phoneInfo": { "phone": "501234567", "countryCode": "971", "countryIsoCode": "AE" }
}
}]
A plain email string (ParticipantSetInfo::signer('a@example.com')) has no check; wrap it in
ParticipantInfo::email() when you need one. Approvers, acceptors and form fillers can be
verified too, since the check guards opening the agreement, not signing it.
One method per participant
Adobe allows one identity check per signer
Adobe's securityOption has a single authenticationMethod, so each participant
gets exactly one check. You cannot ask for "Email OTP and SMS", or "government ID
then phone". verifyWith() reflects this: calling it again replaces the earlier
method, and the password() shorthand is the same as verifyWith(Verification::password(...)).
If one check isn't enough, choose a stronger single method (government ID or a national eID instead of a code), or require a digital signature (AES) on top, which brings the signing provider's own authentication with it.
The methods
No check
ParticipantInfo::email('a@example.com')->verifyWith(Verification::none()); // authenticationMethod NONE
Access through the email link alone, Acrobat Sign's default. Use it explicitly when an account-level default would otherwise add a check you don't want for this participant.
Email one-time code
ParticipantInfo::email('a@example.com')->verifyWith(Verification::emailOtp()); // EMAIL_OTP
A code is sent to the same address as the invitation. It proves the person opening the agreement controls that inbox right now, which protects against forwarded links. It doesn't protect against someone who already controls the inbox. It costs the signer one extra email and is a good default for anything that matters. What the signer sees is described below.
Password
ParticipantInfo::email('a@example.com')->password($sharedSecret);
// same as ->verifyWith(Verification::password($sharedSecret)) // PASSWORD
You choose a password and give it to the signer through another channel (by phone, in person, in a
letter). Adobe never reveals it. Both methods mark the parameter #[\SensitiveParameter],
so it stays out of stack traces, and an empty password throws InvalidArgumentException.
Simple and free, but its strength is only as good as the channel you used to share it.
SMS (or voice) code
Verification::phone('50 123 4567', '+971', 'AE'); // PHONE
// phoneInfo: { phone: "501234567", countryCode: "971", countryIsoCode: "AE" }
A code goes to the signer's mobile number. The SDK normalises the input: spaces, dashes, brackets and
dots are stripped from the number, a leading + is removed from the calling code, and the
ISO code is upper-cased. A number or calling code that isn't numeric after that, or an ISO code that
isn't two letters, throws InvalidArgumentException before anything is sent. Pass the
national number and the calling code separately, not one international string.
It proves possession of the phone, an independent factor from email, so it is stronger than Email OTP
against a compromised inbox. You need the signer's number, and phone checks can count against a
usage limit on some plans. When it runs out, Adobe answers PHONE_LIMIT_EXCEEDED (or a
related PHONE_AUTH_LIMIT_EXCEEDED code), which the SDK turns into an
AccountCapabilityException.
WhatsApp code
Verification::whatsApp('501234567', '971', 'AE'); // WHATSAPP
The same as the phone method, but the code arrives through WhatsApp. Useful in regions where WhatsApp is more reliable than SMS. Availability depends on the account.
Knowledge-based authentication
Verification::knowledgeBased(); // KBA
The signer answers multiple-choice questions generated from US public records
(previous addresses, loans and similar). That limits it to people with a US record history: signers
outside the US usually can't pass it, and there is nothing they can do about it. Use it only for US
consumers. It is metered on many plans; when the quota runs out, Adobe answers KBA_LIMIT_EXCEEDED, which
the SDK turns into an AccountCapabilityException.
Government ID
Verification::governmentId(); // GOV_ID
The signer photographs an identity document (passport, ID card, driving licence) and, depending on the account's settings, takes a selfie that is compared with it. It works internationally and gives strong evidence that a real, named person opened the agreement.
It uses a paid quota on the Acrobat Sign account. When the quota is used up or was
never purchased, Adobe answers GOV_ID_LIMIT_EXCEEDED, which the SDK turns into an
AccountCapabilityException. The signer also needs a phone camera, and some people find
the step intrusive, so reserve it for agreements where the assurance is worth it.
National eID or bank ID (Digital Identity Gateway)
Verification::digitalIdentity(providerId: 'your-provider-id'); // DIG_ID
Verification::digitalIdentity('your-provider-id', loginHint: 'user@example.se');
Adobe's Digital Identity Gateway sends the signer to an identity provider they already use, for example a national eID or bank ID scheme such as itsme, BankID or IDnow. It is excellent evidence, and familiar to signers in countries where these schemes are common.
The provider must first be configured on the account by an Acrobat Sign admin, who gives you its
providerId. The SDK rejects a blank provider id, and a loginHint over 255
characters, with InvalidArgumentException. If the account doesn't allow the method, Adobe
answers with a message such as "not allowed by account settings", which the SDK maps to
AccountCapabilityException.
Adobe account
Verification::adobeAccount(); // ADOBE_SIGN
The signer must sign in with an Acrobat Sign (Adobe) account before opening the agreement. Suitable for colleagues or partners who already have one; a hurdle for consumers who don't.
Comparing the methods
| Method | Builder | The signer needs | Cost and availability | Typical use |
|---|---|---|---|---|
| None | none() | the email | free, always available | internal acknowledgements, low-risk forms |
| Email OTP | emailOtp() | access to the same inbox | free on plans that offer it | sensible default for contracts |
| Password | password() | the password you gave them | free | when you already talk to the signer by phone or in person |
| SMS / voice | phone() | a mobile number you know | may be metered | consumer contracts, second factor beyond email |
whatsApp() | WhatsApp on a number you know | account-dependent | regions where WhatsApp beats SMS | |
| KBA | knowledgeBased() | a US public-records history | metered; US only in practice | US consumer finance |
| Government ID | governmentId() | an ID document and a phone camera | paid quota (GOV_ID_LIMIT_EXCEEDED) | high-value or regulated agreements, international |
| Digital Identity Gateway | digitalIdentity() | a national eID or bank ID | a provider must be configured on the account | countries with widespread eID (Nordics, Benelux, …) |
| Adobe account | adobeAccount() | an Acrobat Sign account | free | B2B, colleagues |
Which methods appear in your account, and at what price, is decided by your Acrobat Sign plan and an admin. Treat the "cost" column as a guide and check your own plan.
Email OTP in practice
This is what happened when Email OTP was tested against a real account:
- The usual invitation
The signer received the normal "please sign" email and clicked through.
- Asked to send a code
Instead of the document, Acrobat Sign showed a page asking the signer to request a verification code by email.
- A second email
A separate email arrived containing the one-time code.
- Code entered, document opened
After entering the code, the signer saw the document and signed as usual.
The agreement's events show the check happening before the document was viewed:
CREATED sender@example.com
ACTION_REQUESTED signer@example.com
EMAIL_OTP_AUTHENTICATED signer@example.com
DOCUMENTS_VIEWED signer@example.com
ACTION_COMPLETED signer@example.com
The audit report records it in plain words: the signer "authenticated with Email OTP by verifying
one-time code sent via email". Webhooks report the same moment as
EventType::AGREEMENT_EMAIL_OTP_AUTHENTICATED. Adobe's event reference also documents
AGREEMENT_RECALLED_MAX_SIGNING_EMAIL_OTP_ATTEMPTS, for when a signer enters wrong codes too
many times and the agreement is recalled.
Tell signers to expect the second email. Most confusion with OTP comes from people who don't see it (spam folders, delayed delivery), not from the code itself.
Checking which check applied
Two places tell you what was required of each participant:
// Per signer, together with the signature level reached
foreach ($sign->agreements()->signatureEvidence($agreementId)->signers as $signer) {
echo $signer->email, ': ', $signer->verification, "\n"; // NONE, EMAIL_OTP, PHONE, KBA, GOV_ID, DIG_ID, ...
}
// For every participant, from the members list
foreach ($sign->agreements()->members($agreementId)['participantSets'] as $set) {
foreach ($set['memberInfos'] as $member) {
echo $member['email'], ': ', $member['securityOption']['authenticationMethod'] ?? 'NONE', "\n";
}
}
Those show what was required. Proof that the signer passed the check is in the events
(EMAIL_OTP_AUTHENTICATED, PHONE_AUTHENTICATED, KBA_AUTHENTICATED,
GOV_ID_AUTHENTICATED, DIG_ID_AUTHENTICATED and similar) and in the audit report from auditTrail().
When the account doesn't allow a method
Your code can be perfectly valid and Adobe can still refuse, because the method is switched off for the account, not in its plan, or out of quota. The SDK separates these from input errors:
| Adobe says | The SDK throws | Fix |
|---|---|---|
GOV_ID_LIMIT_EXCEEDED | AccountCapabilityException | buy or top up the government-ID quota |
400 with "not allowed by account settings" (e.g. DIG_ID without a configured provider) | AccountCapabilityException | enable the method or configure the provider in Acrobat Sign admin |
KBA_LIMIT_EXCEEDED, PHONE_LIMIT_EXCEEDED, PHONE_AUTH_LIMIT_EXCEEDED, AGREEMENT_PHONE_AUTH_LIMIT_EXCEEDED | AccountCapabilityException | buy or top up the KBA or phone quota, or choose another method |
SIGNER_AUTH_NOT_ALLOWED, AUTH_METHOD_PROVIDED_IS_NOT_ALLOWED and their INTERNAL_ / EXTERNAL_ variants | AccountCapabilityException | the sender's settings don't allow this check for that kind of recipient; enable it in Acrobat Sign admin |
INVALID_RECIPIENT_AUTH_METHOD, INVALID_PHONE_NUMBER | ValidationException | fix the request |
AccountCapabilityException extends ApiException, so existing handlers keep
working. Catch it specifically when you want a fallback:
use Conceptlz\AdobeSign\Builder\{AgreementInfo, ParticipantInfo, ParticipantSetInfo, Verification};
use Conceptlz\AdobeSign\Exception\AccountCapabilityException;
$build = fn (Verification $check) => AgreementInfo::create('Guarantee')
->file($documentId)
->participantSet(ParticipantSetInfo::signer(
ParticipantInfo::email('guarantor@example.com')->verifyWith($check),
));
try {
$agreementId = $sign->agreements()->create($build(Verification::governmentId()));
} catch (AccountCapabilityException $e) {
$log->warning('Government ID unavailable, falling back to SMS', ['code' => $e->errorCode]);
$agreementId = $sign->agreements()->create($build(Verification::phone($phone, $countryCode)));
}
Whether a fallback is acceptable is a business decision: for some agreements the right reaction to a missing government-ID quota is to stop and alert someone, not to send with a weaker check.
Different checks for different people
Each participant has their own check, so one agreement can use several methods, and members of the same participant set (any one of whom may sign for the set) can each have their own:
AgreementInfo::create('Supply agreement')
->file($documentId)
// Either director may sign for the customer; each proves identity their own way
->participantSet(ParticipantSetInfo::signer(
ParticipantInfo::email('director.one@customer.example')->verifyWith(Verification::governmentId()),
ParticipantInfo::email('director.two@customer.example')->verifyWith(Verification::digitalIdentity('your-provider-id')),
))
// Our own signatory signs in with their Adobe account
->participantSet(ParticipantSetInfo::signer(
ParticipantInfo::email('ceo@supplier.example')->verifyWith(Verification::adobeAccount()),
));
Common questions
Can I require Email OTP and SMS for the same signer?
No. Adobe accepts one authenticationMethod per participant, and a second
verifyWith() call replaces the first. Pick the stronger single method, or add a
digital signature (AES), which brings the signing provider's own authentication.
Does Email OTP make the signature an AES?
No. It is still an SES; the code strengthens access to the document, not the signature itself. See the same question on the signature-levels page.
Why can't my signer in Europe pass KBA?
The questions come from US public records, so people without a US history can't answer them. Use
government ID, a national eID through digitalIdentity(), or an SMS code instead.
What does GOV_ID_LIMIT_EXCEEDED mean?
The account has no government-ID verifications left (or never had any). It arrives as an
AccountCapabilityException; the fix is a purchase in Acrobat Sign, not a code change.
Creating an agreement fails with "not allowed by account settings". What now?
The method you chose is switched off for the account, or (for DIG_ID) no Digital
Identity Gateway provider is configured. Ask an Acrobat Sign admin to enable it, or choose another
method. The SDK throws AccountCapabilityException so you can tell this apart from a
mistake in your request.
How can I show later that the signer passed the check?
Keep the audit report: it records the check in plain words, for example "authenticated with Email
OTP by verifying one-time code sent via email". The agreement's events carry the same information
(EMAIL_OTP_AUTHENTICATED before DOCUMENTS_VIEWED).