Verdocs - Developer Documentation

Proxied signing flows

Complete one recipient's part of an envelope through the API from your own interface, such as a chat assistant or a lobby kiosk, without showing them a signing interface.

In a proxied signing flow, your application completes one recipient's part of an envelope by calling the Verdocs API on their behalf. The recipient still accepts the disclosures, answers each field, adopts a signature, and submits, but they do it through your interface (e.g. a chat conversation, a kiosk screen, or a step in your own app) and never interact with the document in a signing view.

Where this fits

A proxied flow gives up some of the legal weight of a standard signing session in exchange for a much lighter experience. It is not intended to be used for documents that require strong legal binding by all parties, but may be appropriate in cases where one recipient has a lower level of commitment relative to the others, such as:

  • Accepting a benefit. The recipient of an insurance payout is simply accepting that payout - the insurer is the party taking on the real obligation. A claims manager or other authorized company representative would still participate normally, but the beneficiary is accepting a check, and the insurer is never going to sue them to make them take it. An AI-assisted chat session, text or email message engagement, mobile app form, or other streamlined interface might be all that's needed to record acceptance, and it benefits the recipient to have a painless experience during a stressful time.
  • Kiosks and shared devices. A tablet in the lobby of a gym or coworking space asks guests to sign a waiver or a guest agreement. No document needs to be presented - just the terms and an opportunity for the guest to sign their acceptance and Submit.
  • Disclosure / Terms and Conditions Dialogs. Web and mobile apps often present disclosures and terms in modals or other formats in onboarding flows. The user's acceptance is often little more than a database column or sometimes not stored at all. Courts may not hold users to these agreements because the developer failed to provide an audit trail of the user's interaction with these dialogs, making them useless barriers in some cases, but a full e-signing flow is still overkill for this need. A proxied signing flow can provide that audit trail with just a few API calls.
  • Sign-off on a staff device. A technician or courier hands their tablet to a customer to confirm the work was done or the delivery arrived.
  • Acknowledgements inside your product. A signed-in user acknowledges a policy, a handbook, or updated terms from a screen in your app, where your own login already identifies them.

When a recipient is taking on obligations someone may need to enforce later, such as a loan, a lease, or an employment agreement, we recommend a standard signing flow with recipient authentication for that person. Proxied signing is set up per recipient, not per envelope, so one envelope can have a proxied beneficiary followed by a claims manager who signs in the usual Verdocs interface.

Authentication

In a standard flow, Verdocs establishes who the recipient is. The invitation goes to their email or phone, and the envelope can require a passcode, a one-time code, KBA, or an ID scan before they sign. Developers can set no_contact when creating an envelope to disable notifications, but this only changes who is responsible for handling those notifications (to the developer) - the workflow still proceeds in the same way.

A proxied flow replaces the signing user interface with a sequence of API calls. Your application starts the session with an in-person link (NOTE: this makes you responsible for authenticating the recipient). Verdocs records the session as in-person, and our audit trail and signing certificate will show this as an in-person session. The signature details on the certificate (IP address, platform, and device) describe whichever machine made the API calls, which means your server in a chat flow and the tablet in a kiosk flow. Keep your own record of how you identified the recipient, tied to the envelope ID, so you can produce it alongside the certificate.

Leave auth_methods empty for a proxied recipient. Since the user will not be directly interacting with Verdocs, we cannot conduct additional authentication steps. If needed, these must be performed by your application.

Workflow

Your server creates the envelope as usual and requests an in-person link for the proxied recipient. That link's invite code starts a signing session scoped to that one recipient. With the session, you accept the disclosures, fill in the recipient's fields, create and apply their signature and initials, and submit. Webhooks tell your server what happens after that.

The code below uses @verdocs/js-sdk. If you're working in another language, each step links the REST call behind it.

1. Create the envelope

Create the envelope on your server with your API key.

import {VerdocsEndpoint, authenticate, createEnvelope} from '@verdocs/js-sdk';

const server = new VerdocsEndpoint();

const {access_token} = await authenticate(server, {
  grant_type: 'client_credentials',
  client_id: process.env.VERDOCS_CLIENT_ID!,
  client_secret: process.env.VERDOCS_CLIENT_SECRET!,
});
server.setToken(access_token);

