> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vumasign.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Signer verification

> Ask a signer to enter a code emailed to them before the document opens, and what the certificate then says.

export const verificationSmsCode = "sms_code";

export const verificationSessionLifetime = "4 hours";

export const verificationResendCooldown = "30 seconds";

export const verificationPauseWindow = "1 hour";

export const verificationNone = "none";

export const verificationEmailCode = "email_code";

export const verificationCodesBeforePause = "3";

export const verificationCodeLifetime = "10 minutes";

export const verificationCodeLength = "6";

export const verificationAttemptsPerCode = "5";

export const routingCc = "cc";

export const recipientVerificationUnavailableCode = "recipient_verification_unavailable";

export const recipientVerificationNotAllowedCode = "recipient_verification_not_allowed";

export const recipientMobileUnusedCode = "recipient_mobile_unused";

export const eventRecipientVerificationLocked = "recipient.verification_locked";

export const eventRecipientCompleted = "recipient.completed";

A signing link is a bearer credential: whoever holds the email holds the document. Signer verification
adds one step for a recipient you choose. Before the document opens, they are emailed a
{verificationCodeLength}-digit code and must enter it.

## Turning it on

Set `verification_method` on a recipient when you create the envelope, with
[`POST /api/v1/envelopes`](/api-reference/create-envelope), a row of
[`POST /api/v1/envelopes/batches`](/api-reference/create-envelope-batch), or
[`POST /api/v1/envelopes/one-off`](/api-reference/create-one-off-envelope):

```json theme={null}
{
  "role": "Tenant",
  "name": "Thandi Mokoena",
  "email": "thandi@example.test",
  "verification_method": "email_code"
}
```

* <code>{verificationNone}</code> is the default. A request that does not name the field creates the recipient
  it always did.
* <code>{verificationEmailCode}</code> asks for a code sent to the recipient's `email`.
* It is decided per recipient, so one envelope can ask one signer for a code and not another. A template
  has no setting for it: the request decides, every time.
* It is fixed once the envelope is sent, like the rest of the recipient. See
  [what cannot change after sending](/concepts/after-sending).
* Every recipient the API describes carries `verification_method`, so you can read back what you asked for
  with [`GET /api/v1/envelopes/{envelopeId}`](/api-reference/get-envelope).

<code>{verificationSmsCode}</code> is in the published list and is not available yet: codes by text
message come later, and `mobile` is reserved for them.

## Who cannot be asked

* **An [embedded](/guides/embedded-signing) recipient cannot be asked for an email code.** Vumasign never
  emails an embedded recipient, so the code could never reach them. Your application signed them in, and
  that is the check. Use <code>{verificationNone}</code>.
* **A recipient on a <code>{routingCc}</code> role cannot be asked for anything.** A copied-in reader never
  opens a signing page, so there is nothing for them to verify.

Both are refused before anything is created. See the refusals below.

## What the signer sees

When they open their link, they see a short screen instead of the document. It names who sent it and the
address the code will go to, partly hidden. They choose to have the code emailed, then type it in.

* A code can be entered for {verificationCodeLifetime} after it is sent.
* A new code can be asked for after {verificationResendCooldown}. Asking for one replaces the last.
* They are asked once per signing session, not once per page. The session lasts {verificationSessionLifetime}
  in that browser, and never longer than the link itself. After that, or in another browser, they are asked
  again.
* Each code allows {verificationAttemptsPerCode} tries. Once {verificationCodesBeforePause} codes have been
  used up within {verificationPauseWindow}, the recipient is paused: no further code is sent for up to
  {verificationPauseWindow}, and the screen says when they can try again. The pause ends by itself, and
  nobody has to lift it. A new link ends it sooner: the sender's resend from the dashboard, or a
  reminder, each carry one.
* The pause is not the only limit. How many codes one link, one address or one network can be sent over
  time is limited too. Those limits also lift by themselves, but a new link does not lift the ones on the
  address or the network.

Every signing act checks the session again, not just the first page. A recipient who has not entered a code
can save nothing, sign nothing and download nothing.

## What the certificate says

The certificate of completion states a code only where the audit trail records the code being entered. It
does not read the setting on the recipient to decide. So a recipient who was asked for a code and never
signed is not described as verified, and a signature always names the verification that let it happen.

| Party | What the certificate prints as their authentication |
| - | - |
| A signer or approver who was emailed their link | “Email link with expiring token” |
| A signer or approver who is [embedded](/guides/embedded-signing) | “Single-use link issued to the sender's application, with expiring token” |
| A party copied in (`cc`), who is issued no link and takes no part | “None: copied in, so no link was issued” |

Where the audit trail records a one-time code being entered, the line goes on to say so — for example “Email link with expiring token, plus a one-time code by email to j•••@example.com, entered 01 Oct 2026, 12:02:11”. On a test document, whose code is shown on screen rather than sent, it reads “Email link with expiring token, plus a one-time code shown on screen (test document: nothing was sent), entered 01 Oct 2026, 12:02:11”.

An emailed code shows that the person signing had the mailbox when they signed, not only when the link was
sent. It is not a second factor: it reaches the same mailbox as the link.

## Refusals

Each refusal names the recipient by its path in your request, for example `recipients[1]` or
`rows[3].recipients[0]`. Nothing is created, and in a batch no row is created.

* <code>{recipientVerificationNotAllowedCode}</code>: an email code for an embedded recipient, or any code
  for a <code>{routingCc}</code>.
* <code>{recipientVerificationUnavailableCode}</code>: <code>{verificationSmsCode}</code>, until codes by text
  message are available.
* <code>{recipientMobileUnusedCode}</code>: a `mobile` was given. No method that sends to one is available
  yet, so it is refused rather than stored.

| Code | HTTP | Retry? | What it means |
| - | - | - | - |
| `recipient_verification_unavailable` | 422 | no | A recipient asked for a `verification_method` that is published and not offered yet — `sms_code`, until codes by text message are available. Use `email_code`, or `none`. The message names the recipient; nothing was created. |
| `recipient_mobile_unused` | 422 | no | A recipient carried a `mobile`, and their `verification_method` sends nothing to a mobile number. It is refused rather than dropped, because a number you sent being silently discarded would be a surprise, and rather than stored, because keeping a person’s phone number for no purpose is the thing not to do with it. Leave it out, or send `null`. |
| `recipient_verification_not_allowed` | 422 | no | A recipient was asked for a code they could never be asked for: `email_code` on an embedded recipient, who is never emailed by Vumasign — your application authenticated them — or any code on a role routed as `cc`, a copied-in reader who never opens a signing page. Use `none` for both. In a batch the message names the row, and nothing in the batch was created. |

## The webhook

Subscribe to <code>{eventRecipientVerificationLocked}</code> to hear when a recipient is paused. Its body
carries `recipient_id` and `locked_until`, the time the pause ends. That is not a promise that a code will be
sent then: the other limits above can still refuse one, and they publish nothing. It fires once per pause,
and not for a recipient who has already signed and is only entering a code to download what they signed.
It is the only verification event published: wrong codes and codes sent stay in the audit
trail, and you hear that the recipient got in from <code>{eventRecipientCompleted}</code> when they finish.
See [webhooks](/guides/webhooks) for the body and how to verify it.

A paused embedded recipient would be stuck with only you able to help, which is why the event exists. In
this release an embedded recipient cannot be asked for a code, so every paused recipient is one who was
emailed: they can wait, or the sender can resend their invitation from the dashboard.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.