Integrating DocuSign electronic signature on a website
DocuSign is the market leader in electronic signatures in the US and Europe. It supports legally binding signatures according to eIDAS standards (Europe), UETA/ESIGN (USA), and several national standards. For the Russian market, DocuSign provides a simple electronic signature (PEP), which is recognized in court when there is an agreement between parties — sufficient for most commercial contracts.
Integration architecture
Typical flow:
- On the website, the user fills in the data → clicks "Sign contract"
- The backend creates an Envelope in DocuSign with the document and recipients
- The user is redirected to DocuSign for signing (or receives an email)
- After signing, DocuSign notifies the website via webhook
- The backend downloads the signed document and saves it
Application setup
In DocuSign Developer Portal: create an Integration Key → add redirect URI → request a Secret Key. For testing, use the free Demo environment (account-d.docusign.com).
composer require docusign/esign-client
// config/docusign.php
return [
'integrator_key' => env('DOCUSIGN_INTEGRATOR_KEY'),
'client_secret' => env('DOCUSIGN_CLIENT_SECRET'),
'account_id' => env('DOCUSIGN_ACCOUNT_ID'),
'base_url' => env('DOCUSIGN_BASE_URL', 'https://demo.docusign.net/restapi'),
'redirect_uri' => env('DOCUSIGN_REDIRECT_URI'),
];
OAuth: getting a token
DocuSign uses OAuth 2.0 Authorization Code Grant:
class DocuSignAuthService
{
public function getAuthUrl(): string
{
$params = http_build_query([
'response_type' => 'code',
'scope' => 'signature',
'client_id' => config('docusign.integrator_key'),
'redirect_uri' => config('docusign.redirect_uri'),
]);
// Demo: account-d.docusign.com, Prod: account.docusign.com
return 'https://account-d.docusign.com/oauth/auth?' . $params;
}
public function handleCallback(string $code): string
{
$response = Http::withBasicAuth(
config('docusign.integrator_key'),
config('docusign.client_secret')
)->asForm()->post('https://account-d.docusign.com/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => config('docusign.redirect_uri'),
]);
return $response->json('access_token');
}
}
For server scenarios without user involvement — JWT Grant (service account).
Creating an envelope and sending for signing
class DocuSignEnvelopeService
{
public function createEnvelope(
string $accessToken,
string $pdfPath,
array $signers
): string {
$config = new \DocuSign\eSign\Configuration();
$config->setHost(config('docusign.base_url'));
$config->addDefaultHeader('Authorization', "Bearer {$accessToken}");
$apiClient = new \DocuSign\eSign\client\ApiClient($config);
$envelopesApi = new \DocuSign\eSign\Api\EnvelopesApi($apiClient);
// Document from file
$document = new \DocuSign\eSign\Model\Document([
'document_base64' => base64_encode(file_get_contents($pdfPath)),
'name' => 'Contract',
'file_extension' => 'pdf',
'document_id' => '1',
]);
// Signature placement
$signHere = new \DocuSign\eSign\Model\SignHere([
'anchor_string' => '/sig1/', // or fixed coordinates
'anchor_x_offset' => '20',
'anchor_y_offset' => '-10',
'anchor_units' => 'pixels',
]);
$recipientList = [];
foreach ($signers as $i => $signer) {
$tabs = new \DocuSign\eSign\Model\Tabs(['sign_here_tabs' => [$signHere]]);
$recipientList[] = new \DocuSign\eSign\Model\Signer([
'email' => $signer['email'],
'name' => $signer['name'],
'recipient_id' => (string)($i + 1),
'routing_order'=> (string)($i + 1),
'tabs' => $tabs,
]);
}
$envelopeDefinition = new \DocuSign\eSign\Model\EnvelopeDefinition([
'email_subject' => 'Please sign the document',
'documents' => [$document],
'recipients' => new \DocuSign\eSign\Model\Recipients([
'signers' => $recipientList,
]),
'status' => 'sent', // 'created' for draft
]);
$result = $envelopesApi->createEnvelope(
config('docusign.account_id'),
$envelopeDefinition
);
return $result->getEnvelopeId();
}
}
Embedded Signing: signing directly on the website
Instead of redirecting to DocuSign — embedded iframe or redirect back to the website:
public function getSigningUrl(string $accessToken, string $envelopeId, array $signer): string
{
$config = new \DocuSign\eSign\Configuration();
$config->setHost(config('docusign.base_url'));
$config->addDefaultHeader('Authorization', "Bearer {$accessToken}");
$apiClient = new \DocuSign\eSign\client\ApiClient($config);
$envelopesApi = new \DocuSign\eSign\Api\EnvelopesApi($apiClient);
$viewRequest = new \DocuSign\eSign\Model\RecipientViewRequest([
'authentication_method' => 'none',
'client_user_id' => $signer['id'], // internal user ID
'recipient_id' => '1',
'return_url' => route('contracts.signed'),
'user_name' => $signer['name'],
'email' => $signer['email'],
]);
$result = $envelopesApi->createRecipientView(
config('docusign.account_id'),
$envelopeId,
$viewRequest
);
return $result->getUrl(); // redirect here or open in iframe
}
Webhook: signing notification
public function handleDocuSignWebhook(Request $request): Response
{
// DocuSign sends XML
$xml = simplexml_load_string($request->getContent());
$status = (string)$xml->EnvelopeStatus->Status;
$envelopeId = (string)$xml->EnvelopeStatus->EnvelopeID;
if ($status === 'Completed') {
DownloadSignedDocumentJob::dispatch($envelopeId);
}
return response('OK', 200);
}
Timeline
Basic integration (envelope creation + email sending to signer + webhook): 2–3 working days. Embedded signing with full workflow inside the website and automatic document download: 4–5 working days. Timeline includes registering the DocuSign application, testing in the Demo environment, and switching to Production.