const envelope = await createEnvelope(server, {
  template_id: '8e0b...c41a',
  name: 'Claim 20931 settlement acceptance',
  no_contact: true,
  recipients: [
    {role_name: 'Beneficiary', first_name: 'Ada', last_name: 'Lovelace', email: 'ada@example.com'},
    {role_name: 'Claims Manager', first_name: 'Grace', last_name: 'Hopper', email: 'grace@insurer.example'},
  ],
});

no_contact applies to the whole envelope, so choose it with every recipient in mind. With no_contact: true, Verdocs sends no email or text to anyone on the envelope, and your application tells each later recipient when it's their turn (Bring-your-own notification covers how). With it off, later recipients get the standard Verdocs invitation and sign in the usual interface, but the proxied recipient also gets an invitation email when their turn arrives, which is immediately if they act first. For a single-recipient kiosk flow, no_contact: true is the natural choice.

Request the link from your server with the same credentials that created the envelope, because Verdocs issues in-person links only to the envelope's sender. (Get In-Person Signing Link)

import {getInPersonLink} from '@verdocs/js-sdk';

const inPerson = await getInPersonLink(server, envelope.id, 'Beneficiary');

inPerson.link; // A Verdocs signing URL ending in /sign/<envelope_id>/<role_name>/<invite_code>
inPerson.access_key.key; // The invite code from the end of that URL
inPerson.access_token; // A signing session for this recipient

The response breaks the link into its parts, so there's no URL to parse. access_key.key is the invite code, and access_token is a signing session you can use right away when the same process carries on with the signing. When signing happens somewhere else, such as on the kiosk tablet or in a chat worker, send that process only the invite code and start the session there in step 3. Treat the invite code like a password for this recipient on this envelope, and never send your API credentials to a kiosk or a browser.

If the proxied recipient doesn't act first, wait for their turn before you start. The recipient_invited webhook fires when it arrives, or recipient_invite_failed on a no_contact envelope.

3. Start the signing session

Start the session wherever the signing happens, with the envelope ID, role name, and invite code from step 2. It gets its own VerdocsEndpoint, separate from the one holding your API session. (Start Signing Session)

import {VerdocsEndpoint, startSigningSession} from '@verdocs/js-sdk';

// Kiosks are shared, so keep the session out of localStorage
const signer = new VerdocsEndpoint({sessionType: 'signing', persist: false});

const session = await startSigningSession(signer, envelopeId, roleName, inviteCode);
const {envelope, recipient} = session;

startSigningSession attaches the token to the endpoint for you. To use the access_token from step 2 instead, attach it with signer.setToken(inPerson.access_token, 'signing'). Make every remaining call with this endpoint: those calls act as the recipient, and submitting through the signing session is what records In-Person Link on the certificate.

A signing session lasts two hours. If a conversation pauses for longer, call startSigningSession again with the same invite code to pick up where the recipient left off.

4. Accept the disclosures

Before anything else, the recipient agrees to do business electronically. Show them the same disclosure text the Verdocs signing interface would, get a clear yes, and send that text with the call. Verdocs stores it with the time of acceptance, and both appear on the certificate. (Agree to e-Signing Disclosures)

import {DEFAULT_DISCLOSURES, envelopeRecipientAgree} from '@verdocs/js-sdk';

const disclosures = session.brand?.disclaimer || envelope.organization?.disclaimer || DEFAULT_DISCLOSURES;

// After the recipient has read and agreed to `disclosures`
const {timeZone, locale} = Intl.DateTimeFormat().resolvedOptions();
await envelopeRecipientAgree(signer, envelopeId, roleName, disclosures, {timezone: timeZone, locale});

DEFAULT_DISCLOSURES is HTML with links to the full disclosure and our license agreement, so render those links in your interface rather than stripping them. The timezone and locale control how dates appear on the certificate for this recipient. In a chat flow running on your server, send the recipient's own values instead of your server's.

The recipient doesn't see the document in a signing view, but they should still be able to read it. Your server can generate a short-lived link for them to open right away with getEnvelopeDocumentPreviewLink(server, document.id) for each entry in envelope.documents.

