Skip to content

omnisign/docusign

Omnisign DocuSign: envelopes through DocuSign's eSignature REST API v2.1 - JWT grant with the application's RSA key, documents, signers in order, signature tabs, embedded signing (recipient views), reminders, voiding, the combined signed document and the certificate of completion, Connect webhooks with their HMAC checked.

Floor
Provider · Omnisign
Version
1.x
Conformance suite
not wired
Verified for real
Verified in part
Licence
MIT
composer require omnisign/docusign:1.x-dev

DocuSign eSignature for omnisense/omnisign: envelopes signed by e-mail or in the application's page, through the eSignature REST API v2.1, authenticated by the JWT grant - an integration acting as one of the account's users. The gateway calls https://account-d.docusign.com/oauth/token and https://demo.docusign.net/restapi (the demo environment, sandbox: true, the default), or https://account.docusign.com/oauth/token and the account's base URI read from /oauth/userinfo (sandbox: false).

Installation

composer require omnisign/docusign

PHP 8.2 or later, ext-openssl, omnisense/omnisign and symfony/http-client. A DocuSign integration (its key, an RSA key pair whose public half is on the integration), the user it acts as

  • who granted it consent once -, and the account's id.

The sources

Read on 2026-10-08: the eSignature REST API v2.1 OpenAPI (docusign/OpenAPI-Specifications, esignature.rest.swagger-v2.1.json, MIT) - envelopes, recipients, recipient views, documents combined and certificate; DocuSign's guides on the JWT grant (account-d.docusign.com / account.docusign.com, grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer, claims iss, sub, aud, iat, exp, scope: signature impersonation, RS256) and on Connect's HMAC (X-DocuSign-Signature-1, -2..., base64 HMAC-SHA-256 of the raw body, one per active key).

Credentials

  • An integration in DocuSign's admin (Apps and Keys): its integration key, and an RSA key pair whose public half is on the integration (the private half is private_key).
  • The user the integration acts as (user_id), who granted it consent once; until then the token is refused with consent_required (InvalidKeyException).
  • The API account id (a GUID).
  • For webhooks, Connect configured to send JSON with HMAC, and its keys (hmac_keys).

Options

Option Default
integration_key, user_id, account_id required
private_key required the RSA private key's PEM, or its path
sandbox true the demo environment (account-d.docusign.com, demo.docusign.net); false: production
base_uri the account's https://<host>/restapi; production: read from /oauth/userinfo when left empty
hmac_keys Connect's keys (a list, or comma-separated); without them, notify() is not supported

From an envelope to DocuSign's

create() POST /v2.1/accounts/{id}/envelopes, status: created: the documents in base64 (documentId 1, 2...), the signers (recipientId 1, 2..., routingOrder their turn when ordered, clientUserId their key when embedded) and their tabs - SIGNATURE signHereTabs, INITIALS initialHereTabs, DATE dateSignedTabs, TEXT and MENTION textTabs; a position (pageNumber, xPosition, yPosition) or an anchorString; an expiry in days
send() PUT .../envelopes/{id}, status: sent
fetch() GET .../envelopes/{id}?include=recipients
remind() PUT .../envelopes/{id}/recipients?resend_envelope=true with the signers who have not signed
cancel() PUT .../envelopes/{id}, status: voided with its voidedReason
download() GET .../documents/combined?certificate=false, GET .../documents/certificate
signingUrl() POST .../views/recipient (returnUrl, authenticationMethod: none, e-mail, name, clientUserId) - an envelope created embedded only; use the URL at once
notify() Connect in JSON: the HMAC checked against each key, event read (recipient-completed: SIGNED, envelope-completed: COMPLETED...)

Only the simple level, and no signer authentication: advanced and qualified signatures, an access code, a phone check are the DocuSign account's settings, not written here - such an envelope is refused.

DocuSign Status Recipient SignerStatus
created DRAFT created WAITING
sent, delivered SENT sent NOTIFIED
completed COMPLETED delivered OPENED
declined DECLINED signed, completed SIGNED
voided CANCELED (EXPIRED when voided for expiry) declined DECLINED

Capabilities

