DocuSign and Dropbox Sign skill for Codex
Contracts signed in-app, status from verified webhooks. Codex builds e-signature flows that create envelopes as drafts and send them only after you approve, place signatures with anchor strings instead of brittle coordinates, and generate embedded signing links on demand. Completion comes from HMAC-verified Connect or Dropbox Sign callbacks, and the signed PDF plus certificate are stored with a hash.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- docusign-esign Installs to ~/.agents/skills/docusign-esign/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install docusign-esign The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project docusign-esign Installs to .agents/skills/docusign-esign/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/docusign-esign.zipunzip -o docusign-esign.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) docusign-esign PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $docusign-esign, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds e-signature flows with the DocuSign eSignature REST API v2.1 and the Dropbox Sign API - JWT Grant and consent, envelopes from documents or templates, anchor tabs, embedded signing with clientUserId, Connect webhooks with HMAC checks, downloading completed documents and the certificate, and go-live. Use when an app must send contracts for signature, embed signing in its own UI, track envelope status, migrate between DocuSign and Dropbox Sign, or fix consent_required and tab placement problems.
Use it when
- Sending contracts or forms for electronic signature from an app
- Embedding the signing ceremony in your own UI
- Tracking envelope or signature request status with webhooks
- Moving from DocuSign demo to production or between providers
Use something else when
- You need to generate the contract PDF itself: use pdf-generation
- You want a legal review of the contract terms: use contract-review-checklist
How to check the result
A demo envelope goes from draft to signed through an embedded view, the HMAC-checked webhook updates the record, and the combined PDF and certificate are stored with a hash.
The mistake it prevents
Trusting the browser return URL's signing_complete parameter instead of the verified webhook to mark a contract signed.
Done well means: nothing is sent to a real signer without the user’s approval, signer identity and routing are explicit, completion is learned from verified webhooks (not polling), the signed PDF and its certificate of completion are stored immutably, and the whole flow runs in the demo or test environment before production.
Inspect first
- Read
AGENTS.md; searchrg -n "docusign|hellosign|dropbox/sign|envelope" --glob '!node_modules'. - Identify the provider account, environment (DocuSign demo
account-d.docusign.comvs production; Dropbox Signtest_mode), and whether signers sign by email (remote) or inside the app (embedded). - List the document sources: static PDFs, server templates, or generated documents (see
$pdf-generationor$docx-templates). - Ask who reviews contract wording and signature legality for the target jurisdictions. That is a lawyer’s call, not the integration’s; this skill covers mechanics only.
Pick the pattern
| Need | DocuSign | Dropbox Sign |
|---|---|---|
| Send for signature by email | Envelope status: "sent" | signature_request/send |
| Sign inside your app | Recipient with clientUserId + views/recipient | signature_request/create_embedded + embedded/sign_url |
| Same document every time | Server template + templateRoles | Template + send_with_template |
| Prepare and review before sending | Envelope status: "created" (draft), then sender view | Unclaimed draft |
| Server-to-server, no user login | JWT Grant with impersonation | API key |
DocuSign authentication
- Backend integrations use JWT Grant: an integration key, an RSA key pair, and the impersonated
user’s GUID. The user (or an org admin) must grant consent once by visiting
https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=<KEY>&redirect_uri=<REGISTERED_URI>(account.docusign.comin production). Aconsent_requirederror means this step was skipped. - After getting a token, call
GET /oauth/userinfoand use the account’sbase_uri; production accounts live on different hosts (na3,eu, …), so never hard-code the REST base. - Tokens from JWT Grant last about an hour; cache and renew. The private key is a secret.
import docusign from 'docusign-esign';
const api = new docusign.ApiClient();
api.setOAuthBasePath('account-d.docusign.com');
const { body } = await api.requestJWTUserToken(
process.env.DS_INTEGRATION_KEY, process.env.DS_USER_ID, ['signature', 'impersonation'],
Buffer.from(process.env.DS_PRIVATE_KEY!, 'utf8'), 3600,
);
const info = await api.getUserInfo(body.access_token);
const account = info.accounts.find((a) => a.isDefault === 'true');
api.setBasePath(`${account.baseUri}/restapi`);
api.addDefaultHeader('Authorization', `Bearer ${body.access_token}`);
Create an envelope
Raw request shape (the SDK models mirror it):
POST /restapi/v2.1/accounts/{accountId}/envelopes
{
"emailSubject": "Please sign: Services Agreement",
"status": "created",
"documents": [{ "documentId": "1", "name": "agreement.pdf", "fileExtension": "pdf", "documentBase64": "<...>" }],
"recipients": {
"signers": [{
"recipientId": "1", "routingOrder": "1",
"email": "[email protected]", "name": "Dana Client",
"clientUserId": "user_812",
"tabs": {
"signHereTabs": [{ "anchorString": "/sig1/", "anchorUnits": "pixels", "anchorXOffset": "0", "anchorYOffset": "-10" }],
"dateSignedTabs": [{ "anchorString": "/date1/" }],
"textTabs": [{ "anchorString": "/title1/", "tabLabel": "signer_title", "required": "true" }]
}
}],
"carbonCopies": [{ "recipientId": "2", "routingOrder": "2", "email": "[email protected]", "name": "Acme Legal" }]
},
"customFields": { "textCustomFields": [{ "name": "contract_id", "value": "C-2026-0412", "show": "false" }] }
}
- Create as
created(draft), show the user the recipients and documents, and switch tosent(PUT .../envelopes/{id}with{"status":"sent"}) only after they confirm. Sending emails real people and starts a legal process. - Anchor tabs beat x/y coordinates: put invisible anchor strings (white text) in the source document so layout changes do not move signatures. Unmatched anchors produce no tab; check the response.
- Store
envelopeIdwith your record and put your own ID incustomFieldsfor reconciliation. - Same signer twice: unique
recipientIdper role; routing order controls sequence.
Embedded signing
POST /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/recipient
{ "returnUrl": "https://app.acme.example/sign/done?envelope=<id>", "authenticationMethod": "none",
"email": "[email protected]", "userName": "Dana Client", "clientUserId": "user_812" }
email,userNameandclientUserIdmust match the recipient exactly.- The returned URL is single-use and expires within minutes; generate it when the user clicks Sign, never in an email or ahead of time.
- Your app is responsible for authenticating the signer before generating the view; record how
(
authenticationMethoddescribes it in the audit trail). - The
eventquery parameter onreturnUrl(signing_complete,decline,cancel) is a UI hint only; trust the webhook for status.
Connect webhooks (DocuSign)
Configure an envelope-level eventNotification or an account Connect configuration with JSON
format, HMAC enabled, and events such as envelope-completed, envelope-declined,
envelope-voided, recipient-completed.
const sig = crypto.createHmac('sha256', process.env.DS_CONNECT_HMAC_KEY!).update(rawBody).digest('base64');
const given = req.header('x-docusign-signature-1') ?? '';
if (!(given.length === sig.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(sig)))) return res.sendStatus(401);
Return 200 quickly, enqueue, and deduplicate on envelopeId + event + generatedDateTime.
DocuSign retries failed deliveries, so handlers must be idempotent. Avoid polling envelope status;
DocuSign limits how often the same envelope may be polled and enforces hourly API limits.
Completed documents
On envelope-completed:
GET .../envelopes/{id}/documents/combined(signed PDF) and.../documents/certificate(certificate of completion).- Store both in write-once storage with a SHA-256 hash and the envelope ID.
- Update your record’s status from the webhook, not from the browser redirect.
Dropbox Sign equivalents
- Auth: API key as HTTP Basic username (
https://api.hellosign.com/v3/...), or OAuth for apps acting for other accounts. SDK:@dropbox/sign. test_mode: 1makes requests non-binding and free; keep it on until go-live.- Embedded signing needs an API app
client_id,create_embedded, thenembedded/sign_url/{signature_id}and thehellosign-embeddedclient library on the page. - Callbacks arrive as multipart form posts with a
jsonfield; verifyevent.event_hash= HMAC-SHA256(key = API key, message =event_time + event_type) hex, and respond with the bodyHello API Event Received, or delivery is treated as failed.
Going live
- DocuSign: complete the go-live review for the integration key from the demo account (it checks
recent successful API traffic), then use production OAuth host and
base_urifrom userinfo, and redo consent in production. - Swap environment by configuration only; keep demo and production keys separate.
Verification
- Demo envelope: draft created, user approves, sent, signed via embedded view, Connect webhook received and verified, combined PDF and certificate stored with hash.
- Every anchor string in the template produces a tab (count tabs in the create response).
- Declined and voided envelopes update the record correctly.
- A webhook with a modified body returns 401; a duplicate delivery is processed once.
- No production envelope was sent during development.
Gotchas
documentBase64of a PDF with form fields may turn them into tabs (transformPdfFields); decide deliberately.- Envelopes cannot be deleted once sent; voiding requires a reason and notifies recipients. Confirm first.
- Email addresses in recipients are case-insensitive for DocuSign but your
clientUserIdmatch is exact. - Large documents: base64 inflates size by about a third; check request limits and use chunked uploads for big files.
Output
Report: provider and environment, auth method, envelope or request structure, signer flow, webhook events and verification, document storage, and test results.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "DocuSign and Dropbox Sign"
short_description: "Contracts signed in-app, status from verified webhooks"
default_prompt: "$docusign-esign Let customers sign the service agreement inside our app with DocuSign" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.