Recipient authentication
Every recipient is verified at least by the signing link sent to their email address. A sender can ask for more: a one-time code emailed to the same address, which the recipient enters before the document is shown. The level that was satisfied is recorded in the document's audit trail and printed on the certificate page, so higher assurance is provable, not only enforced.
The levels
auth_level | What it means | Status |
|---|---|---|
link | The signing link sent to the recipient's address is the credential. The default. | Available |
otp | The signing link, plus a 6-digit code emailed to the same address. The document is not shown until the code is entered. | Available |
account | The recipient signs in to a SignEnvoy account whose verified address matches. | Refused with auth-level-unavailable (422) until accounts ship |
passkey | An account plus a passkey assertion at signing time. | Refused with auth-level-unavailable (422) until accounts ship |
The API refuses the two unavailable levels rather than storing a promise
it cannot enforce. Nothing is created or changed when a request is refused;
the detail names the recipient and the level.
Setting the level
In the API
auth_level is a property of each recipient, accepted wherever
recipients are given: POST /v1/documents,
PUT /v1/documents/{id}/recipients,
POST /v1/templates (placeholder roles), and
POST /v1/templates/{id}/use.
curl -s -X POST https://signenvoystaging.fly.dev/v1/documents \
-H "Authorization: Bearer senv_test_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Lease",
"recipients": [
{"name": "Ada Lovelace", "email": "ada@example.com", "auth_level": "otp"},
{"name": "Bob Byrne", "email": "bob@example.com"}
]
}'
Ada must enter a code before she sees the document; Bob's link alone is
his credential. Omitting auth_level means link.
In the dashboard
On the Recipients step of the send flow, open More options and choose, per person, between "Signing link only" and "Signing link plus a one-time code". The review step and the document page show the level next to each recipient, and a badge reads "Verified by a one-time code" once it has been satisfied.
What the recipient experiences
-
The first screen explains itself before asking for anything: who is
asking, why this document needs a code, and the masked address the code
goes to (for example
a***@example.com). Declining the request is possible from this screen; a recipient is never forced through the code to say no. - The recipient sends the code to their address. Each code is six digits and works for 10 minutes. Only the most recent code counts.
- A wrong code says so and shows the attempts left. After 5 wrong attempts the link stops working; the sender can re-send, which issues a fresh link that starts with a clean count.
- A new code can be requested after a 60 second cooldown, at most 10 times in 24 hours. The waiting screen counts the seconds down.
- "Request a new link" stays reachable at every step, so a recipient whose code expired or whose attempts ran out is never stranded.
- Once the code is accepted, the consent screen and the document appear as they would for any other recipient.
What integrators see on the ceremony API
If you drive the signing surface yourself, the session object carries an
auth member:
{
"auth": {
"required": true,
"level": "otp",
"satisfied": false,
"destination_masked": "a***@example.com",
"resend_after_s": 0
},
"recipient": { "email": "a***@example.com", ... },
"fields": []
}
While required is true and satisfied is false, the
recipient's email is masked, fields is empty, and the PDF,
consent, field, and completion routes answer
auth-required (409). Two
routes move the session forward:
POST /ceremony/v1/session/{token}/otp/send: emails a code; returnsresend_after_sanddestination_masked. Inside the cooldown or past the daily limit it answersrate-limitedwithRetry-After.POST /ceremony/v1/session/{token}/otp/verifywith{"code": "123456"}: 204 on success, after which the session reloads withsatisfied: trueand the fields present.
Verification can fail three ways:
otp-invalid(422): wrong code; the body carriesattempts_remaining.otp-expired(410): the code is older than 10 minutes; request a new one.otp-attempts-exhausted(403): 5 wrong codes; this link is done. The sender's re-send issues a fresh link.
Rejecting the request (POST .../reject) works before the code
is entered.
Test mode
For a document created with a test key no code is emailed. The code is stored with the captured emails and shown in the dashboard under Settings, Test mode, as "Code sent to {email}: {code}", so you can walk the whole flow without a mailbox.
What the record says
The satisfied level is an audit event on the document, and the certificate page carries a "Verified by:" line per recipient with one of two sentences:
- "Signing link sent to this email address"
- "Signing link plus a one-time code emailed to this address"
The recipient's completion screen says the same thing in the second person ("You were verified by the signing link plus a one-time code emailed to this address."), so what the recipient read and what the artifact records never differ.