Request Supported
Create, Send, Fetch, Remind, Cancel, Download yes
SigningUrl yes, for an envelope created embedded
Notify with hmac_keys

capabilities(): simple; in the page; ordered; no identity check; third party; documents; no templates. Signer::$phone, $locale and $role are not sent; Document::$signable is not read (every document is sent alike).

Token. A JWT (RS256, iss the integration key, sub the user, aud the account server, an hour's life, scope: signature impersonation) exchanged for an access token, kept until a minute before it lapses. A private key it cannot sign with is an InvalidConfigException.

Example

<?php

require __DIR__.'/vendor/autoload.php';

use Omnisign\Docusign\DocusignGatewayFactory;
use Omnisign\Model\Document;
use Omnisign\Model\Envelope;
use Omnisign\Model\Field;
use Omnisign\Model\File;
use Omnisign\Model\Signer;
use Symfony\Component\HttpClient\HttpClient;

$docusign = (new DocusignGatewayFactory(HttpClient::create()))->create([
    'integration_key' => getenv('DOCUSIGN_INTEGRATION_KEY'),
    'user_id' => getenv('DOCUSIGN_USER_ID'),
    'private_key' => getenv('DOCUSIGN_PRIVATE_KEY'),
    'account_id' => getenv('DOCUSIGN_ACCOUNT_ID'),
]);

$envelope = $docusign->create(new Envelope('Devis 2026-042', [new Document('quote', File::fromPath('quote.pdf'))], [new Signer('client', 'Camille Érable', 'camille@erable.example')], [new Field('client', 'quote', anchor: '/sign/')], embedded: true, redirectUrl: 'https://site.example/quote/42/back'));
$envelope = $docusign->send($envelope);
$url = $docusign->signingUrl($envelope, 'client')->url;   // the recipient view, to use at once

Run on 2026-10-26 on a MockHttpClient serving this package's fixtures (the token, then the envelope; a key pair generated for the run), with the Connect message signed as DocuSign does:

docusign
  > POST https://account-d.docusign.com/oauth/token
  > POST https://demo.docusign.net/restapi/v2.1/accounts/0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d/envelopes
  7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f draft client=1 waiting
  notify: signed recipient-completed 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f 1 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f@recipient-completed@1@2026-10-26T09:00:00Z

DocuSign itself was not called. In Symfony:

omnisign:
    gateways:
        quotes:
            factory: docusign
            options:
                integration_key: '%env(DOCUSIGN_INTEGRATION_KEY)%'
                user_id: '%env(DOCUSIGN_USER_ID)%'
                private_key: '%env(DOCUSIGN_PRIVATE_KEY)%'
                account_id: '%env(DOCUSIGN_ACCOUNT_ID)%'
                sandbox: false
                hmac_keys: '%env(default::DOCUSIGN_HMAC_KEYS)%'

Errors

Answer Exception
the JWT grant answering 400 or more, or without access_token InvalidKeyException (The JWT grant was refused (HTTP 400): consent_required, providerCode the error)
401, 403 InvalidKeyException
other 4xx ProviderException (<METHOD> <path>: HTTP <status>, <errorCode> <message>, providerCode the errorCode)
5xx, 429, no answer, a timeout (10 s) UnreachableException
the account id not among the user's accounts (production, no base_uri) InvalidConfigException
a private key it cannot sign with; a level other than simple; no document or no signer; a signer authentication other than none; a field on no document; a signing view without a return URL or for an envelope not embedded InvalidConfigException, before the call
a Connect message that does not hold, or is not JSON InvalidNotificationException

Verified, and not

Against DocuSign not verified in real: no developer account. The answers in Tests/Fixtures are written from the OpenAPI and the guides
The JWT: its header, claims and RS256 signature by the tests, checked with the key pair's public half
The envelope's body, the tabs, the recipient view, the HMAC by the tests, against the OpenAPI and the guides

The package's tests (Tests/DocusignGatewayTest.php, 5 tests) pass on 2026-10-26 (PHP 8.4).

Responsibility

This package is an independent, unofficial client: it is not affiliated with, endorsed by or sponsored by DocuSign, Inc. Whoever runs it complies with DocuSign's Developer Terms and Conditions; see the README's Responsibility section.

Propose a change docs/index.md