5. Fill in the fields

The recipient's fields arrive in the session's envelope, sorted in the order they appear on each page. Each one has a type, a label to prompt with, required, and any value the sender prefilled.

import {updateEnvelopeField} from '@verdocs/js-sdk';

const fields = (envelope.fields || []).filter((field) => field.role_name === roleName && !field.readonly);

for (const field of fields) {
  if (field.type === 'textbox') {
    const answer = await askRecipient(field.label || field.name); // Your chat or kiosk prompt
    await updateEnvelopeField(signer, envelopeId, roleName, field.name, answer, false);
  } else if (field.type === 'checkbox') {
    const checked = await confirmWithRecipient(field.label || field.name);
    await updateEnvelopeField(signer, envelopeId, roleName, field.name, String(checked), false);
  }
}

Leave the last argument, prepared, as false. The value depends on the field type:

Field typeValue
textboxThe text
checkbox, radio"true" or "false"
signatureA signature block ID (step 6)
initialAn initials block ID (step 6)
timestampNothing to send. Verdocs fills it in at submission.

6. Adopt and apply a signature and initials

The recipient adopts a signature by approving the image that will appear on the document. Create the signature block once, then apply it to each of their signature fields, and do the same with initials. (Create Signature Block, Create Initial Block)

import {createInitials, createSignature, fullNameToInitials, updateEnvelopeField} from '@verdocs/js-sdk';

const name = `${recipient.first_name} ${recipient.last_name}`;
const signature = await createSignature(signer, 'signature', await renderTypedName(name));
const initials = await createInitials(signer, 'initial', await renderTypedName(fullNameToInitials(name)));

for (const field of fields) {
  if (field.type === 'signature') {
    await updateEnvelopeField(signer, envelopeId, roleName, field.name, signature.id, false);
  } else if (field.type === 'initial') {
    await updateEnvelopeField(signer, envelopeId, roleName, field.name, initials.id!, false);
  }
}

Both images are PNGs. On a kiosk, let the guest draw on a canvas, or render their typed name in a script font the way the Verdocs signing interface does:

// Draws a typed name as a transparent PNG. Load the font before calling this.
const renderTypedName = (text: string, width = 300, height = 80) => {
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;

  const context = canvas.getContext('2d')!;
  context.font = '40px "Dancing Script"';
  context.textAlign = 'center';
  context.textBaseline = 'middle';
  context.fillText(text, width / 2, height / 2);

  return new Promise<Blob>((resolve) => canvas.toBlob((blob) => resolve(blob!), 'image/png'));
};

In a chat flow, show the recipient the rendered image and have them confirm it before you create the block. On a server, any canvas library that outputs a PNG works, wrapped as new Blob([png], {type: 'image/png'}).

7. Submit

Submitting finishes the recipient's part. (Submit envelope)

import {envelopeRecipientSubmit} from '@verdocs/js-sdk';

await envelopeRecipientSubmit(signer, envelopeId, roleName, {timezone: timeZone, locale});

Verdocs checks every required field first, and the call fails if any is empty or invalid. Once it succeeds, Verdocs stamps the recipient's fields onto the documents, fills in any timestamps, and moves the envelope to the next recipient. The signing session has nothing left to do, so discard the endpoint (on a kiosk, before the next guest walks up).

If the recipient refuses instead, call envelopeRecipientDecline(signer, envelopeId, roleName). A decline ends the envelope for every recipient, and recipient_declined fires.

Closure and Next Steps

recipient_submitted fires as soon as the submission succeeds, and envelope_completed fires once every recipient has acted and the signed documents and certificate are ready. In between, the remaining recipients take their turns:

  • With no_contact off, Verdocs invites the next recipient as usual, recipient_invited fires, and they sign in the standard interface.
  • With no_contact: true, Verdocs sends nothing and fires recipient_invite_failed as each recipient's turn arrives. That's your cue to notify them yourself, or to start another proxied session if they're proxied too.

Act on these events from your server rather than relying on the kiosk or chat client to report back. Webhooks lists every event, and Securing your webhooks covers verifying them.

On this page